deadbyte-mcp 0.14.0 → 0.15.1
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/AGENTS.md +11 -18
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +156 -70
- package/PROVENANCE.json +10 -0
- package/R43-PARENT.json +20 -0
- package/R44-DONORS.json +99 -0
- package/R44-PARENT.json +18 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +19 -14
- package/RELEASE.json +37 -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 +5 -6
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/R44-ARCHITECTURE.md +37 -0
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +18 -8
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r43.mjs +126 -0
- package/scripts/deferred-slot-operation-r44.mjs +126 -0
- package/scripts/deferred-slot-operation-r45.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-r43.mjs +115 -0
- package/scripts/final-closure-r44.mjs +121 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-verify-r43.mjs +213 -0
- package/scripts/final-closure-verify-r44.mjs +220 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r43.mjs +217 -0
- package/scripts/finalize-r44.mjs +217 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r43.mjs +20 -0
- package/scripts/full-regression-r44.mjs +20 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r43.ps1 +36 -0
- package/scripts/gate-windows-r44.ps1 +36 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r43.mjs +218 -0
- package/scripts/release-parity-r44.mjs +242 -0
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-tests-r43.ps1 +8 -0
- package/scripts/release-parity-tests-r44.ps1 +8 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r43-parent.mjs +126 -0
- package/scripts/verify-r44-parent.mjs +142 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r43.mjs +55 -0
- package/scripts/windows-gate-evidence-r44.mjs +55 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/agent-backend-r44.mjs +254 -0
- package/src/autonomous-output-contracts.mjs +1 -1
- package/src/autonomous-policy.mjs +41 -4
- package/src/autonomous-runtime.mjs +37 -19
- package/src/autonomous-supervisor-host.mjs +12 -2
- package/src/capability-ledger.mjs +3 -1
- package/src/codex-app-server-r44.mjs +154 -0
- package/src/coding-plane.mjs +66 -19
- package/src/compact-gateway-mcp-tools.mjs +4 -2
- package/src/deadbyte-cli.mjs +17 -9
- package/src/doctor-r45.mjs +65 -0
- package/src/durable-job-r44.mjs +145 -0
- package/src/external-job-provider-r44.mjs +126 -0
- package/src/host-plane.mjs +156 -87
- package/src/job-supervisor-host-r44.mjs +112 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +21 -10
- package/src/mcp-host-smoke-client.mjs +1 -1
- package/src/mcp-server.mjs +84 -26
- package/src/mcp-smoke-client.mjs +1 -1
- package/src/native-diff-v2.mjs +12 -7
- package/src/native-diff.mjs +32 -9
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +33 -2
- package/src/observation-smoke-client.mjs +7 -3
- package/src/operator-surface.mjs +2 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-policy.mjs +19 -0
- package/src/process-runtime.mjs +5 -2
- package/src/process-smoke-client.mjs +2 -2
- package/src/provider-circuit.mjs +8 -2
- package/src/r44-compact-disarmed-probe.mjs +74 -0
- package/src/r44-full-surface-probe.mjs +34 -0
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +1 -0
- package/src/remote-device-r44.mjs +205 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
- package/src/remote-mcp-r44-parity-client.mjs +203 -0
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +7 -4
- package/src/runtime-update.mjs +100 -39
- package/src/skill-mcp-tools-r44.mjs +37 -0
- package/src/skill-plane-r44.mjs +203 -0
- package/src/tunnel-stdio-compat-smoke.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-backend-r44.test.mjs +115 -0
- package/test/agent-runtime.test.mjs +4 -3
- package/test/autonomous-loop.test.mjs +121 -11
- package/test/autonomous-runtime.test.mjs +3 -2
- package/test/capability-ledger-r36.test.mjs +3 -3
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/codex-app-server-r44.test.mjs +115 -0
- package/test/codex-autonomous-r44.test.mjs +87 -0
- package/test/coding-plane.test.mjs +3 -2
- package/test/coding-runtime.test.mjs +36 -1
- package/test/compact-catalog-r36.test.mjs +3 -3
- package/test/compact-max-catalog-r40.test.mjs +4 -2
- package/test/compact-max-catalog-r44.test.mjs +97 -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/durable-job-r44.test.mjs +75 -0
- package/test/external-job-provider-r44.test.mjs +46 -0
- package/test/full-max-catalog-r40.test.mjs +5 -3
- package/test/full-max-catalog-r44.test.mjs +91 -0
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/host-plane.test.mjs +15 -1
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/native-diff.test.mjs +28 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +3 -2
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-policy.test.mjs +22 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r33-closeout-regression.test.mjs +20 -36
- package/test/r33-finalization.test.mjs +7 -7
- package/test/r43-finalization.test.mjs +31 -0
- package/test/r43-finalizer.test.mjs +34 -0
- package/test/r43-parent.test.mjs +29 -0
- package/test/r44-finalization.test.mjs +58 -0
- package/test/r44-finalizer.test.mjs +48 -0
- package/test/r44-parent.test.mjs +25 -0
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +1 -0
- package/test/release-version.test.mjs +65 -20
- package/test/remote-device-r44.test.mjs +104 -0
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
- package/test/runtime-self-update.test.mjs +42 -0
- package/test/skill-mcp-r44.test.mjs +38 -0
- package/test/skill-plane-r44.test.mjs +93 -0
- package/test/standing-grants.test.mjs +5 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash, createPrivateKey, createPublicKey, sign as cryptoSign } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { probeR45FullSurface } from '../src/r45-full-surface-probe.mjs';
|
|
7
|
+
import { probeR45CompactDisarmed } from '../src/r45-compact-disarmed-probe.mjs';
|
|
8
|
+
import {
|
|
9
|
+
validateParityEvidence,
|
|
10
|
+
R45_PREDECESSOR_RELEASE_ID,
|
|
11
|
+
R45_PREDECESSOR_MANIFEST
|
|
12
|
+
} from './release-parity-r45.mjs';
|
|
13
|
+
|
|
14
|
+
const DOMAIN=Buffer.from('DEADBYTE-R45-FINAL-CLOSURE-V1\0','utf8');
|
|
15
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
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
|
+
function insist(ok,msg){if(!ok) throw new Error(msg);}
|
|
21
|
+
function samePath(a,b){return path.resolve(String(a)).toLowerCase()===path.resolve(String(b)).toLowerCase();}
|
|
22
|
+
async function fileEvidence(file){const abs=path.resolve(file),bytes=await readFile(abs);return {path:abs,sha256:sha256(bytes)};}
|
|
23
|
+
async function publicUrl(){
|
|
24
|
+
const value=(await readFile(path.join(stateRoot,'bridge','mcp-url.txt'),'utf8')).trim();
|
|
25
|
+
const url=new URL(value);insist(url.protocol==='https:','final closure requires HTTPS public endpoint');return value;
|
|
26
|
+
}
|
|
27
|
+
async function grantAbsence(){
|
|
28
|
+
const out={};
|
|
29
|
+
for(const name of ['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']){
|
|
30
|
+
let absent=false;try{await lstat(path.join(stateRoot,'runtime',name));}catch(error){if(error?.code==='ENOENT') absent=true;else throw error;}
|
|
31
|
+
insist(absent,`authority grant still present: ${name}`);out[name]=true;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
async function atomicCreate(file,bytes){
|
|
36
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
37
|
+
const temp=`${file}.tmp-${process.pid}-${Math.random().toString(16).slice(2)}`;
|
|
38
|
+
const handle=await open(temp,'wx');
|
|
39
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
40
|
+
try{await rename(temp,file);}catch(error){await rm(temp,{force:true}).catch(()=>{});throw error;}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function writeFinalClosure({parityPath,activationPath,windowsGatePath}){
|
|
44
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
45
|
+
insist(pointer.version==='0.15.1','R45 final closure requires V0.15.1 active release');
|
|
46
|
+
insist(pointer.release_id===`v${pointer.version}-${pointer.manifest_sha256.slice(0,16)}`,'final closure release id/content address mismatch');
|
|
47
|
+
|
|
48
|
+
const parity=JSON.parse(await readFile(path.resolve(parityPath),'utf8'));
|
|
49
|
+
insist(parity.schema==='deadbyte.r45-parity.v1','R45 parity schema mismatch at final closure');
|
|
50
|
+
validateParityEvidence(parity);
|
|
51
|
+
insist(parity.candidate?.release_id===pointer.release_id&&parity.candidate?.manifest_sha256===pointer.manifest_sha256,'parity candidate/current release mismatch');
|
|
52
|
+
insist(parity.successor?.release_id===pointer.release_id&&parity.successor?.manifest_sha256===pointer.manifest_sha256,'parity successor/current release mismatch');
|
|
53
|
+
insist(parity.predecessor?.release_id===R45_PREDECESSOR_RELEASE_ID&&parity.predecessor?.manifest_sha256===R45_PREDECESSOR_MANIFEST,'parity predecessor identity mismatch');
|
|
54
|
+
insist(parity.rollback?.receipt_path&&parity.restore?.receipt_path,'parity rollback/restore receipt paths missing');
|
|
55
|
+
insist(parity.immutable_slot?.protected===true,'parity immutable-slot proof missing');
|
|
56
|
+
insist(samePath(parity.immutable_slot.release_root,pointer.release_root),'parity immutable release root mismatch');
|
|
57
|
+
insist(!samePath(parity.immutable_slot.development_root,pointer.release_root),'development_root must differ from immutable release_root');
|
|
58
|
+
|
|
59
|
+
const windowsGate=JSON.parse(await readFile(path.resolve(windowsGatePath),'utf8'));
|
|
60
|
+
insist(windowsGate.schema==='deadbyte.r45-windows-gate.v1'&&windowsGate.status==='passed'&&windowsGate.exit_code===0,'R45 Windows gate evidence invalid');
|
|
61
|
+
insist(windowsGate.version==='0.15.1'&&windowsGate.manifest_sha256===pointer.manifest_sha256,'R45 Windows gate/current release mismatch');
|
|
62
|
+
insist(windowsGate.marker==='DEADBYTE V0.15.1 / R45 MACHINE FREEDOM WINDOWS BATCH GATE: PASS','R45 Windows gate marker mismatch');
|
|
63
|
+
|
|
64
|
+
const localFull=await probeR45FullSurface({packageRoot:pointer.release_root,generationRoot:pointer.generation_root,stateRoot});
|
|
65
|
+
insist(localFull.status==='passed'&&localFull.tool_count===82&&localFull.strict_output_schemas===true,'final local full 82-tool proof failed');
|
|
66
|
+
const compact=await probeR45CompactDisarmed(await publicUrl());
|
|
67
|
+
insist(compact.status==='passed'&&compact.tool_count===43&&compact.capability_closure===true&&compact.skill_catalog_verified===true,'final public compact capability/skill closure failed');
|
|
68
|
+
insist(compact.coding_armed===false&&compact.autonomous_armed===false&&compact.desktop_armed===false,'final public compact authority is not DISARMED');
|
|
69
|
+
insist(compact.desktop_observation_receipt_verified===true&&compact.desktop_grounding_receipt_verified===true,'final public Desktop read-only proof missing');
|
|
70
|
+
insist(compact.release_id===pointer.release_id&&compact.release_manifest_sha256===pointer.manifest_sha256,'final public compact release binding mismatch');
|
|
71
|
+
insist(compact.machine_status?.release_id===pointer.release_id&&compact.machine_status?.release_manifest_sha256===pointer.manifest_sha256,'final machine status/current release mismatch');
|
|
72
|
+
insist(compact.machine_ping?.nonce===compact.nonce,'final machine ping nonce mismatch');
|
|
73
|
+
|
|
74
|
+
const windowsGateBytes=await readFile(path.resolve(windowsGatePath));
|
|
75
|
+
insist(parity.feature_gate_sha256===sha256(windowsGateBytes),'R45 parity feature gate hash/current Windows gate mismatch');
|
|
76
|
+
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']) {
|
|
77
|
+
insist(windowsGate.features?.[key]===true,`R45 Windows gate feature missing ${key}`);
|
|
78
|
+
}
|
|
79
|
+
const evidence={
|
|
80
|
+
parity:await fileEvidence(parityPath),activation:await fileEvidence(activationPath),
|
|
81
|
+
rollback:await fileEvidence(parity.rollback.receipt_path),restore:await fileEvidence(parity.restore.receipt_path),
|
|
82
|
+
windows_gate:await fileEvidence(windowsGatePath)
|
|
83
|
+
};
|
|
84
|
+
const body={
|
|
85
|
+
schema:'deadbyte.r45-final-closure.v1',version:'0.15.1',release_label:'R45 MACHINE FREEDOM FINAL BASELINE',
|
|
86
|
+
completed_at_utc:new Date().toISOString(),active_pointer:pointer,
|
|
87
|
+
predecessor:{release_id:R45_PREDECESSOR_RELEASE_ID,version:'0.15.0',manifest_sha256:R45_PREDECESSOR_MANIFEST},
|
|
88
|
+
immutable_slot:{protected:true,release_root:path.resolve(pointer.release_root),development_root:path.resolve(parity.immutable_slot.development_root)},
|
|
89
|
+
surfaces:{
|
|
90
|
+
local_full:{status:localFull.status,profile:localFull.profile,tool_count:localFull.tool_count,catalog_sha256:localFull.catalog_sha256,strict_output_schemas:localFull.strict_output_schemas,protocol_revision:localFull.protocol_revision},
|
|
91
|
+
public_compact:{status:compact.status,profile:compact.profile,tool_count:compact.tool_count,catalog_sha256:compact.catalog_sha256,strict_output_schemas:compact.strict_output_schemas,ledger_sha256:compact.ledger_sha256,capability_count:compact.capability_count,production_non_host_capabilities:compact.production_non_host_capabilities,capability_closure:compact.capability_closure,coding_armed:compact.coding_armed,autonomous_armed:compact.autonomous_armed,desktop_armed:compact.desktop_armed,desktop_observation_receipt_verified:compact.desktop_observation_receipt_verified,desktop_grounding_receipt_verified:compact.desktop_grounding_receipt_verified,protocol_revision:compact.protocol_revision,skill_catalog_verified:compact.skill_catalog_verified,skill_count:compact.skill_count},
|
|
92
|
+
deterministic_memory:parity.deterministic_memory,
|
|
93
|
+
feature_contract:parity.feature_gate
|
|
94
|
+
},
|
|
95
|
+
evidence,
|
|
96
|
+
machine:{status:compact.machine_status,ping:compact.machine_ping,nonce:compact.nonce},
|
|
97
|
+
authority:{all_disarmed:true,grants_absent:await grantAbsence()}
|
|
98
|
+
};
|
|
99
|
+
const privateKey=createPrivateKey(await readFile(path.join(stateRoot,'trust','ed25519-private.pem')));
|
|
100
|
+
const publicKey=createPublicKey(privateKey);
|
|
101
|
+
insist(privateKey.asymmetricKeyType==='ed25519'&&publicKey.asymmetricKeyType==='ed25519','closure signing key must be Ed25519');
|
|
102
|
+
const keyId=sha256(Buffer.from(publicKey.export({type:'spki',format:'der'})));
|
|
103
|
+
const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
|
|
104
|
+
const signature=cryptoSign(null,Buffer.concat([DOMAIN,bodyBytes]),privateKey);
|
|
105
|
+
const envelope={body,body_sha256:sha256(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R45-FINAL-CLOSURE-V1',key_id:keyId,signature_base64:signature.toString('base64')}};
|
|
106
|
+
envelope.closure_sha256=sha256(Buffer.from(canonicalJson(envelope),'utf8'));
|
|
107
|
+
const dir=path.join(stateRoot,'evidence','release');
|
|
108
|
+
const file=path.join(dir,`r45-final-${pointer.manifest_sha256.slice(0,16)}.json`);
|
|
109
|
+
const bytes=Buffer.from(`${JSON.stringify(envelope,null,2)}\n`,'utf8');
|
|
110
|
+
await atomicCreate(file,bytes);
|
|
111
|
+
return {status:'written',path:file,sha256:sha256(bytes),closure_sha256:envelope.closure_sha256,release_id:pointer.release_id};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
115
|
+
if(invoked){
|
|
116
|
+
const [parityPath,activationPath,windowsGatePath]=process.argv.slice(2);
|
|
117
|
+
if(!parityPath||!activationPath||!windowsGatePath) throw new Error('usage: node scripts/final-closure-r45.mjs <parity.json> <activation-receipt.json> <windows-gate.json>');
|
|
118
|
+
writeFinalClosure({parityPath,activationPath,windowsGatePath})
|
|
119
|
+
.then(result=>console.log(JSON.stringify(result,null,2)))
|
|
120
|
+
.catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
121
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
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-R43-FINAL-CLOSURE-V1\0','utf8');
|
|
10
|
+
const PING_DOMAIN=Buffer.from('DEADBYTE-MACHINE-PING-V1\0','utf8');
|
|
11
|
+
const R43_PREDECESSOR_RELEASE_ID='v0.14.0-08cd8f3b98c90e0e';
|
|
12
|
+
const R43_PREDECESSOR_MANIFEST='08cd8f3b98c90e0e250cf506ff396515065ff5457909f850b75b0b6e8ecc2dd1';
|
|
13
|
+
const R40_FULL_TOOL_COUNT=80;
|
|
14
|
+
const R40_COMPACT_TOOL_COUNT=42;
|
|
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'];
|
|
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.14.1'&&value?.manifest_sha256===manifest&&value?.release_id===`v0.14.1-${manifest.slice(0,16)}`;
|
|
25
|
+
}
|
|
26
|
+
function isR42PredecessorIdentity(value){
|
|
27
|
+
return value?.version==='0.14.0'&&value?.manifest_sha256===R43_PREDECESSOR_MANIFEST&&value?.release_id===R43_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.r43-final-closure.v1','closure schema mismatch');
|
|
96
|
+
insist(envelope.body.version==='0.14.1','closure version mismatch');
|
|
97
|
+
insist(envelope.body.release_label==='R43 AUTHORITY HARDENING PATCH','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-R43-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.14.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(isR42PredecessorIdentity(predecessor),'closure predecessor is not exact sealed R40 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===R40_FULL_TOOL_COUNT&&surfaces.local_full.strict_output_schemas===true,'closure local full 80-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===R40_COMPACT_TOOL_COUNT&&surfaces.public_compact.capability_closure===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.r43-parity.v1'&&parity.status==='passed','R40 parity evidence invalid');
|
|
139
|
+
insist(parity.rdc_operation_count===0&&parity.execution_provider==='deadbyte_mcp','R40 parity execution provider mismatch');
|
|
140
|
+
insist(isCandidateIdentity(parity.candidate,pointer.manifest_sha256),'R40 parity candidate binding mismatch');
|
|
141
|
+
insist(isCandidateIdentity(parity.successor,pointer.manifest_sha256),'R40 parity successor binding mismatch');
|
|
142
|
+
insist(isR42PredecessorIdentity(parity.predecessor),'R40 parity predecessor binding mismatch');
|
|
143
|
+
insist(parity.local_full?.status==='passed'&&parity.local_full.tool_count===R40_FULL_TOOL_COUNT&&parity.local_full.strict_output_schemas===true,'R40 parity local full proof invalid');
|
|
144
|
+
insist(parity.local_full.catalog_sha256===surfaces.local_full.catalog_sha256,'R36 local full catalog drift between parity and closure');
|
|
145
|
+
insist(parity.public_compact?.status==='passed'&&parity.public_compact.tool_count===R40_COMPACT_TOOL_COUNT&&parity.public_compact.capability_closure===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,'R40 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,'R40 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===R40_COMPACT_TOOL_COUNT&&parity.successor_disarmed.capability_closure===true&&parity.successor_disarmed.coding_armed===false&&parity.successor_disarmed.autonomous_armed===false&&parity.successor_disarmed.desktop_armed===false,'R40 parity successor disarmed proof invalid');
|
|
150
|
+
insist(parity.successor_disarmed.catalog_sha256===surfaces.public_compact.catalog_sha256,'R36 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,'R40 parity immutable-slot proof missing');
|
|
153
|
+
insist(samePath(parity.immutable_slot.release_root,pointer.release_root),'R40 parity immutable release_root mismatch');
|
|
154
|
+
insist(samePath(parity.immutable_slot.development_root,immutable.development_root),'R40 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,`R40 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,'R40 activation receipt invalid');
|
|
162
|
+
insist(activation.body.release_version==='0.14.1'&&activation.body.candidate_manifest_sha256===pointer.manifest_sha256,'R40 activation candidate identity invalid');
|
|
163
|
+
insist(isCandidateIdentity(activation.body.active_pointer,pointer.manifest_sha256),'R40 activation active pointer invalid');
|
|
164
|
+
insist(isR42PredecessorIdentity(activation.body.prior_pointer),'R41 activation predecessor is not exact R40 predecessor');
|
|
165
|
+
insist(samePath(activation.body.development_root,immutable.development_root),'R40 activation development_root mismatch');
|
|
166
|
+
insist(!samePath(activation.body.development_root,activation.body.active_pointer.release_root),'R40 activation development_root aliases release slot');
|
|
167
|
+
insist(activation.body.authority?.final?.desktop_armed===false,'R40 activation restored Desktop authority unexpectedly');
|
|
168
|
+
|
|
169
|
+
insist(rollback.body.schema==='deadbyte.r31-slot-rollback-receipt.v1'&&rollback.body.rollback_performed===true,'R40 rollback receipt invalid');
|
|
170
|
+
insist(isCandidateIdentity(rollback.body.from_pointer,pointer.manifest_sha256),'R40 rollback source is not exact candidate');
|
|
171
|
+
insist(isR42PredecessorIdentity(rollback.body.active_pointer),'R41 rollback target is not exact R40 predecessor');
|
|
172
|
+
insist(rollback.body.activation_receipt_sha256===activation.receipt_sha256,'R40 rollback not bound to activation receipt');
|
|
173
|
+
|
|
174
|
+
insist(restore.body.schema==='deadbyte.r27-slot-update-receipt.v1'&&restore.body.rollback_performed===false,'R40 restore receipt invalid');
|
|
175
|
+
insist(restore.body.release_version==='0.14.1'&&restore.body.candidate_manifest_sha256===pointer.manifest_sha256,'R40 restore candidate identity invalid');
|
|
176
|
+
insist(isR42PredecessorIdentity(restore.body.prior_pointer),'R41 restore predecessor is not exact R40 predecessor');
|
|
177
|
+
insist(isCandidateIdentity(restore.body.active_pointer,pointer.manifest_sha256)&&same(restore.body.active_pointer,pointer),'R40 restore/current pointer mismatch');
|
|
178
|
+
insist(samePath(restore.body.development_root,immutable.development_root),'R40 restore development_root mismatch');
|
|
179
|
+
insist(!samePath(restore.body.development_root,restore.body.active_pointer.release_root),'R40 restore development_root aliases release slot');
|
|
180
|
+
insist(restore.body.authority?.final?.desktop_armed===false,'R40 restore restored Desktop authority unexpectedly');
|
|
181
|
+
|
|
182
|
+
const windowsGate=JSON.parse(await readFile(evidence.windows_gate.path,'utf8'));
|
|
183
|
+
insist(windowsGate.schema==='deadbyte.r43-windows-gate.v1'&&windowsGate.status==='passed'&&windowsGate.exit_code===0,'R40 Windows gate evidence invalid');
|
|
184
|
+
insist(windowsGate.version==='0.14.1'&&windowsGate.manifest_sha256===pointer.manifest_sha256,'R40 Windows gate manifest/version binding mismatch');
|
|
185
|
+
insist(windowsGate.marker==='DEADBYTE V0.14.1 / R43 AUTHORITY HARDENING WINDOWS BATCH GATE: PASS','R40 Windows gate PASS marker mismatch');
|
|
186
|
+
|
|
187
|
+
const machine=envelope.body.machine;
|
|
188
|
+
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');
|
|
189
|
+
verifyPing(machine.ping,machine.status,pointer);
|
|
190
|
+
insist(machine.ping.nonce===machine.nonce,'machine ping nonce binding mismatch');
|
|
191
|
+
|
|
192
|
+
const grantNames=['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json'];
|
|
193
|
+
insist(envelope.body.authority?.all_disarmed===true,'closure authority not disarmed');
|
|
194
|
+
for(const name of grantNames){
|
|
195
|
+
let exists=true;try{await lstat(path.join(stateRoot,'runtime',name));}catch(error){if(error?.code==='ENOENT') exists=false;else throw error;}
|
|
196
|
+
insist(!exists,`authority grant still present: ${name}`);
|
|
197
|
+
insist(envelope.body.authority.grants_absent?.[name]===true,`closure grant absence not recorded: ${name}`);
|
|
198
|
+
}
|
|
199
|
+
return {ok:true,status:'passed',release_id:pointer.release_id,manifest_sha256:pointer.manifest_sha256,
|
|
200
|
+
generation_manifest_sha256:pointer.generation_manifest_sha256,release_files:releaseFiles,generation_files:generationFiles,
|
|
201
|
+
machine_id:machine.status.machine_id,parity_sha256:evidence.parity.sha256,closure_sha256:envelope.closure_sha256,
|
|
202
|
+
full_tool_count:R40_FULL_TOOL_COUNT,compact_tool_count:surfaces.public_compact.tool_count};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
206
|
+
if(invoked){
|
|
207
|
+
const file=process.argv[2];
|
|
208
|
+
if(!file) throw new Error('usage: node scripts/final-closure-verify-r43.mjs <closure.json>');
|
|
209
|
+
try{
|
|
210
|
+
const envelope=JSON.parse(await readFile(path.resolve(file),'utf8'));
|
|
211
|
+
console.log(JSON.stringify(await verifyFinalClosureObject(envelope),null,2));
|
|
212
|
+
}catch(error){console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;}
|
|
213
|
+
}
|
|
@@ -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-R44-FINAL-CLOSURE-V1\0','utf8');
|
|
10
|
+
const PING_DOMAIN=Buffer.from('DEADBYTE-MACHINE-PING-V1\0','utf8');
|
|
11
|
+
const R44_PREDECESSOR_RELEASE_ID='v0.14.1-0a5a833892593283';
|
|
12
|
+
const R44_PREDECESSOR_MANIFEST='0a5a8338925932830986421dda60ec5643f62f600404de4992fda2a5e79d09a9';
|
|
13
|
+
const R44_FULL_TOOL_COUNT=81;
|
|
14
|
+
const R44_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.0'&&value?.manifest_sha256===manifest&&value?.release_id===`v0.15.0-${manifest.slice(0,16)}`;
|
|
25
|
+
}
|
|
26
|
+
function isR43PredecessorIdentity(value){
|
|
27
|
+
return value?.version==='0.14.1'&&value?.manifest_sha256===R44_PREDECESSOR_MANIFEST&&value?.release_id===R44_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.r44-final-closure.v1','closure schema mismatch');
|
|
96
|
+
insist(envelope.body.version==='0.15.0','closure version mismatch');
|
|
97
|
+
insist(envelope.body.release_label==='R44 REMOTE AGENT SKILL 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-R44-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.0','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(isR43PredecessorIdentity(predecessor),'closure predecessor is not exact sealed R43 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===R44_FULL_TOOL_COUNT&&surfaces.local_full.strict_output_schemas===true,'closure local full 81-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===R44_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.r44-parity.v1'&&parity.status==='passed','R44 parity evidence invalid');
|
|
139
|
+
insist(parity.rdc_operation_count===0&&parity.execution_provider==='deadbyte_mcp','R44 parity execution provider mismatch');
|
|
140
|
+
insist(isCandidateIdentity(parity.candidate,pointer.manifest_sha256),'R44 parity candidate binding mismatch');
|
|
141
|
+
insist(isCandidateIdentity(parity.successor,pointer.manifest_sha256),'R44 parity successor binding mismatch');
|
|
142
|
+
insist(isR43PredecessorIdentity(parity.predecessor),'R44 parity predecessor binding mismatch');
|
|
143
|
+
insist(parity.local_full?.status==='passed'&&parity.local_full.tool_count===R44_FULL_TOOL_COUNT&&parity.local_full.strict_output_schemas===true,'R44 parity local full proof invalid');
|
|
144
|
+
insist(parity.local_full.catalog_sha256===surfaces.local_full.catalog_sha256,'R44 local full catalog drift between parity and closure');
|
|
145
|
+
insist(parity.public_compact?.status==='passed'&&parity.public_compact.tool_count===R44_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,'R44 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,'R44 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===R44_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,'R44 parity successor disarmed proof invalid');
|
|
150
|
+
insist(parity.successor_disarmed.catalog_sha256===surfaces.public_compact.catalog_sha256,'R44 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,'R44 parity immutable-slot proof missing');
|
|
153
|
+
insist(samePath(parity.immutable_slot.release_root,pointer.release_root),'R44 parity immutable release_root mismatch');
|
|
154
|
+
insist(samePath(parity.immutable_slot.development_root,immutable.development_root),'R44 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,`R44 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,'R44 activation receipt invalid');
|
|
162
|
+
insist(activation.body.release_version==='0.15.0'&&activation.body.candidate_manifest_sha256===pointer.manifest_sha256,'R44 activation candidate identity invalid');
|
|
163
|
+
insist(isCandidateIdentity(activation.body.active_pointer,pointer.manifest_sha256),'R44 activation active pointer invalid');
|
|
164
|
+
insist(isR43PredecessorIdentity(activation.body.prior_pointer),'R44 activation predecessor is not exact R43 predecessor');
|
|
165
|
+
insist(samePath(activation.body.development_root,immutable.development_root),'R44 activation development_root mismatch');
|
|
166
|
+
insist(!samePath(activation.body.development_root,activation.body.active_pointer.release_root),'R44 activation development_root aliases release slot');
|
|
167
|
+
insist(activation.body.authority?.final?.desktop_armed===false,'R44 activation restored Desktop authority unexpectedly');
|
|
168
|
+
|
|
169
|
+
insist(rollback.body.schema==='deadbyte.r31-slot-rollback-receipt.v1'&&rollback.body.rollback_performed===true,'R44 rollback receipt invalid');
|
|
170
|
+
insist(isCandidateIdentity(rollback.body.from_pointer,pointer.manifest_sha256),'R44 rollback source is not exact candidate');
|
|
171
|
+
insist(isR43PredecessorIdentity(rollback.body.active_pointer),'R44 rollback target is not exact R43 predecessor');
|
|
172
|
+
insist(rollback.body.activation_receipt_sha256===activation.receipt_sha256,'R44 rollback not bound to activation receipt');
|
|
173
|
+
|
|
174
|
+
insist(restore.body.schema==='deadbyte.r27-slot-update-receipt.v1'&&restore.body.rollback_performed===false,'R44 restore receipt invalid');
|
|
175
|
+
insist(restore.body.release_version==='0.15.0'&&restore.body.candidate_manifest_sha256===pointer.manifest_sha256,'R44 restore candidate identity invalid');
|
|
176
|
+
insist(isR43PredecessorIdentity(restore.body.prior_pointer),'R44 restore predecessor is not exact R43 predecessor');
|
|
177
|
+
insist(isCandidateIdentity(restore.body.active_pointer,pointer.manifest_sha256)&&same(restore.body.active_pointer,pointer),'R44 restore/current pointer mismatch');
|
|
178
|
+
insist(samePath(restore.body.development_root,immutable.development_root),'R44 restore development_root mismatch');
|
|
179
|
+
insist(!samePath(restore.body.development_root,restore.body.active_pointer.release_root),'R44 restore development_root aliases release slot');
|
|
180
|
+
insist(restore.body.authority?.final?.desktop_armed===false,'R44 restore restored Desktop authority unexpectedly');
|
|
181
|
+
|
|
182
|
+
const windowsGate=JSON.parse(await readFile(evidence.windows_gate.path,'utf8'));
|
|
183
|
+
insist(windowsGate.schema==='deadbyte.r44-windows-gate.v1'&&windowsGate.status==='passed'&&windowsGate.exit_code===0,'R44 Windows gate evidence invalid');
|
|
184
|
+
insist(windowsGate.version==='0.15.0'&&windowsGate.manifest_sha256===pointer.manifest_sha256,'R44 Windows gate manifest/version binding mismatch');
|
|
185
|
+
insist(windowsGate.marker==='DEADBYTE V0.15.0 / R44 REMOTE AGENT SKILL WINDOWS BATCH GATE: PASS','R44 Windows gate PASS marker mismatch');
|
|
186
|
+
|
|
187
|
+
const featureContract=surfaces.feature_contract;
|
|
188
|
+
insist(featureContract?.schema==='deadbyte.r44-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']) {
|
|
191
|
+
insist(windowsGate.features?.[key]===true&&featureContract.features?.[key]===true,`R44 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:R44_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-r44.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
|
+
}
|