deadbyte-mcp 0.12.0 → 0.14.0

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 (90) hide show
  1. package/.gitattributes +7 -0
  2. package/AGENTS.md +25 -0
  3. package/CONTEXT.md +3 -3
  4. package/MANIFEST.SHA256 +89 -45
  5. package/R41-PARENT.json +15 -0
  6. package/R42-PARENT.json +19 -0
  7. package/README.txt +2 -2
  8. package/bin/appcontainer-stage.obj +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.obj +0 -0
  11. package/bin/bootstrap-kernel32.obj +0 -0
  12. package/bin/child-control-probe.obj +0 -0
  13. package/bin/child-control-stage.obj +0 -0
  14. package/bin/contained-reverse-worker.obj +0 -0
  15. package/bin/contained-transform-worker.obj +0 -0
  16. package/bin/containment-probe.obj +0 -0
  17. package/bin/deadbyte-contain.obj +0 -0
  18. package/bin/deadbyte-exec.obj +0 -0
  19. package/bin/deadbyte-process-host.obj +0 -0
  20. package/bin/deadbyte-tunnel-host.obj +0 -0
  21. package/controller/README.TXT +1 -1
  22. package/controller/deadbyte-controller.ps1 +1 -1
  23. package/controller/deadbyte-desktop-policy.json +5 -5
  24. package/controller/deadbyte-process-policy.json +4 -4
  25. package/docs/ARCHITECTURE.md +1 -1
  26. package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
  27. package/harness/r42-agent-evals.json +48 -0
  28. package/harness/task-contract.schema.json +33 -0
  29. package/package.json +11 -7
  30. package/proof/CONTAINMENT-BUILD.txt +6 -6
  31. package/scripts/agent-eval-r42.mjs +46 -0
  32. package/scripts/build-containment.ps1 +24 -14
  33. package/scripts/build-desktop-r40.ps1 +15 -9
  34. package/scripts/deferred-slot-operation-r41.mjs +127 -0
  35. package/scripts/deferred-slot-operation-r42.mjs +126 -0
  36. package/scripts/final-closure-r41.mjs +116 -0
  37. package/scripts/final-closure-r42.mjs +116 -0
  38. package/scripts/final-closure-verify-r41.mjs +215 -0
  39. package/scripts/final-closure-verify-r42.mjs +223 -0
  40. package/scripts/gate-windows-r41.ps1 +48 -0
  41. package/scripts/gate-windows-r42.ps1 +54 -0
  42. package/scripts/init-desktop-policy-r40.ps1 +10 -4
  43. package/scripts/release-parity-r41.mjs +221 -0
  44. package/scripts/release-parity-r42.mjs +235 -0
  45. package/scripts/release-parity-tests-r41.ps1 +9 -0
  46. package/scripts/release-parity-tests-r42.ps1 +6 -0
  47. package/scripts/verify-memory-r41.mjs +88 -0
  48. package/scripts/verify-r41-parent.mjs +61 -0
  49. package/scripts/verify-r42-parent.mjs +101 -0
  50. package/scripts/windows-gate-evidence-r41.mjs +53 -0
  51. package/scripts/windows-gate-evidence-r42.mjs +64 -0
  52. package/src/agent-eval-r42.mjs +73 -0
  53. package/src/agent-eval-runner-r42.mjs +138 -0
  54. package/src/agent-governance-r42.mjs +142 -0
  55. package/src/autonomous-context-engine.mjs +4 -1
  56. package/src/autonomous-loop-state.mjs +10 -0
  57. package/src/autonomous-mcp-tools.mjs +21 -6
  58. package/src/autonomous-output-contracts.mjs +18 -2
  59. package/src/autonomous-planner-contracts.mjs +3 -0
  60. package/src/autonomous-planner.mjs +48 -6
  61. package/src/autonomous-policy.mjs +10 -2
  62. package/src/autonomous-runtime.mjs +144 -25
  63. package/src/autonomous-supervisor.mjs +1 -1
  64. package/src/evidence-publisher-r42.mjs +76 -0
  65. package/src/mcp-server.mjs +15 -2
  66. package/src/memory-r41.mjs +692 -0
  67. package/src/provider-circuit.mjs +4 -4
  68. package/src/version.mjs +1 -1
  69. package/test/agent-eval-r42.test.mjs +39 -0
  70. package/test/agent-eval-runner-r42.test.mjs +61 -0
  71. package/test/agent-governance-r42.test.mjs +77 -0
  72. package/test/autonomous-r34-e2e.test.mjs +2 -1
  73. package/test/cli-entrypoint.test.mjs +1 -1
  74. package/test/controller-desktop-r40.test.mjs +2 -1
  75. package/test/core.test.mjs +1 -1
  76. package/test/evidence-publisher-r42.test.mjs +53 -0
  77. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  78. package/test/human-clarification-r42.test.mjs +53 -0
  79. package/test/memory-autonomous-integration-r41.test.mjs +31 -0
  80. package/test/memory-history-r41.test.mjs +126 -0
  81. package/test/memory-projections-r41.test.mjs +92 -0
  82. package/test/memory-retrieval-r41.test.mjs +124 -0
  83. package/test/memory-snapshot-r41.test.mjs +106 -0
  84. package/test/provider-responses-r42.test.mjs +90 -0
  85. package/test/r33-closeout-regression.test.mjs +27 -12
  86. package/test/r33-finalization.test.mjs +4 -4
  87. package/test/r41-finalization.test.mjs +38 -0
  88. package/test/r42-finalization.test.mjs +38 -0
  89. package/test/release-version.test.mjs +14 -13
  90. package/test/task-contract-r42.test.mjs +41 -0
