deadbyte-mcp 0.15.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.
Files changed (77) hide show
  1. package/MANIFEST.SHA256 +76 -41
  2. package/PROVENANCE.json +10 -0
  3. package/R45-PARENT.json +18 -0
  4. package/README.txt +14 -14
  5. package/RELEASE.json +37 -0
  6. package/SBOM.cdx.json +46 -0
  7. package/TEST-MATRIX.json +31 -0
  8. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  9. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  10. package/controller/README.TXT +2 -2
  11. package/controller/deadbyte-controller.ps1 +2 -2
  12. package/docs/ARCHITECTURE.md +1 -1
  13. package/native/deadbyte-hardware-observer.cpp +308 -0
  14. package/package.json +12 -7
  15. package/scripts/autonomous-tests.ps1 +2 -2
  16. package/scripts/build-hardware-r45.ps1 +71 -0
  17. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  18. package/scripts/derive-r45-parent.mjs +24 -0
  19. package/scripts/derive-r45-release.mjs +72 -0
  20. package/scripts/derive-r45-tests.mjs +20 -0
  21. package/scripts/final-closure-r45.mjs +121 -0
  22. package/scripts/final-closure-verify-r45.mjs +220 -0
  23. package/scripts/finalize-r45.mjs +219 -0
  24. package/scripts/full-regression-r45.mjs +20 -0
  25. package/scripts/full-regression-summary-r45.mjs +14 -0
  26. package/scripts/gate-windows-r45.ps1 +44 -0
  27. package/scripts/init-host-policy.ps1 +39 -1
  28. package/scripts/inspect-active-release-r45.mjs +20 -0
  29. package/scripts/release-manifest.mjs +3 -1
  30. package/scripts/release-parity-r45.mjs +253 -0
  31. package/scripts/release-parity-tests-r45.ps1 +8 -0
  32. package/scripts/verify-r45-parent.mjs +142 -0
  33. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  34. package/src/autonomous-runtime.mjs +12 -7
  35. package/src/capability-ledger.mjs +1 -0
  36. package/src/compact-gateway-mcp-tools.mjs +4 -2
  37. package/src/deadbyte-cli.mjs +17 -9
  38. package/src/doctor-r45.mjs +65 -0
  39. package/src/host-plane.mjs +133 -85
  40. package/src/mcp-server.mjs +64 -23
  41. package/src/native-diff-v2.mjs +9 -4
  42. package/src/native-diff.mjs +29 -7
  43. package/src/observation-mcp-tools.mjs +5 -1
  44. package/src/observation-output-contracts.mjs +8 -0
  45. package/src/observation-runtime.mjs +33 -2
  46. package/src/observation-smoke-client.mjs +6 -2
  47. package/src/output-contracts.mjs +27 -11
  48. package/src/r45-compact-disarmed-probe.mjs +74 -0
  49. package/src/r45-full-surface-probe.mjs +34 -0
  50. package/src/release-generation.mjs +1 -0
  51. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  52. package/src/runtime-update.mjs +36 -8
  53. package/src/version.mjs +1 -1
  54. package/test/agent-runtime.test.mjs +1 -0
  55. package/test/autonomous-loop.test.mjs +24 -0
  56. package/test/autonomous-runtime.test.mjs +1 -0
  57. package/test/cli-entrypoint.test.mjs +4 -4
  58. package/test/coding-plane.test.mjs +1 -0
  59. package/test/compact-max-catalog-r45.test.mjs +99 -0
  60. package/test/contained.test.mjs +23 -11
  61. package/test/controller.test.mjs +5 -1
  62. package/test/core.test.mjs +1 -1
  63. package/test/full-max-catalog-r45.test.mjs +91 -0
  64. package/test/hardware-observation-r45.test.mjs +44 -0
  65. package/test/host-inline-r45.test.mjs +67 -0
  66. package/test/machine-mcp-integration.test.mjs +1 -0
  67. package/test/observation-mcp-tools.test.mjs +4 -3
  68. package/test/output-schema.test.mjs +1 -0
  69. package/test/package-boundary.test.mjs +1 -1
  70. package/test/production-docs.test.mjs +3 -3
  71. package/test/r33-closeout-regression.test.mjs +20 -19
  72. package/test/r33-finalization.test.mjs +3 -3
  73. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  74. package/test/r45-finalization.test.mjs +84 -0
  75. package/test/release-generation.test.mjs +1 -0
  76. package/test/release-version.test.mjs +52 -41
  77. package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
