deadbyte-mcp 0.15.0 → 0.15.2
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 +109 -64
- package/PROVENANCE.json +11 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +36 -20
- package/RELEASE.json +53 -0
- package/SBOM.cdx.json +46 -0
- package/TEST-MATRIX.json +31 -0
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/controller/README.TXT +2 -2
- package/controller/deadbyte-controller.ps1 +51 -3
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +14 -7
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r45.mjs +126 -0
- package/scripts/deferred-slot-operation-r46.mjs +126 -0
- package/scripts/derive-r45-parent.mjs +24 -0
- package/scripts/derive-r45-release.mjs +72 -0
- package/scripts/derive-r45-tests.mjs +20 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-r46.mjs +98 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-r46.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/gate-windows-r46.ps1 +56 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-r46.mjs +187 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/scripts/windows-gate-evidence-r46.mjs +110 -0
- package/src/autonomous-runtime.mjs +12 -7
- package/src/capability-ledger.mjs +2 -0
- package/src/compact-gateway-mcp-tools.mjs +11 -7
- package/src/deadbyte-cli.mjs +24 -12
- package/src/doctor-r45.mjs +65 -0
- package/src/host-plane.mjs +133 -85
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs.mjs +63 -10
- package/src/mcp-server.mjs +68 -23
- package/src/mcp-smoke-client.mjs +4 -1
- package/src/native-diff-v2.mjs +9 -4
- package/src/native-diff.mjs +29 -7
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +57 -5
- package/src/observation-smoke-client.mjs +6 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-mcp-tools.mjs +18 -5
- package/src/process-output-contracts.mjs +20 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +99 -19
- package/src/process-smoke-client.mjs +67 -22
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +23 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/runtime-update.mjs +36 -8
- package/src/stdio-console-guard.mjs +24 -0
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -0
- package/test/autonomous-loop.test.mjs +24 -0
- package/test/autonomous-runtime.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/coding-plane.test.mjs +1 -0
- package/test/compact-gateway-r36.test.mjs +28 -0
- package/test/compact-max-catalog-r45.test.mjs +99 -0
- package/test/contained.test.mjs +23 -11
- package/test/controller.test.mjs +5 -1
- package/test/core.test.mjs +1 -1
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/full-max-catalog-r46.test.mjs +92 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/observation-runtime.test.mjs +29 -2
- package/test/output-schema.test.mjs +1 -0
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-mcp-tools.test.mjs +23 -5
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +111 -1
- package/test/process-supervision.test.mjs +4 -2
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +20 -19
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +26 -11
- package/test/release-version.test.mjs +112 -46
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { lstat, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
8
|
+
const UPDATE_DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
|
|
9
|
+
const CLOSURE_DOMAIN=Buffer.from('DEADBYTE-R45-FINAL-CLOSURE-V1\0','utf8');
|
|
10
|
+
const PING_DOMAIN=Buffer.from('DEADBYTE-MACHINE-PING-V1\0','utf8');
|
|
11
|
+
const R45_PREDECESSOR_RELEASE_ID='v0.15.0-c15fa41719be61b3';
|
|
12
|
+
const R45_PREDECESSOR_MANIFEST='c15fa41719be61b3186e49b2367a5f4c057365cfb465102dbefb7d7355f45483';
|
|
13
|
+
const R45_FULL_TOOL_COUNT=82;
|
|
14
|
+
const R45_COMPACT_TOOL_COUNT=43;
|
|
15
|
+
const REQUIRED_PARITY_KEYS=['tree_read','precondition_mutation','search','owned_process','observation','coding_loop','autonomy','interrupted_recovery','evidence_verification','compact_closure','full_surface','desktop_readonly','desktop_guarded_input','deterministic_memory','skill_integrity','agent_backend_persistence','codex_protocol_contract','remote_device_identity','durable_job_supervisor','real_external_supervisor_smoke'];
|
|
16
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
17
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
18
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
19
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
20
|
+
const same=(a,b)=>canonicalJson(a)===canonicalJson(b);
|
|
21
|
+
function insist(ok,msg){if(!ok) throw new Error(msg);}
|
|
22
|
+
function samePath(a,b){return path.resolve(String(a)).toLowerCase()===path.resolve(String(b)).toLowerCase();}
|
|
23
|
+
function isCandidateIdentity(value,manifest){
|
|
24
|
+
return value?.version==='0.15.1'&&value?.manifest_sha256===manifest&&value?.release_id===`v0.15.1-${manifest.slice(0,16)}`;
|
|
25
|
+
}
|
|
26
|
+
function isR44PredecessorIdentity(value){
|
|
27
|
+
return value?.version==='0.15.0'&&value?.manifest_sha256===R45_PREDECESSOR_MANIFEST&&value?.release_id===R45_PREDECESSOR_RELEASE_ID;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseManifest(text){
|
|
31
|
+
const out=new Map();
|
|
32
|
+
for(const [i,line] of text.split(/\r?\n/).entries()){
|
|
33
|
+
if(!line) continue;
|
|
34
|
+
const m=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);insist(m,`manifest line ${i+1} malformed`);
|
|
35
|
+
insist(!path.posix.isAbsolute(m[2])&&!m[2].split('/').includes('..')&&!out.has(m[2]),`unsafe/duplicate manifest path ${m[2]}`);
|
|
36
|
+
out.set(m[2],m[1]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
async function walk(root,rel=''){
|
|
41
|
+
const dir=path.join(root,...(rel?rel.split('/'):[]));
|
|
42
|
+
const names=(await readdir(dir)).sort((a,b)=>a.localeCompare(b,'en'));
|
|
43
|
+
const files=[];
|
|
44
|
+
for(const name of names){
|
|
45
|
+
const childRel=rel?`${rel}/${name}`:name;
|
|
46
|
+
const full=path.join(root,...childRel.split('/'));
|
|
47
|
+
const st=await lstat(full);insist(!st.isSymbolicLink(),`symlink forbidden: ${childRel}`);
|
|
48
|
+
if(st.isDirectory()) files.push(...await walk(root,childRel));
|
|
49
|
+
else{insist(st.isFile(),`non-file forbidden: ${childRel}`);files.push(childRel);}
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
async function verifyTree(root,manifestName,expectedDigest){
|
|
54
|
+
const manifestBytes=await readFile(path.join(root,manifestName));
|
|
55
|
+
insist(sha256(manifestBytes)===expectedDigest,`${manifestName} digest mismatch`);
|
|
56
|
+
const entries=parseManifest(manifestBytes.toString('utf8'));
|
|
57
|
+
const actual=(await walk(root)).filter(rel=>rel!==manifestName).sort((a,b)=>a.localeCompare(b,'en'));
|
|
58
|
+
const expected=[...entries.keys()].sort((a,b)=>a.localeCompare(b,'en'));
|
|
59
|
+
insist(same(actual,expected),`${manifestName} exact tree mismatch`);
|
|
60
|
+
for(const [rel,digest] of entries) insist(sha256(await readFile(path.join(root,...rel.split('/'))))===digest,`${manifestName} file hash mismatch: ${rel}`);
|
|
61
|
+
return entries.size;
|
|
62
|
+
}
|
|
63
|
+
function publicKeyInfo(publicPem){
|
|
64
|
+
const key=createPublicKey(publicPem);insist(key.asymmetricKeyType==='ed25519','public key must be Ed25519');
|
|
65
|
+
const der=Buffer.from(key.export({type:'spki',format:'der'}));
|
|
66
|
+
return {key,key_id:sha256(der),spki_base64:der.toString('base64')};
|
|
67
|
+
}
|
|
68
|
+
function verifyUpdateEnvelope(env,keyInfo){
|
|
69
|
+
insist(env?.body&&env?.attestation,'update envelope malformed');
|
|
70
|
+
const bodyBytes=Buffer.from(canonicalJson(env.body),'utf8');
|
|
71
|
+
insist(sha256(bodyBytes)===env.body_sha256,'update body hash mismatch');
|
|
72
|
+
const {receipt_sha256,...unsigned}=env;
|
|
73
|
+
insist(sha256(Buffer.from(canonicalJson(unsigned),'utf8'))===receipt_sha256,'update receipt hash mismatch');
|
|
74
|
+
insist(env.attestation.algorithm==='ed25519'&&env.attestation.domain==='DEADBYTE-R27-SLOT-UPDATE-V1','update attestation contract mismatch');
|
|
75
|
+
insist(env.attestation.key_id===keyInfo.key_id,'update signer mismatch');
|
|
76
|
+
insist(cryptoVerify(null,Buffer.concat([UPDATE_DOMAIN,bodyBytes]),keyInfo.key,Buffer.from(env.attestation.signature_base64,'base64')),'update signature invalid');
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function verifyPing(ping,status,pointer){
|
|
80
|
+
insist(ping?.status==='ok'&&ping.schema==='deadbyte.machine-ping.v1','machine ping malformed');
|
|
81
|
+
insist(status?.status==='ok'&&typeof status.public_key_spki_base64==='string','machine status malformed');
|
|
82
|
+
const der=Buffer.from(status.public_key_spki_base64,'base64');
|
|
83
|
+
insist(der.toString('base64')===status.public_key_spki_base64,'machine SPKI base64 non-canonical');
|
|
84
|
+
const machineKey=createPublicKey({key:der,format:'der',type:'spki'});insist(machineKey.asymmetricKeyType==='ed25519','machine key must be Ed25519');
|
|
85
|
+
const machineId=sha256(der);
|
|
86
|
+
insist(status.machine_id===machineId&&ping.machine_id===machineId&&ping.attestation?.key_id===machineId,'machine id/key mismatch');
|
|
87
|
+
insist(ping.release_id===pointer.release_id&&ping.release_manifest_sha256===pointer.manifest_sha256,'machine ping release binding mismatch');
|
|
88
|
+
insist(ping.generation_manifest_sha256===pointer.generation_manifest_sha256,'machine ping generation binding mismatch');
|
|
89
|
+
const {status:_s,attestation:_a,...body}=ping;
|
|
90
|
+
const bytes=Buffer.from(canonicalJson(body),'utf8');
|
|
91
|
+
insist(cryptoVerify(null,Buffer.concat([PING_DOMAIN,bytes]),machineKey,Buffer.from(ping.attestation.signature_base64,'base64')),'machine ping signature invalid');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function verifyFinalClosureObject(envelope,{stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
|
|
95
|
+
insist(envelope?.body?.schema==='deadbyte.r45-final-closure.v1','closure schema mismatch');
|
|
96
|
+
insist(envelope.body.version==='0.15.1','closure version mismatch');
|
|
97
|
+
insist(envelope.body.release_label==='R45 MACHINE FREEDOM FINAL BASELINE','closure release label mismatch');
|
|
98
|
+
const keyInfo=publicKeyInfo(await readFile(path.join(stateRoot,'trust','ed25519-public.pem')));
|
|
99
|
+
const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
|
|
100
|
+
insist(sha256(bodyBytes)===envelope.body_sha256,'closure body hash mismatch');
|
|
101
|
+
const {closure_sha256,...unsigned}=envelope;
|
|
102
|
+
insist(sha256(Buffer.from(canonicalJson(unsigned),'utf8'))===closure_sha256,'closure envelope hash mismatch');
|
|
103
|
+
insist(envelope.attestation?.algorithm==='ed25519'&&envelope.attestation?.domain==='DEADBYTE-R45-FINAL-CLOSURE-V1','closure attestation contract mismatch');
|
|
104
|
+
insist(envelope.attestation.key_id===keyInfo.key_id,'closure signer mismatch');
|
|
105
|
+
insist(cryptoVerify(null,Buffer.concat([CLOSURE_DOMAIN,bodyBytes]),keyInfo.key,Buffer.from(envelope.attestation.signature_base64,'base64')),'closure signature invalid');
|
|
106
|
+
|
|
107
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
108
|
+
insist(same(pointer,envelope.body.active_pointer),'active pointer drift');
|
|
109
|
+
insist(pointer.version==='0.15.1','active pointer version mismatch');
|
|
110
|
+
insist(isCandidateIdentity(pointer,pointer.manifest_sha256),'release id/content address mismatch');
|
|
111
|
+
const releaseFiles=await verifyTree(pointer.release_root,'MANIFEST.SHA256',pointer.manifest_sha256);
|
|
112
|
+
const generationFiles=await verifyTree(pointer.generation_root,'GENERATION.SHA256',pointer.generation_manifest_sha256);
|
|
113
|
+
insist(sha256(await readFile(path.join(stateRoot,'bin','DEADBYTE-MCP.ps1')))===pointer.launcher_sha256,'launcher hash mismatch');
|
|
114
|
+
|
|
115
|
+
const predecessor=envelope.body.predecessor;
|
|
116
|
+
insist(isR44PredecessorIdentity(predecessor),'closure predecessor is not exact sealed R44 predecessor');
|
|
117
|
+
const immutable=envelope.body.immutable_slot;
|
|
118
|
+
insist(immutable?.protected===true,'closure immutable-slot protection missing');
|
|
119
|
+
insist(samePath(immutable.release_root,pointer.release_root),'closure immutable release_root mismatch');
|
|
120
|
+
insist(!samePath(immutable.development_root,immutable.release_root),'closure development_root aliases immutable release_root');
|
|
121
|
+
|
|
122
|
+
const surfaces=envelope.body.surfaces;
|
|
123
|
+
insist(surfaces?.local_full?.status==='passed'&&surfaces.local_full.tool_count===R45_FULL_TOOL_COUNT&&surfaces.local_full.strict_output_schemas===true,'closure local full 82-tool proof invalid');
|
|
124
|
+
insist(HEX64.test(surfaces.local_full.catalog_sha256??''),'closure local full catalog hash missing');
|
|
125
|
+
insist(surfaces?.public_compact?.status==='passed'&&surfaces.public_compact.tool_count===R45_COMPACT_TOOL_COUNT&&surfaces.public_compact.capability_closure===true&&surfaces.public_compact.skill_catalog_verified===true,'closure public compact capability proof invalid');
|
|
126
|
+
insist(surfaces.public_compact.coding_armed===false&&surfaces.public_compact.autonomous_armed===false&&surfaces.public_compact.desktop_armed===false,'closure compact authority is not DISARMED');
|
|
127
|
+
insist(surfaces.public_compact.desktop_observation_receipt_verified===true&&surfaces.public_compact.desktop_grounding_receipt_verified===true,'closure Desktop read-only proof invalid');
|
|
128
|
+
insist(HEX64.test(surfaces.public_compact.catalog_sha256??'')&&HEX64.test(surfaces.public_compact.ledger_sha256??''),'closure compact catalog/ledger hash missing');
|
|
129
|
+
|
|
130
|
+
const evidence=envelope.body.evidence;
|
|
131
|
+
insist(evidence&&evidence.parity&&evidence.activation&&evidence.rollback&&evidence.restore&&evidence.windows_gate,'closure evidence set incomplete');
|
|
132
|
+
for(const [name,item] of Object.entries(evidence)){
|
|
133
|
+
insist(item?.path&&HEX64.test(item.sha256??''),`${name} evidence identity malformed`);
|
|
134
|
+
insist(sha256(await readFile(item.path))===item.sha256,`${name} evidence hash mismatch`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parity=JSON.parse(await readFile(evidence.parity.path,'utf8'));
|
|
138
|
+
insist(parity.schema==='deadbyte.r45-parity.v1'&&parity.status==='passed','R45 parity evidence invalid');
|
|
139
|
+
insist(parity.rdc_operation_count===0&&parity.execution_provider==='deadbyte_mcp','R45 parity execution provider mismatch');
|
|
140
|
+
insist(isCandidateIdentity(parity.candidate,pointer.manifest_sha256),'R45 parity candidate binding mismatch');
|
|
141
|
+
insist(isCandidateIdentity(parity.successor,pointer.manifest_sha256),'R45 parity successor binding mismatch');
|
|
142
|
+
insist(isR44PredecessorIdentity(parity.predecessor),'R45 parity predecessor binding mismatch');
|
|
143
|
+
insist(parity.local_full?.status==='passed'&&parity.local_full.tool_count===R45_FULL_TOOL_COUNT&&parity.local_full.strict_output_schemas===true,'R45 parity local full proof invalid');
|
|
144
|
+
insist(parity.local_full.catalog_sha256===surfaces.local_full.catalog_sha256,'R45 local full catalog drift between parity and closure');
|
|
145
|
+
insist(parity.public_compact?.status==='passed'&&parity.public_compact.tool_count===R45_COMPACT_TOOL_COUNT&&parity.public_compact.capability_closure===true&&parity.public_compact.skill_catalog_verified===true&&parity.public_compact.gateway_count===6&&parity.public_compact.desktop_armed===false&&parity.public_compact.desktop_observation_receipt_verified===true&&parity.public_compact.desktop_grounding_receipt_verified===true,'R45 parity public compact/Desktop proof invalid');
|
|
146
|
+
insist(parity.desktop_guarded?.status==='passed'&&parity.desktop_guarded.native_injected===true&&parity.desktop_guarded.replay_performed===false&&parity.desktop_guarded.action_receipts_verified===3,'R45 parity guarded Desktop proof invalid');
|
|
147
|
+
insist(parity.deterministic_memory?.status==='passed'&&parity.deterministic_memory.goal_count>=1&&parity.deterministic_memory.snapshot_count>=1&&parity.deterministic_memory.index_count>=1,'R41 deterministic memory proof invalid');
|
|
148
|
+
insist(same(surfaces.deterministic_memory,parity.deterministic_memory),'R41 closure deterministic memory binding mismatch');
|
|
149
|
+
insist(parity.successor_disarmed?.status==='passed'&&parity.successor_disarmed.tool_count===R45_COMPACT_TOOL_COUNT&&parity.successor_disarmed.capability_closure===true&&parity.successor_disarmed.skill_catalog_verified===true&&parity.successor_disarmed.coding_armed===false&&parity.successor_disarmed.autonomous_armed===false&&parity.successor_disarmed.desktop_armed===false,'R45 parity successor disarmed proof invalid');
|
|
150
|
+
insist(parity.successor_disarmed.catalog_sha256===surfaces.public_compact.catalog_sha256,'R45 compact catalog drift between restore and closure');
|
|
151
|
+
insist(parity.successor_disarmed.ledger_sha256===surfaces.public_compact.ledger_sha256,'R36 capability ledger drift between restore and closure');
|
|
152
|
+
insist(parity.immutable_slot?.protected===true,'R45 parity immutable-slot proof missing');
|
|
153
|
+
insist(samePath(parity.immutable_slot.release_root,pointer.release_root),'R45 parity immutable release_root mismatch');
|
|
154
|
+
insist(samePath(parity.immutable_slot.development_root,immutable.development_root),'R45 parity/closure development_root mismatch');
|
|
155
|
+
insist(!samePath(parity.immutable_slot.development_root,pointer.release_root),'R36 mutable development_root aliases immutable release slot');
|
|
156
|
+
for(const key of REQUIRED_PARITY_KEYS) insist(parity.requirements?.[key]===true,`R45 parity requirement missing ${key}`);
|
|
157
|
+
const activation=JSON.parse(await readFile(evidence.activation.path,'utf8'));
|
|
158
|
+
const rollback=JSON.parse(await readFile(evidence.rollback.path,'utf8'));
|
|
159
|
+
const restore=JSON.parse(await readFile(evidence.restore.path,'utf8'));
|
|
160
|
+
verifyUpdateEnvelope(activation,keyInfo);verifyUpdateEnvelope(rollback,keyInfo);verifyUpdateEnvelope(restore,keyInfo);
|
|
161
|
+
insist(activation.body.schema==='deadbyte.r27-slot-update-receipt.v1'&&activation.body.rollback_performed===false,'R45 activation receipt invalid');
|
|
162
|
+
insist(activation.body.release_version==='0.15.1'&&activation.body.candidate_manifest_sha256===pointer.manifest_sha256,'R45 activation candidate identity invalid');
|
|
163
|
+
insist(isCandidateIdentity(activation.body.active_pointer,pointer.manifest_sha256),'R45 activation active pointer invalid');
|
|
164
|
+
insist(isR44PredecessorIdentity(activation.body.prior_pointer),'R45 activation predecessor is not exact R44 predecessor');
|
|
165
|
+
insist(samePath(activation.body.development_root,immutable.development_root),'R45 activation development_root mismatch');
|
|
166
|
+
insist(!samePath(activation.body.development_root,activation.body.active_pointer.release_root),'R45 activation development_root aliases release slot');
|
|
167
|
+
insist(activation.body.authority?.final?.desktop_armed===false,'R45 activation restored Desktop authority unexpectedly');
|
|
168
|
+
|
|
169
|
+
insist(rollback.body.schema==='deadbyte.r31-slot-rollback-receipt.v1'&&rollback.body.rollback_performed===true,'R45 rollback receipt invalid');
|
|
170
|
+
insist(isCandidateIdentity(rollback.body.from_pointer,pointer.manifest_sha256),'R45 rollback source is not exact candidate');
|
|
171
|
+
insist(isR44PredecessorIdentity(rollback.body.active_pointer),'R45 rollback target is not exact R44 predecessor');
|
|
172
|
+
insist(rollback.body.activation_receipt_sha256===activation.receipt_sha256,'R45 rollback not bound to activation receipt');
|
|
173
|
+
|
|
174
|
+
insist(restore.body.schema==='deadbyte.r27-slot-update-receipt.v1'&&restore.body.rollback_performed===false,'R45 restore receipt invalid');
|
|
175
|
+
insist(restore.body.release_version==='0.15.1'&&restore.body.candidate_manifest_sha256===pointer.manifest_sha256,'R45 restore candidate identity invalid');
|
|
176
|
+
insist(isR44PredecessorIdentity(restore.body.prior_pointer),'R45 restore predecessor is not exact R44 predecessor');
|
|
177
|
+
insist(isCandidateIdentity(restore.body.active_pointer,pointer.manifest_sha256)&&same(restore.body.active_pointer,pointer),'R45 restore/current pointer mismatch');
|
|
178
|
+
insist(samePath(restore.body.development_root,immutable.development_root),'R45 restore development_root mismatch');
|
|
179
|
+
insist(!samePath(restore.body.development_root,restore.body.active_pointer.release_root),'R45 restore development_root aliases release slot');
|
|
180
|
+
insist(restore.body.authority?.final?.desktop_armed===false,'R45 restore restored Desktop authority unexpectedly');
|
|
181
|
+
|
|
182
|
+
const windowsGate=JSON.parse(await readFile(evidence.windows_gate.path,'utf8'));
|
|
183
|
+
insist(windowsGate.schema==='deadbyte.r45-windows-gate.v1'&&windowsGate.status==='passed'&&windowsGate.exit_code===0,'R45 Windows gate evidence invalid');
|
|
184
|
+
insist(windowsGate.version==='0.15.1'&&windowsGate.manifest_sha256===pointer.manifest_sha256,'R45 Windows gate manifest/version binding mismatch');
|
|
185
|
+
insist(windowsGate.marker==='DEADBYTE V0.15.1 / R45 MACHINE FREEDOM WINDOWS BATCH GATE: PASS','R45 Windows gate PASS marker mismatch');
|
|
186
|
+
|
|
187
|
+
const featureContract=surfaces.feature_contract;
|
|
188
|
+
insist(featureContract?.schema==='deadbyte.r45-windows-gate.v1'&&featureContract.manifest_sha256===pointer.manifest_sha256,'closure feature contract invalid');
|
|
189
|
+
insist(parity.feature_gate_sha256===evidence.windows_gate.sha256,'parity feature gate hash mismatch');
|
|
190
|
+
for(const key of ['skill_integrity','agent_backend_persistence','codex_protocol_contract','remote_device_identity','durable_job_supervisor','real_external_supervisor_smoke','full_regression_required','host_inline_native','native_hardware_observation','doctor_release_truth']) {
|
|
191
|
+
insist(windowsGate.features?.[key]===true&&featureContract.features?.[key]===true,`R45 feature proof missing ${key}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const machine=envelope.body.machine;
|
|
195
|
+
insist(machine?.status?.release_id===pointer.release_id&&machine.status.release_manifest_sha256===pointer.manifest_sha256&&machine.status.generation_manifest_sha256===pointer.generation_manifest_sha256,'machine status binding mismatch');
|
|
196
|
+
verifyPing(machine.ping,machine.status,pointer);
|
|
197
|
+
insist(machine.ping.nonce===machine.nonce,'machine ping nonce binding mismatch');
|
|
198
|
+
|
|
199
|
+
const grantNames=['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json'];
|
|
200
|
+
insist(envelope.body.authority?.all_disarmed===true,'closure authority not disarmed');
|
|
201
|
+
for(const name of grantNames){
|
|
202
|
+
let exists=true;try{await lstat(path.join(stateRoot,'runtime',name));}catch(error){if(error?.code==='ENOENT') exists=false;else throw error;}
|
|
203
|
+
insist(!exists,`authority grant still present: ${name}`);
|
|
204
|
+
insist(envelope.body.authority.grants_absent?.[name]===true,`closure grant absence not recorded: ${name}`);
|
|
205
|
+
}
|
|
206
|
+
return {ok:true,status:'passed',release_id:pointer.release_id,manifest_sha256:pointer.manifest_sha256,
|
|
207
|
+
generation_manifest_sha256:pointer.generation_manifest_sha256,release_files:releaseFiles,generation_files:generationFiles,
|
|
208
|
+
machine_id:machine.status.machine_id,parity_sha256:evidence.parity.sha256,closure_sha256:envelope.closure_sha256,
|
|
209
|
+
full_tool_count:R45_FULL_TOOL_COUNT,compact_tool_count:surfaces.public_compact.tool_count};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
213
|
+
if(invoked){
|
|
214
|
+
const file=process.argv[2];
|
|
215
|
+
if(!file) throw new Error('usage: node scripts/final-closure-verify-r45.mjs <closure.json>');
|
|
216
|
+
try{
|
|
217
|
+
const envelope=JSON.parse(await readFile(path.resolve(file),'utf8'));
|
|
218
|
+
console.log(JSON.stringify(await verifyFinalClosureObject(envelope),null,2));
|
|
219
|
+
}catch(error){console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;}
|
|
220
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const DOMAIN=Buffer.from('DEADBYTE-R46-FINAL-CLOSURE-V1\0','utf8');
|
|
8
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const packageRoot=path.resolve(here,'..');
|
|
10
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
11
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
13
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
14
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
15
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
16
|
+
|
|
17
|
+
export async function verifyFinalClosureObject(envelope,{root=packageRoot,runtimeStateRoot=stateRoot}={}){
|
|
18
|
+
const candidate=path.resolve(root),state=path.resolve(runtimeStateRoot);
|
|
19
|
+
insist(envelope&&typeof envelope==='object'&&!Array.isArray(envelope),'R46 closure envelope invalid');
|
|
20
|
+
insist(envelope.body?.schema==='deadbyte.r46-final-closure.v1','R46 closure schema mismatch');
|
|
21
|
+
insist(envelope.body?.version==='0.15.2'&&envelope.body?.train==='R46','R46 closure version/train mismatch');
|
|
22
|
+
insist(envelope.body?.release_label==='FILELESS POWERSHELL PRODUCT BASELINE','R46 closure label mismatch');
|
|
23
|
+
|
|
24
|
+
const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
|
|
25
|
+
insist(envelope.body_sha256===sha256(bodyBytes),'R46 closure body SHA-256 mismatch');
|
|
26
|
+
insist(envelope.attestation?.algorithm==='ed25519'&&envelope.attestation?.domain==='DEADBYTE-R46-FINAL-CLOSURE-V1','R46 closure attestation contract mismatch');
|
|
27
|
+
const publicKey=createPublicKey(await readFile(path.join(state,'trust','ed25519-public.pem')));
|
|
28
|
+
insist(publicKey.asymmetricKeyType==='ed25519','R46 closure verify key must be Ed25519');
|
|
29
|
+
const keyId=sha256(Buffer.from(publicKey.export({type:'spki',format:'der'})));
|
|
30
|
+
insist(envelope.attestation.key_id===keyId,'R46 closure signer key mismatch');
|
|
31
|
+
insist(typeof envelope.attestation.signature_base64==='string'&&cryptoVerify(
|
|
32
|
+
null,Buffer.concat([DOMAIN,bodyBytes]),publicKey,Buffer.from(envelope.attestation.signature_base64,'base64')
|
|
33
|
+
),'R46 closure signature invalid');
|
|
34
|
+
|
|
35
|
+
const unsigned={body:envelope.body,body_sha256:envelope.body_sha256,attestation:envelope.attestation};
|
|
36
|
+
const closureSha256=sha256(Buffer.from(canonicalJson(unsigned),'utf8'));
|
|
37
|
+
insist(envelope.closure_sha256===closureSha256,'R46 closure digest mismatch');
|
|
38
|
+
|
|
39
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
40
|
+
const manifestSha256=sha256(manifestBytes);
|
|
41
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
42
|
+
insist(pointer.version==='0.15.2'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer/candidate mismatch');
|
|
43
|
+
insist(pointer.release_id===`v0.15.2-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
|
|
44
|
+
insist(envelope.body.active_pointer?.release_id===pointer.release_id&&
|
|
45
|
+
envelope.body.active_pointer?.manifest_sha256===pointer.manifest_sha256&&
|
|
46
|
+
envelope.body.active_pointer?.generation_manifest_sha256===pointer.generation_manifest_sha256,
|
|
47
|
+
'R46 closure active pointer drift');
|
|
48
|
+
|
|
49
|
+
const gateRef=envelope.body.evidence?.windows_gate;
|
|
50
|
+
const parityRef=envelope.body.evidence?.parity;
|
|
51
|
+
for(const [label,ref] of [['windows gate',gateRef],['parity',parityRef]]){
|
|
52
|
+
insist(ref&&typeof ref.path==='string'&&/^[0-9a-f]{64}$/.test(String(ref.sha256??'')),`R46 ${label} evidence reference invalid`);
|
|
53
|
+
const bytes=await readFile(path.resolve(ref.path));
|
|
54
|
+
insist(sha256(bytes)===ref.sha256,`R46 ${label} evidence hash mismatch`);
|
|
55
|
+
}
|
|
56
|
+
const gate=JSON.parse(await readFile(path.resolve(gateRef.path),'utf8'));
|
|
57
|
+
const parity=JSON.parse(await readFile(path.resolve(parityRef.path),'utf8'));
|
|
58
|
+
insist(gate.schema==='deadbyte.r46-windows-gate.v1'&&gate.status==='passed'&&gate.version==='0.15.2','R46 gate evidence invalid');
|
|
59
|
+
insist(gate.manifest_sha256===manifestSha256,'R46 gate candidate mismatch');
|
|
60
|
+
insist(gate.parent?.release_id==='v0.15.1-d55f00b5144dc31a'&&gate.parent?.manifest_sha256==='d55f00b5144dc31a7fe39e4dc60d640a94780da6aaeca0918e87636b816d7854','R46 sealed parent identity invalid');
|
|
61
|
+
insist(gate.runtime_predecessor?.release_id==='v0.15.2-0fb46542f6a4345f'&&gate.runtime_predecessor?.manifest_sha256==='0fb46542f6a4345f1c527565d06bf6e9b8e283260e47bf684fb70a60308fcf34','R46 reopened runtime predecessor identity invalid');
|
|
62
|
+
insist(envelope.body.runtime_predecessor?.release_id===gate.runtime_predecessor.release_id&&envelope.body.runtime_predecessor?.manifest_sha256===gate.runtime_predecessor.manifest_sha256,'R46 closure runtime predecessor mismatch');
|
|
63
|
+
insist(parity.schema==='deadbyte.r46-parity.v1'&&parity.status==='passed'&&parity.version==='0.15.2','R46 parity evidence invalid');
|
|
64
|
+
insist(parity.candidate?.release_id===pointer.release_id&&
|
|
65
|
+
parity.candidate?.manifest_sha256===pointer.manifest_sha256&&
|
|
66
|
+
parity.candidate?.generation_manifest_sha256===pointer.generation_manifest_sha256,
|
|
67
|
+
'R46 parity active generation mismatch');
|
|
68
|
+
insist(parity.process_policy?.target_repin_verified===true,'R46 target-machine process repin proof missing');
|
|
69
|
+
insist(parity.fileless_powershell?.exit_code===0&&
|
|
70
|
+
parity.fileless_powershell?.pscommandpath_empty===true&&
|
|
71
|
+
parity.fileless_powershell?.invocation_path_empty===true&&
|
|
72
|
+
parity.fileless_powershell?.new_temp_ps1_count===0,
|
|
73
|
+
'R46 fileless PowerShell proof invalid');
|
|
74
|
+
insist(parity.persistent_powershell_repl?.state_persisted===true&&
|
|
75
|
+
parity.persistent_powershell_repl?.observed_value===42&&
|
|
76
|
+
parity.persistent_powershell_repl?.wait_for_verified===true&&
|
|
77
|
+
parity.persistent_powershell_repl?.tail_read_verified===true&&
|
|
78
|
+
parity.persistent_powershell_repl?.raw_input_not_journaled===true,
|
|
79
|
+
'R46 persistent PowerShell REPL proof invalid');
|
|
80
|
+
insist(parity.retained_output_ring?.eviction_verified===true&&
|
|
81
|
+
parity.retained_output_ring?.tail_marker_verified===true&&
|
|
82
|
+
parity.retained_output_ring?.earliest_offset>0,
|
|
83
|
+
'R46 retained-output ring proof invalid');
|
|
84
|
+
insist(envelope.body.process_policy?.target_repin_verified===true,'R46 closure process repin proof invalid');
|
|
85
|
+
insist(envelope.body.fileless_powershell?.exit_code===0&&
|
|
86
|
+
envelope.body.fileless_powershell?.new_temp_ps1_count===0,
|
|
87
|
+
'R46 closure fileless proof invalid');
|
|
88
|
+
insist(envelope.body.persistent_powershell_repl?.state_persisted===true&&
|
|
89
|
+
envelope.body.persistent_powershell_repl?.observed_value===42&&
|
|
90
|
+
envelope.body.persistent_powershell_repl?.raw_input_not_journaled===true,
|
|
91
|
+
'R46 closure persistent REPL proof invalid');
|
|
92
|
+
insist(envelope.body.retained_output_ring?.eviction_verified===true&&
|
|
93
|
+
envelope.body.retained_output_ring?.tail_marker_verified===true,
|
|
94
|
+
'R46 closure retained-output ring proof invalid');
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok:true,status:'passed',version:'0.15.2',train:'R46',
|
|
98
|
+
release_id:pointer.release_id,
|
|
99
|
+
manifest_sha256:pointer.manifest_sha256,
|
|
100
|
+
generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
101
|
+
closure_sha256:envelope.closure_sha256,
|
|
102
|
+
signer_key_id:keyId,
|
|
103
|
+
fileless_powershell:true,
|
|
104
|
+
target_machine_process_repin:true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function verifyFinalClosure({file=null,root=packageRoot,runtimeStateRoot=stateRoot}={}){
|
|
109
|
+
const state=path.resolve(runtimeStateRoot),candidate=path.resolve(root);
|
|
110
|
+
const manifestSha256=sha256(await readFile(path.join(candidate,'MANIFEST.SHA256')));
|
|
111
|
+
const target=file?path.resolve(file):path.join(state,'evidence','release',`r46-final-${manifestSha256.slice(0,16)}.json`);
|
|
112
|
+
const envelope=JSON.parse(await readFile(target,'utf8'));
|
|
113
|
+
return {...await verifyFinalClosureObject(envelope,{root:candidate,runtimeStateRoot:state}),path:target};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
117
|
+
if(invoked)verifyFinalClosure({file:process.argv[2]??null})
|
|
118
|
+
.then(value=>console.log(JSON.stringify(value,null,2)))
|
|
119
|
+
.catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { lstat, mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parseActivePointer } from '../src/release-slot.mjs';
|
|
8
|
+
import { loadPowerShellDataFile, parseControllerStatus, runSlotUpdateCli, windowsPowerShellEnvironment } from '../src/runtime-update.mjs';
|
|
9
|
+
import { verifyR45Parent } from './verify-r45-parent.mjs';
|
|
10
|
+
import { runReleaseParity } from './release-parity-r45.mjs';
|
|
11
|
+
import { writeFinalClosure } from './final-closure-r45.mjs';
|
|
12
|
+
import { verifyFinalClosureObject } from './final-closure-verify-r45.mjs';
|
|
13
|
+
|
|
14
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
15
|
+
const EXPECTED_VERSION='0.15.1';
|
|
16
|
+
const PARENT_RELEASE_ID='v0.15.0-c15fa41719be61b3';
|
|
17
|
+
const PARENT_MANIFEST='c15fa41719be61b3186e49b2367a5f4c057365cfb465102dbefb7d7355f45483';
|
|
18
|
+
const PARENT_FINAL_CLOSURE='533b7e0ff9ded04086dd90ed34e3b87bc427acc3c55c25bb9f4ec66cb448e667';
|
|
19
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const packageRoot=path.resolve(here,'..');
|
|
21
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
22
|
+
const evidenceRoot=path.join(stateRoot,'evidence','release','r45-finalization');
|
|
23
|
+
const launcher=path.join(stateRoot,'bin','DEADBYTE-MCP.ps1');
|
|
24
|
+
const ps=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
|
|
25
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
26
|
+
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
27
|
+
const insist=(ok,message)=>{if(!ok)throw new Error(message);};
|
|
28
|
+
|
|
29
|
+
async function atomicJson(file,value){
|
|
30
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
31
|
+
const bytes=Buffer.from(`${JSON.stringify(value,null,2)}\n`,'utf8');
|
|
32
|
+
const temp=`${file}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
|
|
33
|
+
await writeFile(temp,bytes,{flag:'wx'});
|
|
34
|
+
await rename(temp,file);
|
|
35
|
+
return {path:file,sha256:sha256(bytes)};
|
|
36
|
+
}
|
|
37
|
+
async function activePointer(){
|
|
38
|
+
return parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
39
|
+
}
|
|
40
|
+
async function bridgeArgs(){
|
|
41
|
+
const pointer=await activePointer();
|
|
42
|
+
const config=await loadPowerShellDataFile(pointer.config_path);
|
|
43
|
+
const raw=config?.Bridge?.WorkingDirectory;
|
|
44
|
+
insist(typeof raw==='string'&&path.isAbsolute(raw),'active Bridge.WorkingDirectory must be absolute');
|
|
45
|
+
const root=await realpath(path.resolve(raw));
|
|
46
|
+
const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
|
|
47
|
+
insist(pkg?.name==='deadbyte-chatgpt-bridge','active Bridge package identity mismatch');
|
|
48
|
+
const script=path.join(root,'src','bridge.mjs');
|
|
49
|
+
return ['--bridge-root',root,'--bridge-script-sha256',sha256(await readFile(script))];
|
|
50
|
+
}
|
|
51
|
+
async function activationArgs(manifestSha256){
|
|
52
|
+
insist(HEX64.test(manifestSha256??''),'R45 finalizer manifest must be lowercase SHA-256');
|
|
53
|
+
const processPolicy=path.join(packageRoot,'controller','deadbyte-process-policy.json');
|
|
54
|
+
const hostPolicy=path.join(packageRoot,'controller','deadbyte-host-policy.json');
|
|
55
|
+
const desktopPolicy=path.join(packageRoot,'controller','deadbyte-desktop-policy.json');
|
|
56
|
+
const semantic=path.join(packageRoot,'controller','deadbyte-semantic-lsp-config.json');
|
|
57
|
+
return [
|
|
58
|
+
'--stage',packageRoot,'--development-root',packageRoot,'--expected-version',EXPECTED_VERSION,
|
|
59
|
+
'--expected-manifest',manifestSha256,
|
|
60
|
+
'--process-policy',processPolicy,'--process-policy-sha256',sha256(await readFile(processPolicy)),
|
|
61
|
+
'--host-policy',hostPolicy,'--host-policy-sha256',sha256(await readFile(hostPolicy)),
|
|
62
|
+
'--desktop-policy',desktopPolicy,'--desktop-policy-sha256',sha256(await readFile(desktopPolicy)),
|
|
63
|
+
'--semantic-lsp-config',semantic,'--semantic-lsp-config-sha256',sha256(await readFile(semantic)),
|
|
64
|
+
'--enable-machine-fs','--enable-observation','--enable-desktop','--enable-autonomous-supervisor',
|
|
65
|
+
'--restore-standing-grants',
|
|
66
|
+
...await bridgeArgs()
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
export function validateFinalizerAuthorityStatus(status){
|
|
70
|
+
insist(status?.process_armed===true,'R45 finalizer requires Process standing grant');
|
|
71
|
+
insist(status?.coding_armed===true,'R45 finalizer requires Coding standing grant');
|
|
72
|
+
insist(status?.autonomous_armed===true,'R45 finalizer requires Autonomous standing grant');
|
|
73
|
+
insist(status?.desktop_armed===false,'R45 finalizer requires Desktop DISARMED');
|
|
74
|
+
insist(status?.host_armed===false,'R45 finalizer requires Host DISARMED');
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function currentAuthorityStatus(){
|
|
78
|
+
const result=spawnSync(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'status'],
|
|
79
|
+
{encoding:'utf8',windowsHide:true,timeout:20000,env:windowsPowerShellEnvironment()});
|
|
80
|
+
if(result.error) throw result.error;
|
|
81
|
+
if(result.status!==0) throw new Error(`R45 finalizer controller status failed: ${result.stderr||result.stdout}`);
|
|
82
|
+
return parseControllerStatus(result.stdout??'');
|
|
83
|
+
}
|
|
84
|
+
export function validateWindowsGateEvidence(value,manifestSha256,parent){
|
|
85
|
+
insist(value?.schema==='deadbyte.r45-windows-gate.v1'&&value.status==='passed'&&value.exit_code===0,'R45 Windows gate evidence invalid');
|
|
86
|
+
insist(value.version===EXPECTED_VERSION&&value.manifest_sha256===manifestSha256,'R45 Windows gate candidate mismatch');
|
|
87
|
+
insist(value.marker==='DEADBYTE V0.15.1 / R45 MACHINE FREEDOM WINDOWS BATCH GATE: PASS','R45 Windows gate marker mismatch');
|
|
88
|
+
insist(value.parent?.release_id===parent.release_id&&value.parent?.manifest_sha256===parent.manifest_sha256,'R45 Windows gate parent mismatch');
|
|
89
|
+
insist(value.parent?.generation_manifest_sha256===parent.generation_manifest_sha256,'R45 Windows gate parent generation mismatch');
|
|
90
|
+
insist(value.parent?.final_closure_sha256===PARENT_FINAL_CLOSURE&&value.parent?.final_closure_sha256===parent.closure_sha256,'R45 Windows gate parent final closure mismatch');
|
|
91
|
+
for(const key of ['skill_integrity','agent_backend_persistence','codex_protocol_contract','remote_device_identity','durable_job_supervisor','real_external_supervisor_smoke','full_regression_required','host_inline_native','native_hardware_observation','doctor_release_truth']) {
|
|
92
|
+
insist(value.features?.[key]===true,`R45 Windows gate feature contract missing ${key}`);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function disarmFailClosed(){
|
|
97
|
+
const results=[];
|
|
98
|
+
for(const command of ['disarmdesktop','disarmautonomous','disarmcoding','disarmprocess','disarmhost']){
|
|
99
|
+
const result=spawnSync(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],
|
|
100
|
+
{encoding:'utf8',windowsHide:true,timeout:20000,env:windowsPowerShellEnvironment()});
|
|
101
|
+
results.push({command,status:result.status,signal:result.signal??null});
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
async function mirrorRoot(){
|
|
106
|
+
const pointer=await activePointer();
|
|
107
|
+
const policy=JSON.parse(await readFile(path.join(pointer.generation_root,'coding-policy.json'),'utf8'));
|
|
108
|
+
const raw=policy?.roots?.parity_scratch?.path??policy?.roots?.parity_scratch?.real_path;
|
|
109
|
+
if(typeof raw!=='string'||!path.isAbsolute(raw)) throw new Error('active generation parity_scratch root unavailable');
|
|
110
|
+
const stat=await lstat(raw);
|
|
111
|
+
if(!stat.isDirectory()||stat.isSymbolicLink()) throw new Error('parity_scratch root must be a real directory');
|
|
112
|
+
return raw;
|
|
113
|
+
}
|
|
114
|
+
async function writeMirror(manifestSha256,value){
|
|
115
|
+
const root=await mirrorRoot();
|
|
116
|
+
const file=path.join(root,`r45-final-${manifestSha256.slice(0,16)}.json`);
|
|
117
|
+
return atomicJson(file,value);
|
|
118
|
+
}
|
|
119
|
+
async function verifyInputs(manifestSha256){
|
|
120
|
+
insist(HEX64.test(manifestSha256??''),'R45 finalizer manifest invalid');
|
|
121
|
+
const manifestBytes=await readFile(path.join(packageRoot,'MANIFEST.SHA256'));
|
|
122
|
+
insist(sha256(manifestBytes)===manifestSha256,'R45 finalizer manifest drift');
|
|
123
|
+
const pkg=JSON.parse(await readFile(path.join(packageRoot,'package.json'),'utf8'));
|
|
124
|
+
insist(pkg?.name==='deadbyte-mcp'&&pkg?.version===EXPECTED_VERSION,'R45 finalizer package identity mismatch');
|
|
125
|
+
const parent=await verifyR45Parent(path.join(packageRoot,'R45-PARENT.json'));
|
|
126
|
+
const authorityStatus=currentAuthorityStatus();
|
|
127
|
+
validateFinalizerAuthorityStatus(authorityStatus);
|
|
128
|
+
const before=await activePointer();
|
|
129
|
+
insist(before.release_id===PARENT_RELEASE_ID&&before.manifest_sha256===PARENT_MANIFEST,'R45 finalizer must start from exact sealed R44 parent');
|
|
130
|
+
const windowsGatePath=path.join(stateRoot,'evidence','release',`r45-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
131
|
+
const windowsGateBytes=await readFile(windowsGatePath);
|
|
132
|
+
const windowsGate=JSON.parse(windowsGateBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
133
|
+
validateWindowsGateEvidence(windowsGate,manifestSha256,parent);
|
|
134
|
+
return {parent,before,authorityStatus,windowsGatePath,windowsGateFileSha256:sha256(windowsGateBytes)};
|
|
135
|
+
}
|
|
136
|
+
async function runWorker(manifestSha256,operationId){
|
|
137
|
+
const statePath=path.join(evidenceRoot,`${operationId}.json`);
|
|
138
|
+
let activation=null;
|
|
139
|
+
let parity=null;
|
|
140
|
+
try{
|
|
141
|
+
await atomicJson(statePath,{schema:'deadbyte.r45-finalization-state.v1',operation_id:operationId,status:'running',
|
|
142
|
+
manifest_sha256:manifestSha256,started_at_utc:new Date().toISOString()});
|
|
143
|
+
const inputs=await verifyInputs(manifestSha256);
|
|
144
|
+
activation=await runSlotUpdateCli(await activationArgs(manifestSha256),{stateRoot});
|
|
145
|
+
insist(activation?.pointer?.version===EXPECTED_VERSION&&activation.pointer.manifest_sha256===manifestSha256,'R45 activation result mismatch');
|
|
146
|
+
insist(activation?.final_authority?.process_armed===true&&activation?.final_authority?.coding_armed===true&&activation?.final_authority?.autonomous_armed===true,
|
|
147
|
+
'R45 activation must restore prior Process+Coding+Autonomous standing grants for detached parity');
|
|
148
|
+
insist(activation?.final_authority?.desktop_armed===false&&activation?.final_authority?.host_armed===false,
|
|
149
|
+
'R45 activation must keep Desktop+Host disarmed');
|
|
150
|
+
|
|
151
|
+
parity=await runReleaseParity(activation.receipt_path);
|
|
152
|
+
insist(parity?.evidence?.status==='passed'&&parity.evidence?.candidate?.manifest_sha256===manifestSha256,'R45 parity result mismatch');
|
|
153
|
+
|
|
154
|
+
const closure=await writeFinalClosure({parityPath:parity.evidence_path,activationPath:activation.receipt_path,
|
|
155
|
+
windowsGatePath:inputs.windowsGatePath});
|
|
156
|
+
const envelope=JSON.parse(await readFile(closure.path,'utf8'));
|
|
157
|
+
const verified=await verifyFinalClosureObject(envelope,{stateRoot});
|
|
158
|
+
insist(verified?.ok===true&&verified.status==='passed'&&verified.manifest_sha256===manifestSha256,'R45 independent final closure verification failed');
|
|
159
|
+
|
|
160
|
+
const pointer=await activePointer();
|
|
161
|
+
const result={schema:'deadbyte.r45-finalization-result.v1',status:'passed',verified:true,
|
|
162
|
+
completed_at_utc:new Date().toISOString(),release_id:pointer.release_id,version:pointer.version,
|
|
163
|
+
manifest_sha256:pointer.manifest_sha256,generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
164
|
+
activation_receipt_sha256:activation.receipt_sha256,
|
|
165
|
+
parity_sha256:parity.evidence_sha256,windows_gate_sha256:inputs.windowsGateFileSha256,
|
|
166
|
+
closure_file_sha256:closure.sha256,closure_sha256:closure.closure_sha256,
|
|
167
|
+
full_tool_count:verified.full_tool_count,compact_tool_count:verified.compact_tool_count,
|
|
168
|
+
parent_release_id:inputs.parent.release_id,parent_manifest_sha256:inputs.parent.manifest_sha256};
|
|
169
|
+
const mirror=await writeMirror(manifestSha256,result);
|
|
170
|
+
await atomicJson(statePath,{...result,operation_id:operationId,mirror_path:mirror.path,mirror_sha256:mirror.sha256,
|
|
171
|
+
activation_receipt_path:activation.receipt_path,parity_path:parity.evidence_path,closure_path:closure.path});
|
|
172
|
+
return result;
|
|
173
|
+
}catch(error){
|
|
174
|
+
let rollback=null;
|
|
175
|
+
try{
|
|
176
|
+
if(activation?.receipt_path){
|
|
177
|
+
const pointer=await activePointer().catch(()=>null);
|
|
178
|
+
if(pointer?.version===EXPECTED_VERSION&&pointer?.manifest_sha256===manifestSha256){
|
|
179
|
+
rollback=await runSlotUpdateCli(['--rollback-receipt',activation.receipt_path],{stateRoot});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}catch(rollbackError){rollback={error:String(rollbackError?.message??rollbackError)};}
|
|
183
|
+
const disarm=disarmFailClosed();
|
|
184
|
+
const failed={schema:'deadbyte.r45-finalization-result.v1',status:'failed',verified:false,
|
|
185
|
+
completed_at_utc:new Date().toISOString(),manifest_sha256:manifestSha256,
|
|
186
|
+
error:String(error?.message??error),rollback:rollback?{ok:rollback?.ok===true,error:rollback?.error??null}:null,disarm};
|
|
187
|
+
await atomicJson(statePath,{...failed,operation_id:operationId}).catch(()=>{});
|
|
188
|
+
await writeMirror(manifestSha256,failed).catch(()=>{});
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export async function scheduleFinalization(manifestSha256){
|
|
193
|
+
insist(HEX64.test(manifestSha256??''),'R45 finalizer manifest invalid');
|
|
194
|
+
const operationId=`r45-${manifestSha256.slice(0,16)}-${randomBytes(8).toString('hex')}`;
|
|
195
|
+
const statePath=path.join(evidenceRoot,`${operationId}.json`);
|
|
196
|
+
await atomicJson(statePath,{schema:'deadbyte.r45-finalization-state.v1',operation_id:operationId,status:'scheduled',
|
|
197
|
+
manifest_sha256:manifestSha256,scheduled_at_utc:new Date().toISOString()});
|
|
198
|
+
const child=spawn(process.execPath,[fileURLToPath(import.meta.url),manifestSha256,'--worker',operationId],{
|
|
199
|
+
cwd:packageRoot,detached:true,stdio:'ignore',windowsHide:true,env:{...process.env,DEADBYTE_STATE_ROOT:stateRoot}
|
|
200
|
+
});
|
|
201
|
+
child.unref();
|
|
202
|
+
return {status:'scheduled',operation_id:operationId,state_path:statePath,manifest_sha256:manifestSha256,
|
|
203
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
207
|
+
if(invoked){
|
|
208
|
+
const [manifestSha256,flag,operationId]=process.argv.slice(2);
|
|
209
|
+
try{
|
|
210
|
+
if(flag==='--worker'){
|
|
211
|
+
insist(/^r45-[0-9a-f]{16}-[0-9a-f]{16}$/.test(operationId??''),'R45 finalizer worker operation id invalid');
|
|
212
|
+
await sleep(4000);
|
|
213
|
+
await runWorker(manifestSha256,operationId);
|
|
214
|
+
}else{
|
|
215
|
+
insist(flag===undefined&&operationId===undefined,'usage: node scripts/finalize-r45.mjs <manifest_sha256>');
|
|
216
|
+
console.log(JSON.stringify(await scheduleFinalization(manifestSha256)));
|
|
217
|
+
}
|
|
218
|
+
}catch(error){console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;}
|
|
219
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root=path.resolve(here,'..');
|
|
8
|
+
const testDir=path.join(root,'test');
|
|
9
|
+
const files=(await readdir(testDir,{withFileTypes:true}))
|
|
10
|
+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs')&&entry.name!=='full-max-catalog-r44.test.mjs')
|
|
11
|
+
.map(entry=>path.join('test',entry.name))
|
|
12
|
+
.sort((a,b)=>a.localeCompare(b,'en'));
|
|
13
|
+
if(files.length<1) throw new Error('R45 full regression found no test files');
|
|
14
|
+
console.log(`R45-FULL-REGRESSION files=${files.length}`);
|
|
15
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...files],{
|
|
16
|
+
cwd:root,stdio:'inherit',windowsHide:true,shell:false
|
|
17
|
+
});
|
|
18
|
+
if(result.error) throw result.error;
|
|
19
|
+
if(result.signal) throw new Error(`R45 full regression terminated by ${result.signal}`);
|
|
20
|
+
process.exitCode=Number.isInteger(result.status)?result.status:1;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root=path.resolve(here,'..');
|
|
8
|
+
const testDir=path.join(root,'test');
|
|
9
|
+
const files=(await readdir(testDir,{withFileTypes:true}))
|
|
10
|
+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs')&&!['full-max-catalog-r44.test.mjs','full-max-catalog-r45.test.mjs'].includes(entry.name))
|
|
11
|
+
.map(entry=>path.join('test',entry.name))
|
|
12
|
+
.sort((a,b)=>a.localeCompare(b,'en'));
|
|
13
|
+
if(files.length<1) throw new Error('R46 full regression found no test files');
|
|
14
|
+
console.log(`R46-FULL-REGRESSION files=${files.length}`);
|
|
15
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...files],{
|
|
16
|
+
cwd:root,stdio:'inherit',windowsHide:true,shell:false
|
|
17
|
+
});
|
|
18
|
+
if(result.error) throw result.error;
|
|
19
|
+
if(result.signal) throw new Error(`R46 full regression terminated by ${result.signal}`);
|
|
20
|
+
process.exitCode=Number.isInteger(result.status)?result.status:1;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
6
|
+
const files=(await readdir(path.join(root,'test'),{withFileTypes:true})).filter(e=>e.isFile()&&e.name.endsWith('.test.mjs')).map(e=>path.join('test',e.name)).sort();
|
|
7
|
+
const r=spawnSync(process.execPath,['--test','--test-reporter=spec',...files],{cwd:root,encoding:'utf8',windowsHide:true,shell:false,maxBuffer:64*1024*1024});
|
|
8
|
+
if(r.error) throw r.error;
|
|
9
|
+
const lines=(String(r.stdout||'')+'\n'+String(r.stderr||'')).split(/\r?\n/);
|
|
10
|
+
for(let i=0;i<lines.length;i++){
|
|
11
|
+
if(lines[i].startsWith('✖ ')||/^ℹ fail /.test(lines[i])||/^test at /.test(lines[i])) console.log(lines[i]);
|
|
12
|
+
}
|
|
13
|
+
console.log('EXIT='+String(r.status));
|
|
14
|
+
process.exitCode=Number.isInteger(r.status)?r.status:1;
|