@@ -0,0 +1,235 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
3
+ import { loadPowerShellDataFile } from '../src/runtime-update.mjs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { probeR40FullSurface } from '../src/r40-full-surface-probe.mjs';
9
+ import { runR40CompactParity } from '../src/remote-mcp-r40-parity-client.mjs';
10
+ import { probeR40CompactDisarmed } from '../src/r40-compact-disarmed-probe.mjs';
11
+ import { probeR36CompactDisarmed } from '../src/r36-compact-disarmed-probe.mjs';
12
+ import { runR40DesktopGuardedSmoke } from '../src/remote-mcp-desktop-r40-smoke-client.mjs';
13
+ import { verifyR41Memory } from './verify-memory-r41.mjs';
14
+ import { canonicalJson } from '../src/canonical-json.mjs';
15
+ import { readAndVerifyR42EvalEvidence } from '../src/agent-eval-runner-r42.mjs';
16
+
17
+ const here=path.dirname(fileURLToPath(import.meta.url));
18
+ const root=path.resolve(here,'..');
19
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
20
+ const deferred=path.join(root,'scripts','deferred-slot-operation-r42.mjs');
21
+ const ps='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
22
+ const launcher=path.join(stateRoot,'bin','DEADBYTE-MCP.ps1');
23
+ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
24
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
25
+
26
+ export const R42_PREDECESSOR_RELEASE_ID='v0.13.0-3255be10de4daf08';
27
+ export const R42_PREDECESSOR_MANIFEST='3255be10de4daf0837d67d1ab1913b785b8b8215bc7405823c2ff46968c4b98f';
28
+
29
+ function assert(condition,message){if(!condition) throw new Error(message);}
30
+ function parseFrozenManifest(text){
31
+ const entries=[];const seen=new Set();
32
+ for(const [index,line] of text.split(/\r?\n/).entries()){
33
+ if(!line) continue;
34
+ const match=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);
35
+ assert(match,`frozen manifest line ${index+1} malformed`);
36
+ const rel=match[2];
37
+ assert(!path.posix.isAbsolute(rel)&&!rel.split('/').includes('..')&&!seen.has(rel),`unsafe/duplicate frozen manifest path: ${rel}`);
38
+ seen.add(rel);entries.push({sha256:match[1],path:rel});
39
+ }
40
+ return entries;
41
+ }
42
+ // Restore the mutable development stage from the immutable active release after live parity work may drift it.
43
+ export async function restoreFrozenStage({developmentRoot,releaseRoot,expectedManifestSha256}){
44
+ const dev=path.resolve(developmentRoot);const release=path.resolve(releaseRoot);
45
+ const manifestBytes=await readFile(path.join(release,'MANIFEST.SHA256'));
46
+ const manifestSha256=sha256(manifestBytes);
47
+ assert(manifestSha256===expectedManifestSha256,`immutable release manifest drift: ${manifestSha256}`);
48
+ const entries=parseFrozenManifest(manifestBytes.toString('utf8'));
49
+ for(const entry of entries){
50
+ const source=path.join(release,...entry.path.split('/'));const bytes=await readFile(source);
51
+ assert(sha256(bytes)===entry.sha256,`immutable release file drift: ${entry.path}`);
52
+ const destination=path.join(dev,...entry.path.split('/'));await mkdir(path.dirname(destination),{recursive:true});
53
+ await writeFile(destination,bytes);assert(sha256(await readFile(destination))===entry.sha256,`development restore verification failed: ${entry.path}`);
54
+ }
55
+ await writeFile(path.join(dev,'MANIFEST.SHA256'),manifestBytes);
56
+ assert(sha256(await readFile(path.join(dev,'MANIFEST.SHA256')))===expectedManifestSha256,'development manifest restore verification failed');
57
+ return {status:'restored',file_count:entries.length,manifest_sha256:manifestSha256};
58
+ }
59
+ function parseJsonOutput(text,label){
60
+ const trimmed=String(text).trim();const starts=[...trimmed.matchAll(/\{/g)].map(match=>match.index);
61
+ for(const start of starts){try{return JSON.parse(trimmed.slice(start));}catch{}}
62
+ throw new Error(`${label} did not emit JSON`);
63
+ }
64
+ function run(command,args,{timeoutMs=900000,env=process.env,cwd=root}={}){
65
+ return new Promise((resolve,reject)=>{
66
+ const child=spawn(command,args,{cwd,shell:false,windowsHide:true,env,stdio:['ignore','pipe','pipe']});
67
+ const out=[],err=[];const timer=setTimeout(()=>{child.kill();reject(new Error(`command timeout: ${path.basename(command)}`));},timeoutMs);
68
+ child.stdout.on('data',chunk=>out.push(Buffer.from(chunk)));child.stderr.on('data',chunk=>err.push(Buffer.from(chunk)));
69
+ child.on('error',error=>{clearTimeout(timer);reject(error);});child.on('exit',code=>{
70
+ clearTimeout(timer);const stdout=Buffer.concat(out).toString('utf8');const stderr=Buffer.concat(err).toString('utf8');
71
+ if(code!==0) reject(new Error(`command failed exit=${code}: ${stderr||stdout}`));else resolve({stdout,stderr,exitCode:code});
72
+ });
73
+ });
74
+ }
75
+ async function publicUrl(){
76
+ const value=(await readFile(path.join(stateRoot,'bridge','mcp-url.txt'),'utf8')).trim();const url=new URL(value);
77
+ if(url.protocol!=='https:') throw new Error('public MCP URL must be HTTPS');return value;
78
+ }
79
+ async function pointer(){return JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));}
80
+ async function disarmAll(){
81
+ for(const command of ['disarmdesktop','disarmautonomous','disarmcoding','disarmprocess','disarmhost']){
82
+ await run(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{timeoutMs:60000});
83
+ }
84
+ }
85
+ async function waitDeferred(statePath,timeoutMs=180000){
86
+ const deadline=Date.now()+timeoutMs;
87
+ while(Date.now()<deadline){const state=JSON.parse(await readFile(statePath,'utf8'));if(state.state==='completed') return state;if(state.state==='failed') throw new Error(`deferred ${state.mode} failed: ${state.error}`);await sleep(500);}
88
+ throw new Error(`deferred operation timeout: ${statePath}`);
89
+ }
90
+ async function scheduleDeferred(mode,value,{bridgeRoot=null,bridgeScriptSha256=null}={}){
91
+ if((bridgeRoot===null)!==(bridgeScriptSha256===null)) throw new Error('deferred bridge override requires exact root + SHA pair');
92
+ const args=[deferred,mode,value];
93
+ if(bridgeRoot!==null) args.push('--bridge-root',bridgeRoot,'--bridge-script-sha256',bridgeScriptSha256);
94
+ const scheduled=parseJsonOutput((await run(process.execPath,args,{timeoutMs:30000})).stdout,`deferred ${mode}`);
95
+ assert(scheduled.status==='scheduled',`deferred ${mode} was not scheduled`);
96
+ assert(scheduled.execution_provider==='deadbyte_mcp'&&scheduled.rdc_operation_count===0,'deferred operation provider mismatch');
97
+ return {scheduled,completed:await waitDeferred(scheduled.state_path)};
98
+ }
99
+ async function verifyR42ModelEval(candidateManifest){
100
+ const suite=JSON.parse(await readFile(path.join(root,'harness','r42-agent-evals.json'),'utf8'));
101
+ const suiteSha256=sha256(Buffer.from(canonicalJson(suite),'utf8'));
102
+ const evidenceId=`r42-${candidateManifest.slice(0,16)}-${suiteSha256.slice(0,16)}`;
103
+ const file=path.join(stateRoot,'evidence','release','r42-agent-evals','agent_eval',`${evidenceId}.json`);
104
+ const verified=await readAndVerifyR42EvalEvidence(file,{subjectManifestSha256:candidateManifest,suiteSha256});
105
+ return {status:'passed',path:file,evidence_sha256:verified.evidence_sha256,payload_sha256:verified.payload_sha256,
106
+ suite_sha256:suiteSha256,case_count:verified.payload.case_count};
107
+ }
108
+
109
+ async function publicR40PredecessorDisarmed(url){
110
+ const result=await probeR36CompactDisarmed(url);
111
+ assert(result.status==='passed'&&result.tool_count<=42&&result.capability_closure===true,'R40 predecessor public compact catalog mismatch');
112
+ assert(result.release_id===R42_PREDECESSOR_RELEASE_ID&&result.release_manifest_sha256===R42_PREDECESSOR_MANIFEST,'R40 predecessor machine binding mismatch');
113
+ assert(result.coding_armed===false&&result.autonomous_armed===false,'R40 predecessor authority remained armed');
114
+ return result;
115
+ }
116
+ async function mutableCore(pointerValue){
117
+ const policy=JSON.parse(await readFile(path.join(pointerValue.generation_root,'coding-policy.json'),'utf8'));
118
+ const raw=policy?.roots?.core?.path??policy?.roots?.core?.real_path;
119
+ assert(typeof raw==='string'&&raw,'R40 generation missing coding core root');
120
+ const core=path.resolve(raw);const release=path.resolve(pointerValue.release_root);
121
+ assert(core.toLowerCase()!==release.toLowerCase(),'R40 coding core must not point at immutable active release slot');
122
+ return core;
123
+ }
124
+
125
+ export function validateParityEvidence(value){
126
+ assert(value?.schema==='deadbyte.r42-parity.v1','R40 parity schema mismatch');
127
+ assert(value.status==='passed','R40 parity status is not passed');
128
+ assert(value.rdc_operation_count===0&&value.execution_provider==='deadbyte_mcp','R40 parity used non-DEADBYTE execution');
129
+ assert(value.candidate?.version==='0.14.0'&&/^[0-9a-f]{64}$/.test(value.candidate?.manifest_sha256??''),'R40 candidate identity missing');
130
+ assert(value.local_full?.status==='passed'&&value.local_full?.tool_count===80&&value.local_full?.strict_output_schemas===true,'R40 local full 80-tool proof missing');
131
+ assert(value.public_compact?.status==='passed'&&value.public_compact?.tool_count===42&&value.public_compact?.capability_closure===true&&value.public_compact?.gateway_count===6&&value.public_compact?.desktop_armed===false&&value.public_compact?.desktop_observation_receipt_verified===true&&value.public_compact?.desktop_grounding_receipt_verified===true,'R40 public compact/Desktop closure missing');
132
+ assert(value.desktop_guarded?.status==='passed'&&value.desktop_guarded?.native_injected===true&&value.desktop_guarded?.replay_performed===false&&
133
+ Number.isInteger(value.desktop_guarded?.predispatch_retry_count)&&value.desktop_guarded.predispatch_retry_count>=0&&value.desktop_guarded.predispatch_retry_count<=2&&
134
+ value.desktop_guarded?.observation_receipts_verified===value.desktop_guarded.predispatch_retry_count+1&&value.desktop_guarded?.action_receipts_verified===3,
135
+ 'R40 guarded Desktop proof missing');
136
+ assert(value.autonomous?.status==='passed'&&value.autonomous?.signed_release_verified===true,'R40 autonomous live proof missing');
137
+ assert(value.deterministic_memory?.status==='passed'&&value.deterministic_memory?.goal_count>=1&&value.deterministic_memory?.snapshot_count>=1&&value.deterministic_memory?.index_count>=1,'R41 deterministic memory proof missing');
138
+ assert(value.model_eval?.status==='passed'&&value.model_eval?.case_count>=1&&/^[0-9a-f]{64}$/.test(value.model_eval?.evidence_sha256??''),'R42 model-backed eval proof missing');
139
+ assert(value.before_rollback_disarmed?.status==='passed'&&value.before_rollback_disarmed?.capability_closure===true&&value.before_rollback_disarmed?.coding_armed===false&&value.before_rollback_disarmed?.autonomous_armed===false&&value.before_rollback_disarmed?.desktop_armed===false,'R40 pre-rollback disarm proof missing');
140
+ assert(value.rollback?.state==='completed'&&value.restore?.state==='completed','R40 rollback/restore not completed');
141
+ assert(value.stage_restore?.status==='restored'&&value.stage_restore?.manifest_sha256===value.candidate.manifest_sha256,'R40 stage restore proof missing');
142
+ assert(value.predecessor?.release_id===R42_PREDECESSOR_RELEASE_ID&&value.predecessor?.manifest_sha256===R42_PREDECESSOR_MANIFEST&&value.predecessor?.version==='0.13.0','R40 exact predecessor identity mismatch');
143
+ assert(value.predecessor_disarmed?.status==='passed'&&value.predecessor_disarmed?.tool_count<=42&&value.predecessor_disarmed?.capability_closure===true,'R40 predecessor public proof missing');
144
+ assert(value.successor?.version==='0.14.0'&&value.successor?.manifest_sha256===value.candidate.manifest_sha256,'R40 exact successor restore identity mismatch');
145
+ assert(value.successor_disarmed?.status==='passed'&&value.successor_disarmed?.capability_closure===true&&value.successor_disarmed?.coding_armed===false&&value.successor_disarmed?.autonomous_armed===false&&value.successor_disarmed?.desktop_armed===false,'R40 successor disarmed proof missing');
146
+ assert(value.immutable_slot?.protected===true&&value.immutable_slot?.development_root!==value.immutable_slot?.release_root,'R40 immutable-slot separation missing');
147
+ for(const key of ['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','model_eval']) assert(value.requirements?.[key]===true,`R41 parity requirement not proven: ${key}`);
148
+ return true;
149
+ }
150
+
151
+ export async function runReleaseParity(activationReceiptPath){
152
+ const active=await pointer();const candidateManifest=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
153
+ assert(active.version==='0.14.0','R41 parity requires active 0.14.0 candidate');
154
+ assert(active.manifest_sha256===candidateManifest,'active release is not canonical R40 candidate');
155
+ const activeConfig=await loadPowerShellDataFile(active.config_path);
156
+ const successorBridgeRootRaw=activeConfig?.Bridge?.WorkingDirectory;
157
+ const successorBridgeConfiguredSha=activeConfig?.Bridge?.ScriptSha256;
158
+ assert(typeof successorBridgeRootRaw==='string'&&path.isAbsolute(successorBridgeRootRaw),'R40 active bridge root missing');
159
+ assert(typeof successorBridgeConfiguredSha==='string'&&/^[0-9a-f]{64}$/.test(successorBridgeConfiguredSha),'R40 active bridge SHA missing');
160
+ const successorBridgeRoot=await realpath(path.resolve(successorBridgeRootRaw));
161
+ const successorBridgeActualSha=sha256(await readFile(path.join(successorBridgeRoot,'src','bridge.mjs')));
162
+ assert(successorBridgeActualSha===successorBridgeConfiguredSha,'R40 active bridge bytes do not match generation pin');
163
+ const successorBridge={root:successorBridgeRoot,sha256:successorBridgeActualSha};
164
+ const developmentRoot=await mutableCore(active);
165
+ const localFull=await probeR40FullSurface({packageRoot:active.release_root,generationRoot:active.generation_root,stateRoot});
166
+ const url=await publicUrl();
167
+ let publicCompact,autonomous,desktopGuarded;
168
+ try{
169
+ publicCompact=await runR40CompactParity(url);
170
+ autonomous=parseJsonOutput((await run(process.execPath,[path.join(root,'src','remote-mcp-autonomous-smoke-client.mjs'),url],{timeoutMs:900000})).stdout,'R40 autonomous compact parity');
171
+ assert(autonomous.status==='passed'&&autonomous.signed_release_verified===true,'R40 autonomous compact parity failed');
172
+ await run(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'armdesktop'],{timeoutMs:60000});
173
+ try{desktopGuarded=await runR40DesktopGuardedSmoke(url);}
174
+ finally{await run(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'disarmdesktop'],{timeoutMs:60000}).catch(()=>{});}
175
+ assert(desktopGuarded.status==='passed'&&desktopGuarded.native_injected===true&&desktopGuarded.action_receipts_verified===3,'R40 guarded Desktop public smoke failed');
176
+ }catch(error){await disarmAll().catch(()=>{});throw error;}
177
+ const deterministicMemory=await verifyR41Memory({memoryRoot:path.join(stateRoot,'evidence','autonomous','memory-r41',candidateManifest),
178
+ journalRoot:path.join(stateRoot,'evidence','autonomous','goals'),sourceReleaseManifest:candidateManifest,
179
+ publicKeyPath:path.join(stateRoot,'trust','ed25519-public.pem')});
180
+ assert(deterministicMemory.goal_count>=1&&deterministicMemory.snapshot_count>=1&&deterministicMemory.index_count>=1,'R41 live deterministic memory evidence missing');
181
+ const modelEval=await verifyR42ModelEval(candidateManifest);
182
+ assert(modelEval.status==='passed'&&modelEval.case_count>=1,'R42 model-backed eval evidence missing');
183
+
184
+ await disarmAll();
185
+ const beforeRollback=await probeR40CompactDisarmed(url);
186
+ const rollbackPair=await scheduleDeferred('rollback',path.resolve(activationReceiptPath));
187
+ const predecessor=await pointer();
188
+ assert(predecessor.release_id===R42_PREDECESSOR_RELEASE_ID&&predecessor.manifest_sha256===R42_PREDECESSOR_MANIFEST&&predecessor.version==='0.13.0','rollback did not reach exact sealed R40 predecessor');
189
+ const predecessorDisarmed=await publicR40PredecessorDisarmed(url);
190
+ const stageRestore=await restoreFrozenStage({developmentRoot,releaseRoot:active.release_root,expectedManifestSha256:candidateManifest});
191
+ const restorePair=await scheduleDeferred('activate',candidateManifest,{bridgeRoot:successorBridge.root,bridgeScriptSha256:successorBridge.sha256});
192
+ const successor=await pointer();
193
+ assert(successor.version==='0.14.0'&&successor.manifest_sha256===candidateManifest,'restore did not return exact R40 candidate');
194
+ const successorDisarmed=await probeR40CompactDisarmed(url);
195
+ await disarmAll();
196
+
197
+ const evidence={
198
+ schema:'deadbyte.r42-parity.v1',status:'passed',completed_at_utc:new Date().toISOString(),execution_provider:'deadbyte_mcp',rdc_operation_count:0,
199
+ candidate:{release_id:active.release_id,version:active.version,manifest_sha256:candidateManifest},
200
+ immutable_slot:{protected:true,release_root:path.resolve(active.release_root),development_root:developmentRoot},
201
+ local_full:localFull,public_compact:publicCompact,autonomous,deterministic_memory:deterministicMemory,model_eval:modelEval,desktop_guarded:desktopGuarded,before_rollback_disarmed:beforeRollback,
202
+ rollback:{...rollbackPair.completed,scheduled_operation_id:rollbackPair.scheduled.operation_id},
203
+ predecessor:{release_id:predecessor.release_id,version:predecessor.version,manifest_sha256:predecessor.manifest_sha256},predecessor_disarmed:predecessorDisarmed,
204
+ stage_restore:stageRestore,
205
+ restore:{...restorePair.completed,scheduled_operation_id:restorePair.scheduled.operation_id},
206
+ successor:{release_id:successor.release_id,version:successor.version,manifest_sha256:successor.manifest_sha256},successor_disarmed:successorDisarmed,
207
+ requirements:{
208
+ tree_read:publicCompact.tree_entries>=0&&publicCompact.coding_stat_verified===true,
209
+ precondition_mutation:publicCompact.machine_fs_receipts_verified>=3,
210
+ search:publicCompact.coding_search_result_count>=1&&publicCompact.observation_search_result_count>=1,
211
+ owned_process:typeof publicCompact.process_session_id==='string'&&publicCompact.process_output_bytes>0,
212
+ observation:publicCompact.observation_receipts_verified>=2,
213
+ coding_loop:publicCompact.coding_receipts_verified===2,
214
+ autonomy:autonomous.signed_release_verified===true&&autonomous.observed_failure_before_replan===true,
215
+ interrupted_recovery:rollbackPair.completed.state==='completed'&&restorePair.completed.state==='completed',
216
+ evidence_verification:publicCompact.machine_fs_receipts_verified>=3&&autonomous.signed_release_verified===true,
217
+ compact_closure:publicCompact.capability_closure===true&&successorDisarmed.capability_closure===true,
218
+ full_surface:localFull.tool_count===80&&localFull.strict_output_schemas===true,
219
+ desktop_readonly:publicCompact.desktop_armed===false&&publicCompact.desktop_observation_receipt_verified===true&&publicCompact.desktop_grounding_receipt_verified===true,
220
+ desktop_guarded_input:desktopGuarded.native_injected===true&&desktopGuarded.replay_performed===false&&desktopGuarded.action_receipts_verified===3,
221
+ deterministic_memory:deterministicMemory.status==='passed'&&deterministicMemory.goal_count>=1&&deterministicMemory.snapshot_count>=1,
222
+ model_eval:modelEval.status==='passed'&&modelEval.case_count>=1
223
+ }
224
+ };
225
+ validateParityEvidence(evidence);
226
+ const evidenceDir=path.join(stateRoot,'evidence','release');await mkdir(evidenceDir,{recursive:true});
227
+ const evidencePath=path.join(evidenceDir,`r42-parity-${candidateManifest.slice(0,16)}.json`);
228
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');await writeFile(evidencePath,bytes);
229
+ return {evidence,evidence_path:evidencePath,evidence_sha256:sha256(bytes)};
230
+ }
231
+
232
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
233
+ const receipt=process.argv[2];if(!receipt) throw new Error('usage: node scripts/release-parity-r42.mjs <activation-receipt.json>');
234
+ runReleaseParity(receipt).then(result=>console.log(JSON.stringify({status:'passed',...result},null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
235
+ }
@@ -0,0 +1,9 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ Set-StrictMode -Version 2.0
3
+ $root = Split-Path -Parent $PSScriptRoot
4
+ Push-Location $root
5
+ try {
6
+ & node.exe --test test\memory-snapshot-r41.test.mjs test\memory-projections-r41.test.mjs test\memory-retrieval-r41.test.mjs test\memory-history-r41.test.mjs test\memory-autonomous-integration-r41.test.mjs test\r41-finalization.test.mjs test\controller.test.mjs test\controller-desktop-r40.test.mjs test\r40-finalization.test.mjs test\release-version.test.mjs test\desktop-contract-r40.test.mjs test\desktop-policy-r40.test.mjs test\desktop-observer-r40.test.mjs test\desktop-input-r40.test.mjs test\desktop-uia-r40.test.mjs test\desktop-runtime-r40.test.mjs test\desktop-memory-r40.test.mjs test\desktop-mcp-tools-r40.test.mjs test\desktop-adversarial-r40.test.mjs test\autonomous-desktop-r40.test.mjs test\release-generation.test.mjs test\runtime-self-update.test.mjs test\r37-0110-history.test.mjs test\r37-agent-fabric.test.mjs test\remote-live-log-r37.test.mjs test\remote-session-cli.test.mjs test\r36-finalization.test.mjs test\r36-release-identity.test.mjs test\deferred-slot-operation.test.mjs
7
+ if ($LASTEXITCODE -ne 0) { throw "R41 release parity tests failed with exit code $LASTEXITCODE" }
8
+ Write-Output 'R41 RELEASE PARITY TEST PASS'
9
+ } finally { Pop-Location }
@@ -0,0 +1,6 @@
1
+ $ErrorActionPreference='Stop'
2
+ Set-StrictMode -Version 2.0
3
+ & node.exe --test test\provider-responses-r42.test.mjs test\task-contract-r42.test.mjs test\human-clarification-r42.test.mjs test\agent-governance-r42.test.mjs test\evidence-publisher-r42.test.mjs test\agent-eval-r42.test.mjs test\agent-eval-runner-r42.test.mjs test\r42-finalization.test.mjs
4
+ if($LASTEXITCODE -ne 0){exit $LASTEXITCODE}
5
+ & node.exe --test test\memory-snapshot-r41.test.mjs test\memory-projections-r41.test.mjs test\memory-retrieval-r41.test.mjs test\memory-history-r41.test.mjs test\memory-autonomous-integration-r41.test.mjs
6
+ exit $LASTEXITCODE
@@ -0,0 +1,88 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile,readdir } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { canonicalJson } from '../src/canonical-json.mjs';
6
+ import { loadPublicVerifyKey } from '../src/trust.mjs';
7
+ import { buildMemoryArchive,buildMemoryCorpus,buildRetrievalIndex,verifyMemoryGoal,verifyMemoryJournal,
8
+ verifyMemorySnapshot,verifyRetrievalIndex } from '../src/memory-r41.mjs';
9
+
10
+ const HEX64=/^[0-9a-f]{64}$/;
11
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
12
+ const insist=(ok,message)=>{if(!ok)throw new Error(message);};
13
+ const readJson=async file=>JSON.parse((await readFile(file,'utf8')).replace(/^\uFEFF/,''));
14
+ async function jsonFiles(dir){return (await readdir(dir)).filter(name=>name.endsWith('.json')).sort();}
15
+ function parseArgs(argv){
16
+ const out={};for(let index=0;index<argv.length;index+=2){const key=argv[index];insist(key?.startsWith('--')&&argv[index+1],`invalid argument ${key??''}`);out[key.slice(2)]=argv[index+1];}
17
+ for(const key of ['memory-root','journal-root','manifest','public-key'])insist(out[key],`missing --${key}`);return out;
18
+ }
19
+ async function loadCanonicalGoal(journalRoot,goalId,publicKey,keyId,manifest){
20
+ const root=path.join(journalRoot,goalId);const goal=verifyMemoryGoal(await readJson(path.join(root,'goal.json')),{publicKey,keyId,expectedGoalId:goalId});
21
+ const names=await jsonFiles(path.join(root,'events'));
22
+ insist(names.every((name,index)=>name===`${String(index+1).padStart(6,'0')}.json`),`event gap for ${goalId}`);
23
+ const events=[];for(const name of names)events.push(await readJson(path.join(root,'events',name)));
24
+ verifyMemoryJournal(events,{goalId,publicKey,keyId});
25
+ insist(events[0]?.type==='goal_created'&&events[0]?.payload?.source_release_manifest===manifest,`goal ${goalId} is not bound to release manifest`);
26
+ return {goal,events,corpus:buildMemoryCorpus({goal,events,publicKey,keyId,sourceReleaseManifest:manifest})};
27
+ }
28
+
29
+ export async function verifyR41Memory({memoryRoot,journalRoot,sourceReleaseManifest,publicKeyPath}={}){
30
+ insist(path.isAbsolute(memoryRoot)&&path.isAbsolute(journalRoot),'memory verifier roots must be absolute');insist(HEX64.test(sourceReleaseManifest??''),'memory verifier manifest invalid');
31
+ const {publicKey,keyId}=await loadPublicVerifyKey(publicKeyPath);const canonical=new Map();let snapshots=0,indexes=0,archives=0,archiveIndexes=0;
32
+ let journalGoalIds=[];try{journalGoalIds=(await readdir(journalRoot,{withFileTypes:true})).filter(entry=>entry.isDirectory()&&HEX64.test(entry.name)).map(entry=>entry.name).sort();}
33
+ catch(error){if(error?.code!=='ENOENT')throw error;}
34
+ for(const goalId of journalGoalIds){
35
+ const firstPath=path.join(journalRoot,goalId,'events','000001.json');let first;
36
+ try{first=await readJson(firstPath);}catch(error){if(error?.code==='ENOENT')continue;throw error;}
37
+ verifyMemoryJournal([first],{goalId,publicKey,keyId});
38
+ if(first.type!=='goal_created'||first.payload?.source_release_manifest!==sourceReleaseManifest)continue;
39
+ const loaded=await loadCanonicalGoal(journalRoot,goalId,publicKey,keyId,sourceReleaseManifest);canonical.set(goalId,loaded);
40
+ }
41
+ let goalIds=[];try{goalIds=(await readdir(path.join(memoryRoot,'goals'),{withFileTypes:true})).filter(entry=>entry.isDirectory()&&HEX64.test(entry.name)).map(entry=>entry.name).sort();}
42
+ catch(error){if(error?.code!=='ENOENT')throw error;}
43
+ for(const goalId of goalIds){
44
+ const loaded=canonical.get(goalId);insist(loaded,`memory projection goal ${goalId} has no canonical release-bound journal`);
45
+ const base=path.join(memoryRoot,'goals',goalId);const corporaByMemoryHash=new Map();
46
+ for(const name of await jsonFiles(path.join(base,'snapshots'))){
47
+ const value=await readJson(path.join(base,'snapshots',name));
48
+ const proof=verifyMemorySnapshot(value,{goal:loaded.goal,events:loaded.events,publicKey,keyId,sourceReleaseManifest});
49
+ insist(proof.ok,`snapshot invalid ${goalId}/${name}: ${proof.reason}`);
50
+ const prefixEvents=loaded.events.slice(0,proof.last_event_seq);
51
+ const prefixCorpus=buildMemoryCorpus({goal:loaded.goal,events:prefixEvents,publicKey,keyId,sourceReleaseManifest});
52
+ const prior=corporaByMemoryHash.get(prefixCorpus.memory_sha256);
53
+ insist(!prior||canonicalJson(prior)===canonicalJson(prefixCorpus),`snapshot corpus collision ${goalId}/${name}`);
54
+ corporaByMemoryHash.set(prefixCorpus.memory_sha256,prefixCorpus);snapshots+=1;
55
+ }
56
+ for(const name of await jsonFiles(path.join(base,'indexes'))){
57
+ const value=await readJson(path.join(base,'indexes',name));
58
+ const sourceCorpus=corporaByMemoryHash.get(value.source_memory_sha256);
59
+ insist(sourceCorpus,`index source memory hash has no verified snapshot prefix ${goalId}/${name}`);
60
+ const proof=verifyRetrievalIndex(value,{corpus:sourceCorpus,sourceReleaseManifest});
61
+ insist(proof.ok,`index invalid ${goalId}/${name}: ${proof.reason}`);indexes+=1;
62
+ }
63
+ }
64
+ const archiveByHash=new Map();
65
+ try{for(const name of await jsonFiles(path.join(memoryRoot,'archives'))){const value=await readJson(path.join(memoryRoot,'archives',name));
66
+ const corpora=value.corpus_refs.map(ref=>{
67
+ const loaded=canonical.get(ref.goal_id);insist(loaded,`archive references noncanonical goal ${ref.goal_id}`);
68
+ const headIndex=loaded.events.findIndex(event=>event.event_sha256===ref.journal_head_sha256);
69
+ insist(headIndex>=0,`archive references unknown journal head ${ref.goal_id}`);
70
+ const corpus=buildMemoryCorpus({goal:loaded.goal,events:loaded.events.slice(0,headIndex+1),publicKey,keyId,sourceReleaseManifest});
71
+ insist(corpus.memory_sha256===ref.memory_sha256,`archive references noncanonical goal prefix ${ref.goal_id}`);
72
+ return corpus;
73
+ });
74
+ const expected=buildMemoryArchive(corpora,{sourceReleaseManifest});insist(canonicalJson(expected)===canonicalJson(value),'archive projection mismatch');insist(name===`${value.memory_sha256}.json`,'archive filename mismatch');archiveByHash.set(value.memory_sha256,value);archives+=1;}}
75
+ catch(error){if(error?.code!=='ENOENT')throw error;}
76
+ try{for(const name of await jsonFiles(path.join(memoryRoot,'archive-indexes'))){const value=await readJson(path.join(memoryRoot,'archive-indexes',name));const archive=archiveByHash.get(value.source_memory_sha256);insist(archive,'archive index source missing');
77
+ const proof=verifyRetrievalIndex(value,{corpus:archive,sourceReleaseManifest});insist(proof.ok,`archive index invalid ${name}: ${proof.reason}`);insist(canonicalJson(buildRetrievalIndex(archive,{sourceReleaseManifest}))===canonicalJson(value),'archive index rebuild mismatch');archiveIndexes+=1;}}
78
+ catch(error){if(error?.code!=='ENOENT')throw error;}
79
+ return {schema:'deadbyte.r41-memory-verification.v1',status:'passed',source_release_manifest:sourceReleaseManifest,
80
+ goal_count:goalIds.length,snapshot_count:snapshots,index_count:indexes,archive_count:archives,archive_index_count:archiveIndexes,
81
+ verification_sha256:sha(Buffer.from(canonicalJson({goalIds,snapshots,indexes,archives,archiveIndexes,sourceReleaseManifest}),'utf8'))};
82
+ }
83
+
84
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
85
+ const args=parseArgs(process.argv.slice(2));verifyR41Memory({memoryRoot:path.resolve(args['memory-root']),journalRoot:path.resolve(args['journal-root']),
86
+ sourceReleaseManifest:args.manifest,publicKeyPath:path.resolve(args['public-key'])}).then(result=>console.log(JSON.stringify(result,null,2)))
87
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
88
+ }
@@ -0,0 +1,61 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat,readFile,readdir } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
7
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
8
+ const insist=(ok,message)=>{if(!ok)throw new Error(message);};
9
+
10
+ function parseManifest(bytes){
11
+ const entries=new Map();
12
+ for(const [index,line] of bytes.toString('utf8').split(/\r?\n/).entries()){
13
+ if(!line)continue;
14
+ const match=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);insist(match,`parent manifest line ${index+1} malformed`);
15
+ const rel=match[2];insist(!path.posix.isAbsolute(rel)&&!rel.split('/').includes('..')&&!entries.has(rel),`unsafe/duplicate parent manifest path ${rel}`);
16
+ entries.set(rel,match[1]);
17
+ }
18
+ return entries;
19
+ }
20
+ async function walk(dir,rel=''){
21
+ const out=[];
22
+ for(const name of (await readdir(path.join(dir,...(rel?rel.split('/'):[])))).sort((a,b)=>a.localeCompare(b,'en'))){
23
+ const child=rel?`${rel}/${name}`:name;const full=path.join(dir,...child.split('/'));const st=await lstat(full);
24
+ insist(!st.isSymbolicLink(),`parent tree symlink forbidden: ${child}`);
25
+ if(st.isDirectory())out.push(...await walk(dir,child));else{insist(st.isFile(),`parent tree non-file forbidden: ${child}`);out.push(child);}
26
+ }
27
+ return out;
28
+ }
29
+ async function verifyTree(dir,expectedManifest,{exact}){
30
+ const bytes=await readFile(path.join(dir,'MANIFEST.SHA256'));insist(sha(bytes)===expectedManifest,`parent manifest digest mismatch: ${dir}`);
31
+ const entries=parseManifest(bytes);
32
+ if(exact){
33
+ const actual=(await walk(dir)).filter(rel=>rel!=='MANIFEST.SHA256');
34
+ insist(actual.length===entries.size&&actual.every(rel=>entries.has(rel)),`parent exact tree mismatch: ${dir}`);
35
+ }
36
+ for(const [rel,digest] of entries)insist(sha(await readFile(path.join(dir,...rel.split('/'))))===digest,`parent file hash mismatch: ${dir} :: ${rel}`);
37
+ return entries.size;
38
+ }
39
+
40
+ export async function verifyR41Parent(file=path.join(root,'R41-PARENT.json')){
41
+ const record=JSON.parse(await readFile(path.resolve(file),'utf8'));
42
+ insist(record.schema==='deadbyte.r41-parent.v1','R41 parent schema mismatch');
43
+ insist(record.parent_version==='0.12.0'&&record.parent_release_id==='v0.12.0-70906ae0a7e4f062','R41 parent identity mismatch');
44
+ const [sourceFiles,releaseFiles]=await Promise.all([
45
+ verifyTree(path.resolve(record.parent_source_root),record.parent_manifest_sha256,{exact:false}),
46
+ verifyTree(path.resolve(record.parent_release_root),record.parent_manifest_sha256,{exact:true})
47
+ ]);
48
+ insist(sourceFiles===record.parent_manifest_file_count&&releaseFiles===record.parent_manifest_file_count,'R41 parent file count mismatch');
49
+ const evidenceBytes=await readFile(path.resolve(record.parent_public_evidence_path));
50
+ insist(sha(evidenceBytes)===record.parent_public_evidence_sha256,'R40 public evidence hash mismatch');
51
+ const evidence=JSON.parse(evidenceBytes.toString('utf8').replace(/^\uFEFF/,''));
52
+ insist(evidence.schema==='deadbyte.r40-public-final-verify.v1'&&evidence.status==='passed','R40 public evidence status invalid');
53
+ insist(evidence.release_id===record.parent_release_id&&evidence.manifest_sha256===record.parent_manifest_sha256,'R40 public evidence identity mismatch');
54
+ return {status:'passed',parent_release_id:record.parent_release_id,parent_manifest_sha256:record.parent_manifest_sha256,
55
+ source_files:sourceFiles,release_files:releaseFiles,parent_public_evidence_sha256:record.parent_public_evidence_sha256};
56
+ }
57
+
58
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
59
+ verifyR41Parent(process.argv[2]).then(value=>console.log(JSON.stringify(value,null,2)))
60
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
61
+ }
@@ -0,0 +1,101 @@
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
+ import { canonicalJson } from '../src/canonical-json.mjs';
7
+
8
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
9
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
10
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const sha1=bytes=>createHash('sha1').update(bytes).digest('hex');
12
+ const insist=(ok,message)=>{if(!ok)throw new Error(message);};
13
+ const CLOSURE_DOMAIN=Buffer.from('DEADBYTE-R41-FINAL-CLOSURE-V1\0','utf8');
14
+
15
+ function parseManifest(bytes,label){
16
+ const entries=new Map();
17
+ for(const [index,line] of bytes.toString('utf8').split(/\r?\n/).entries()){
18
+ if(!line)continue;
19
+ const match=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);insist(match,`${label} line ${index+1} malformed`);
20
+ const rel=match[2];
21
+ insist(!path.posix.isAbsolute(rel)&&!rel.split('/').includes('..')&&!entries.has(rel),`unsafe/duplicate ${label} path ${rel}`);
22
+ entries.set(rel,match[1]);
23
+ }
24
+ return entries;
25
+ }
26
+ async function walk(dir,rel=''){
27
+ const out=[];
28
+ for(const name of (await readdir(path.join(dir,...(rel?rel.split('/'):[])))).sort((a,b)=>a.localeCompare(b,'en'))){
29
+ const child=rel?`${rel}/${name}`:name;
30
+ const full=path.join(dir,...child.split('/'));
31
+ const st=await lstat(full);
32
+ insist(!st.isSymbolicLink(),`parent tree symlink forbidden: ${child}`);
33
+ if(st.isDirectory())out.push(...await walk(dir,child));
34
+ else{insist(st.isFile(),`parent tree non-file forbidden: ${child}`);out.push(child);}
35
+ }
36
+ return out;
37
+ }
38
+ async function verifyTree(dir,manifestName,expectedManifest,{exact}){
39
+ const bytes=await readFile(path.join(dir,manifestName));
40
+ insist(sha256(bytes)===expectedManifest,`${manifestName} digest mismatch: ${dir}`);
41
+ const entries=parseManifest(bytes,manifestName);
42
+ if(exact){
43
+ const actual=(await walk(dir)).filter(rel=>rel!==manifestName);
44
+ insist(actual.length===entries.size&&actual.every(rel=>entries.has(rel)),`${manifestName} exact tree mismatch: ${dir}`);
45
+ }
46
+ for(const [rel,digest] of entries){
47
+ insist(sha256(await readFile(path.join(dir,...rel.split('/'))))===digest,`${manifestName} file hash mismatch: ${dir} :: ${rel}`);
48
+ }
49
+ return entries.size;
50
+ }
51
+ function verifySignedClosure(envelope,record,publicPem){
52
+ insist(envelope?.body?.schema==='deadbyte.r41-final-closure.v1','R41 parent closure schema mismatch');
53
+ insist(envelope.body.version==='0.13.0','R41 parent closure version mismatch');
54
+ const pointer=envelope.body.active_pointer;
55
+ insist(pointer?.release_id===record.parent_release_id&&pointer?.manifest_sha256===record.parent_manifest_sha256,'R41 parent closure release identity mismatch');
56
+ insist(pointer?.generation_manifest_sha256===record.parent_generation_manifest_sha256,'R41 parent closure generation identity mismatch');
57
+ const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
58
+ insist(sha256(bodyBytes)===envelope.body_sha256,'R41 parent closure body hash mismatch');
59
+ const {closure_sha256,...unsigned}=envelope;
60
+ insist(sha256(Buffer.from(canonicalJson(unsigned),'utf8'))===closure_sha256,'R41 parent closure envelope hash mismatch');
61
+ const key=createPublicKey(publicPem);insist(key.asymmetricKeyType==='ed25519','R41 parent closure key must be Ed25519');
62
+ const der=Buffer.from(key.export({type:'spki',format:'der'}));
63
+ const keyId=sha256(der);
64
+ insist(envelope.attestation?.algorithm==='ed25519'&&envelope.attestation?.domain==='DEADBYTE-R41-FINAL-CLOSURE-V1','R41 parent closure attestation contract mismatch');
65
+ insist(envelope.attestation.key_id===keyId,'R41 parent closure signer mismatch');
66
+ insist(cryptoVerify(null,Buffer.concat([CLOSURE_DOMAIN,bodyBytes]),key,Buffer.from(envelope.attestation.signature_base64,'base64')),'R41 parent closure signature invalid');
67
+ return pointer;
68
+ }
69
+
70
+ export async function verifyR42Parent(file=path.join(root,'R42-PARENT.json')){
71
+ const record=JSON.parse(await readFile(path.resolve(file),'utf8'));
72
+ insist(record.schema==='deadbyte.r42-parent.v1','R42 parent schema mismatch');
73
+ insist(record.parent_version==='0.13.0'&&record.parent_release_id==='v0.13.0-3255be10de4daf08','R42 parent identity mismatch');
74
+ insist(record.parent_manifest_sha256==='3255be10de4daf0837d67d1ab1913b785b8b8215bc7405823c2ff46968c4b98f','R42 parent manifest mismatch');
75
+ insist(record.parent_generation_manifest_sha256==='143d2d98b675b4ed68cce2bfb1cda76ec22633035b49746e445140fee58b25cd','R42 parent generation mismatch');
76
+ const [sourceFiles,releaseFiles]=await Promise.all([
77
+ verifyTree(path.resolve(record.parent_source_root),'MANIFEST.SHA256',record.parent_manifest_sha256,{exact:false}),
78
+ verifyTree(path.resolve(record.parent_release_root),'MANIFEST.SHA256',record.parent_manifest_sha256,{exact:true})
79
+ ]);
80
+ insist(sourceFiles===record.parent_manifest_file_count&&releaseFiles===record.parent_manifest_file_count,'R42 parent file count mismatch');
81
+
82
+ const closureBytes=await readFile(path.resolve(record.parent_final_closure_path));
83
+ insist(sha256(closureBytes)===record.parent_final_closure_sha256,'R41 parent final closure raw hash mismatch');
84
+ const closure=JSON.parse(closureBytes.toString('utf8').replace(/^\uFEFF/,''));
85
+ const pointer=verifySignedClosure(closure,record,await readFile(path.join(stateRoot,'trust','ed25519-public.pem')));
86
+ const generationFiles=await verifyTree(path.resolve(pointer.generation_root),'GENERATION.SHA256',record.parent_generation_manifest_sha256,{exact:true});
87
+
88
+ const tarball=await readFile(path.resolve(record.parent_public_tarball_path));
89
+ insist(sha256(tarball)===record.parent_public_tarball_sha256,'R41 parent public tarball SHA-256 mismatch');
90
+ insist(sha1(tarball)===record.parent_public_tarball_sha1,'R41 parent public tarball SHA-1 mismatch');
91
+
92
+ return {status:'passed',parent_release_id:record.parent_release_id,parent_manifest_sha256:record.parent_manifest_sha256,
93
+ parent_generation_manifest_sha256:record.parent_generation_manifest_sha256,source_files:sourceFiles,release_files:releaseFiles,
94
+ generation_files:generationFiles,parent_final_closure_sha256:record.parent_final_closure_sha256,
95
+ parent_public_tarball_sha256:record.parent_public_tarball_sha256,parent_public_tarball_sha1:record.parent_public_tarball_sha1};
96
+ }
97
+
98
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
99
+ verifyR42Parent(process.argv[2]).then(value=>console.log(JSON.stringify(value,null,2)))
100
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
101
+ }
@@ -0,0 +1,53 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here=path.dirname(fileURLToPath(import.meta.url));
8
+ const root=path.resolve(here,'..');
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const marker='DEADBYTE V0.13.0 / R41 WINDOWS BATCH GATE: PASS';
11
+ function insist(ok,message){if(!ok) throw new Error(message);}
12
+
13
+ export async function writeWindowsGateEvidence({
14
+ candidateRoot=root,
15
+ stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
16
+ clock=()=>new Date()
17
+ }={}){
18
+ const candidate=path.resolve(candidateRoot);
19
+ const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
20
+ const manifestSha256=sha256(manifestBytes);
21
+ const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
22
+ insist(pkg.version==='0.13.0',`Windows gate evidence requires V0.13.0, found ${pkg.version}`);
23
+ const completed=clock();
24
+ insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
25
+ const evidence={
26
+ schema:'deadbyte.r41-windows-gate.v1',status:'passed',exit_code:0,version:'0.13.0',
27
+ marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256
28
+ };
29
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
30
+ const dir=path.join(path.resolve(stateRoot),'evidence','release');
31
+ await mkdir(dir,{recursive:true});
32
+ const file=path.join(dir,`r41-windows-gate-${manifestSha256.slice(0,16)}.json`);
33
+ try{
34
+ const handle=await open(file,'wx',0o600);
35
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
36
+ return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256};
37
+ }catch(error){
38
+ if(error?.code!=='EEXIST') throw error;
39
+ const existingBytes=await readFile(file);
40
+ const existing=JSON.parse(existingBytes.toString('utf8'));
41
+ insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing Windows gate evidence invalid');
42
+ insist(existing.version==='0.13.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
43
+ return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256};
44
+ }
45
+ }
46
+
47
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
48
+ if(invoked){
49
+ writeWindowsGateEvidence()
50
+ .then(result=>console.log(JSON.stringify(result)))
51
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
52
+ }
53
+