@@ -0,0 +1,24 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
5
+ const must=(text,from,to,label)=>{
6
+ if(!text.includes(from)) throw new Error(`derive missing ${label}: ${from}`);
7
+ return text.split(from).join(to);
8
+ };
9
+ let s=await readFile(path.join(root,'scripts','verify-r44-parent.mjs'),'utf8');
10
+ s=s.split('R44').join('R45').split('r44').join('r45');
11
+ for(const [from,to,label] of [
12
+ ['DEADBYTE-R43-FINAL-CLOSURE-V1','DEADBYTE-R44-FINAL-CLOSURE-V1','closure domain'],
13
+ ['v0.14.1-0a5a833892593283','v0.15.0-c15fa41719be61b3','parent release'],
14
+ ["'0.14.1'","'0.15.0'",'parent version'],
15
+ ['0a5a8338925932830986421dda60ec5643f62f600404de4992fda2a5e79d09a9','c15fa41719be61b3186e49b2367a5f4c057365cfb465102dbefb7d7355f45483','parent manifest'],
16
+ ['===1834','===1871','parent file count'],
17
+ ['fcfcb5a47d55039b7bb5d42faa0739b57b13d8a420d4418ae09d96d2862967cb','6d7429c050b7fe893492e102f00282019116e0c9e54e4f25a5791defa1661d30','parent generation'],
18
+ ['f24100212b55d2016ea9c4ff47df5f026c8edc0b9b8643933ed8bf26fde3803f','8aa9c31ab18dfde67275d7cd59295ba08099839d35d6c97ee277fb184c05cae0','parent closure file'],
19
+ ['2f29e94dd726638c23b2e520a608dcf2a6919e43348855be3940599d557f920b','533b7e0ff9ded04086dd90ed34e3b87bc427acc3c55c25bb9f4ec66cb448e667','parent closure'],
20
+ ['deadbyte.r43-final-closure.v1','deadbyte.r44-final-closure.v1','closure schema'],
21
+ ['R43 AUTHORITY HARDENING PATCH','R44 REMOTE AGENT SKILL BASELINE','closure label'],
22
+ ['deadbyte.r43-parity.v1','deadbyte.r44-parity.v1','parity schema']
23
+ ]) s=must(s,from,to,label);
24
+ await writeFile(path.join(root,'scripts','verify-r45-parent.mjs'),s,'utf8');
@@ -0,0 +1,72 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
5
+ const oldRelease='v0.14.1-0a5a833892593283';
6
+ const oldManifest='0a5a8338925932830986421dda60ec5643f62f600404de4992fda2a5e79d09a9';
7
+ const oldClosure='2f29e94dd726638c23b2e520a608dcf2a6919e43348855be3940599d557f920b';
8
+ const parentRelease='v0.15.0-c15fa41719be61b3';
9
+ const parentManifest='c15fa41719be61b3186e49b2367a5f4c057365cfb465102dbefb7d7355f45483';
10
+ const parentClosure='533b7e0ff9ded04086dd90ed34e3b87bc427acc3c55c25bb9f4ec66cb448e667';
11
+ async function derive(src,dst,mutate){
12
+ let s=await readFile(path.join(root,src),'utf8');
13
+ s=s.split('R44').join('R45').split('r44').join('r45').split('0.15.0').join('0.15.1');
14
+ s=mutate?s=mutate(s):s;
15
+ await writeFile(path.join(root,dst),s,'utf8');
16
+ }
17
+ await derive('src/remote-mcp-r44-parity-client.mjs','src/remote-mcp-r45-parity-client.mjs',s=>{
18
+ s=s.replace("'desktop_status','desktop_observe','desktop_target','desktop_action','skill_catalog'","'desktop_status','desktop_observe','desktop_target','desktop_action','skill_catalog','machine_hardware_query'");
19
+ s=s.replace('R45_EXPECTED_TOOLS.length !== 81','R45_EXPECTED_TOOLS.length !== 82');
20
+ s=s.replace("const testScript='scripts/release-parity-tests-r40.ps1';","const testScript='scripts/release-parity-tests-r45.ps1';");
21
+ const needle=" const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');\n observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);";
22
+ const add=needle+"\n const hardware=gateway(await client.callTool({name:'machine_observe',arguments:{op:'hardware_query',query:'cpu'}}),'hardware query');\n if(hardware.query!=='cpu'||!hardware.data||typeof hardware.data!=='object'||!/^[0-9a-f]{64}$/.test(String(hardware.observer_sha256??''))) throw new Error('R45 native hardware observation malformed');\n observationReceipts.push(hardware.receipt_id);await verifyObservationReceipt(policy,hardware.receipt_id);";
23
+ if(!s.includes(needle)) throw new Error('R45 compact parity hardware insertion point missing');
24
+ s=s.replace(needle,add);
25
+ s=s.replace("observation_search_result_count:page.results.length,process_session_id:processSession","observation_search_result_count:page.results.length,native_hardware_query_verified:true,hardware_observer_sha256:hardware.observer_sha256,process_session_id:processSession");
26
+ return s;
27
+ });
28
+ await derive('src/r44-full-surface-probe.mjs','src/r45-full-surface-probe.mjs');
29
+ await derive('src/r44-compact-disarmed-probe.mjs','src/r45-compact-disarmed-probe.mjs');
30
+ await derive('scripts/deferred-slot-operation-r44.mjs','scripts/deferred-slot-operation-r45.mjs');
31
+ await derive('scripts/full-regression-r44.mjs','scripts/full-regression-r45.mjs');
32
+ await derive('scripts/release-parity-r44.mjs','scripts/release-parity-r45.mjs',s=>{
33
+ s=s.split(oldRelease).join(parentRelease).split(oldManifest).join(parentManifest);
34
+ s=s.replace("import { probeR40CompactDisarmed } from '../src/r40-compact-disarmed-probe.mjs';","import { probeR44CompactDisarmed as probeR44PredecessorDisarmed } from '../src/r44-compact-disarmed-probe.mjs';");
35
+ s=s.replaceAll('publicR43PredecessorDisarmed','publicR44PredecessorDisarmed').replaceAll('probeR40CompactDisarmed(url)','probeR44PredecessorDisarmed(url)');
36
+ s=s.replaceAll('R43 predecessor','R44 predecessor').replaceAll('exact R43 predecessor','exact R44 predecessor').replaceAll('sealed R43 predecessor','sealed R44 predecessor');
37
+ s=s.replace('result.tool_count===42','result.tool_count===43');
38
+ return s;
39
+ });
40
+ await derive('scripts/windows-gate-evidence-r44.mjs','scripts/windows-gate-evidence-r45.mjs',s=>{
41
+ s=s.replaceAll('REMOTE AGENT SKILL','MACHINE FREEDOM');
42
+ s=s.replace("full_regression_required:true,codex_live_binary_required:false","full_regression_required:true,codex_live_binary_required:false,host_inline_native:true,native_hardware_observation:true,doctor_release_truth:true");
43
+ return s;
44
+ });
45
+ await derive('scripts/final-closure-r44.mjs','scripts/final-closure-r45.mjs',s=>{
46
+ s=s.replaceAll('REMOTE AGENT SKILL','MACHINE FREEDOM');
47
+ s=s.replaceAll("'0.14.1'","'0.15.0'");
48
+ s=s.split(oldRelease).join(parentRelease).split(oldManifest).join(parentManifest);
49
+ return s;
50
+ });
51
+ await derive('scripts/final-closure-verify-r44.mjs','scripts/final-closure-verify-r45.mjs',s=>{
52
+ s=s.split(oldRelease).join(parentRelease).split(oldManifest).join(parentManifest);
53
+ s=s.replaceAll('R45_FULL_TOOL_COUNT=81','R45_FULL_TOOL_COUNT=82');
54
+ s=s.replaceAll('isR43PredecessorIdentity','isR44PredecessorIdentity').replaceAll('R43 predecessor','R44 predecessor');
55
+ s=s.replaceAll("value?.version==='0.14.1'","value?.version==='0.15.0'");
56
+ s=s.replaceAll('REMOTE AGENT SKILL','MACHINE FREEDOM');
57
+ return s;
58
+ });
59
+ await derive('scripts/finalize-r44.mjs','scripts/finalize-r45.mjs',s=>{
60
+ s=s.split(oldRelease).join(parentRelease).split(oldManifest).join(parentManifest).split(oldClosure).join(parentClosure);
61
+ s=s.replaceAll('REMOTE AGENT SKILL','MACHINE FREEDOM').replaceAll('sealed R43 parent','sealed R44 parent');
62
+ return s;
63
+ });
64
+ await derive('scripts/gate-windows-r44.ps1','scripts/gate-windows-r45.ps1',s=>{
65
+ s=s.replaceAll('REMOTE AGENT SKILL','MACHINE FREEDOM');
66
+ const needle="Invoke-GateStep -Name 'desktop-policy-refresh' -NpmArgs @('run','desktop:policy:refresh')";
67
+ if(!s.includes(needle)) throw new Error('R45 gate hardware insertion point missing');
68
+ s=s.replace(needle,needle+"\nInvoke-GateStep -Name 'hardware-build' -NpmArgs @('run','hardware:build')");
69
+ s=s.replace("'r45-feature-focused'","'r45-machine-freedom-focused'");
70
+ return s;
71
+ });
72
+ console.log('R45 successor files derived');
@@ -0,0 +1,20 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
5
+ async function copy(src,dst,mutate){
6
+ let s=await readFile(path.join(root,src),'utf8');
7
+ s=s.split('R44').join('R45').split('r44').join('r45').split('0.15.0').join('0.15.1');
8
+ s=mutate?s=mutate(s):s;
9
+ await writeFile(path.join(root,dst),s,'utf8');
10
+ }
11
+ await copy('test/full-max-catalog-r44.test.mjs','test/full-max-catalog-r45.test.mjs',s=>{
12
+ s=s.replaceAll('exactly 81','exactly 82').replaceAll('catalog 81','catalog 82').replace('assert.equal(names.length,81','assert.equal(names.length,82');
13
+ s=s.replace("for(const name of ['desktop_status'","for(const name of ['machine_hardware_query','desktop_status'");
14
+ return s;
15
+ });
16
+ await copy('test/compact-max-catalog-r44.test.mjs','test/compact-max-catalog-r45.test.mjs',s=>{
17
+ s=s.replace("const skill=m.capabilities.find(item=>item.id==='skill.catalog');","const hardware=m.capabilities.find(item=>item.id==='observation.hardware_query');\n assert.ok(hardware);assert.equal(hardware.authority,'none');assert.equal(hardware.compact_tool,'machine_observe');assert.equal(hardware.compact_op,'hardware_query');\n const skill=m.capabilities.find(item=>item.id==='skill.catalog');");
18
+ s=s.replace("desktop:true,skill:true","desktop:true,hardware_query:true,skill:true");
19
+ return s;
20
+ });
@@ -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,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
+ }