deadbyte-mcp 0.15.74 → 0.15.76

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 (109) hide show
  1. package/MANIFEST.SHA256 +108 -42
  2. package/PROVENANCE.json +20 -22
  3. package/README.txt +23 -18
  4. package/RELEASE.json +69 -18
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +32 -2
  7. package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
  8. package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
  9. package/bin/deadbyte-desktop-uia-r199.exe +0 -0
  10. package/bin/deadbyte-desktop-uia-r199.obj +0 -0
  11. package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
  12. package/bin/deadbyte-fs-read-r198.exe +0 -0
  13. package/bin/deadbyte-fs-read-r198.obj +0 -0
  14. package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
  15. package/bridge/package.json +1 -1
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R184-package-lock.json +2 -8
  18. package/native/deadbyte-desktop-uia-r199.cpp +345 -0
  19. package/native/deadbyte-fs-read-r197.cpp +174 -0
  20. package/native/deadbyte-fs-read-r198.cpp +181 -0
  21. package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
  22. package/package.json +5 -2
  23. package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
  24. package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
  25. package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
  26. package/scripts/build-native-fs-read-r197.ps1 +28 -0
  27. package/scripts/build-native-fs-read-r198.ps1 +28 -0
  28. package/scripts/build-native-root-probe-r197.ps1 +28 -0
  29. package/scripts/gate-windows-r195.ps1 +114 -0
  30. package/scripts/gate-windows-r206.ps1 +116 -0
  31. package/scripts/gate-windows-r207.ps1 +104 -0
  32. package/scripts/init-desktop-policy-r199.ps1 +85 -0
  33. package/scripts/npm-release-r48.mjs +27 -0
  34. package/scripts/npm-release-readiness-r195.mjs +72 -0
  35. package/scripts/npm-release-readiness-r206.mjs +76 -0
  36. package/scripts/npm-release-readiness-r207.mjs +76 -0
  37. package/scripts/r193-owner-request-live.mjs +8 -7
  38. package/scripts/r193-owner-uac-broker.mjs +6 -5
  39. package/scripts/r205-candidate-selection.mjs +22 -0
  40. package/src/candidate-selection-evidence-r206.mjs +71 -0
  41. package/src/candidate-selection-r205.mjs +96 -0
  42. package/src/desktop-postcondition-r196.mjs +30 -0
  43. package/src/desktop-runtime-r40.mjs +44 -7
  44. package/src/desktop-uia-identity-r199.mjs +51 -0
  45. package/src/desktop-uia-r40.mjs +9 -1
  46. package/src/machine-fs-handle-read-r196.mjs +43 -0
  47. package/src/machine-fs-native-read-r197.mjs +73 -0
  48. package/src/machine-fs-native-read-r198.mjs +76 -0
  49. package/src/machine-fs-read-worker.mjs +5 -25
  50. package/src/machine-fs.mjs +31 -16
  51. package/src/r195-canonical-owner-observation.mjs +15 -0
  52. package/src/r207-owner-enrollment-io.mjs +30 -0
  53. package/src/remote-destination-journal-r202.mjs +120 -0
  54. package/src/remote-device-r44.mjs +89 -14
  55. package/src/remote-payload-boundary-r201.mjs +71 -0
  56. package/src/remote-terminal-evidence-r200.mjs +60 -0
  57. package/src/version.mjs +1 -1
  58. package/test/desktop-runtime-r40.test.mjs +111 -0
  59. package/test/desktop-uia-r40.test.mjs +15 -0
  60. package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
  61. package/test/fixtures/r203-journal-writer.mjs +6 -0
  62. package/test/process-policy-refresh.test.mjs +4 -4
  63. package/test/r130-release-security-contract.test.mjs +8 -8
  64. package/test/r132-release-identity.test.mjs +7 -7
  65. package/test/r153-prepublish-identity.test.mjs +1 -1
  66. package/test/r160-release-contract.test.mjs +3 -3
  67. package/test/r165-live-cli-skew.test.mjs +1 -1
  68. package/test/r167-release-contract.test.mjs +11 -11
  69. package/test/r168-release-contract.test.mjs +4 -4
  70. package/test/r172-release-contract.test.mjs +6 -6
  71. package/test/r173-release-contract.test.mjs +16 -16
  72. package/test/r179-release-contract.test.mjs +12 -12
  73. package/test/r190-release-contract.test.mjs +10 -10
  74. package/test/r191-release-contract.test.mjs +12 -12
  75. package/test/r192-release-identity.test.mjs +9 -9
  76. package/test/r193-release-identity.test.mjs +24 -22
  77. package/test/r193-release-readiness.test.mjs +2 -2
  78. package/test/r193-windows-gate.test.mjs +10 -10
  79. package/test/r194-release-identity.test.mjs +19 -15
  80. package/test/r194-release-readiness.test.mjs +3 -3
  81. package/test/r194-windows-gate.test.mjs +9 -8
  82. package/test/r195-canonical-owner-observation.test.mjs +32 -0
  83. package/test/r195-release-readiness.test.mjs +22 -0
  84. package/test/r196-desktop-postcondition.test.mjs +53 -0
  85. package/test/r196-handle-bound-read.test.mjs +99 -0
  86. package/test/r196-remote-outcome.test.mjs +94 -0
  87. package/test/r197-namespace-race.test.mjs +41 -0
  88. package/test/r197-native-dispatch-race.test.mjs +49 -0
  89. package/test/r197-native-file-read.test.mjs +60 -0
  90. package/test/r197-native-root-open.test.mjs +61 -0
  91. package/test/r198-root-identity.test.mjs +88 -0
  92. package/test/r199-native-uia-worker.test.mjs +28 -0
  93. package/test/r199-uia-identity.test.mjs +43 -0
  94. package/test/r200-remote-reconcile.test.mjs +106 -0
  95. package/test/r201-payload-boundary.test.mjs +78 -0
  96. package/test/r201-payload-fuzz.test.mjs +37 -0
  97. package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
  98. package/test/r202-destination-journal.test.mjs +92 -0
  99. package/test/r203-atomic-journal.test.mjs +86 -0
  100. package/test/r205-candidate-selection-cli.test.mjs +31 -0
  101. package/test/r205-candidate-selection.test.mjs +83 -0
  102. package/test/r206-candidate-evidence.test.mjs +64 -0
  103. package/test/r206-release-contract.test.mjs +42 -0
  104. package/test/r206-release-readiness.test.mjs +23 -0
  105. package/test/r207-owner-enrollment-io.test.mjs +37 -0
  106. package/test/r207-release-contract.test.mjs +51 -0
  107. package/test/r207-release-readiness.test.mjs +23 -0
  108. package/test/r59-tool-surface-compat.test.mjs +1 -1
  109. package/test/r64-machine-fs-large-read.test.mjs +26 -12
@@ -0,0 +1,106 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createRemoteDevicePlaneR44} from '../src/remote-device-r44.mjs';
8
+ import {canonicalJson} from '../src/canonical-json.mjs';
9
+ import {REMOTE_TERMINAL_DOMAIN,REMOTE_TERMINAL_EVIDENCE_SCHEMA} from '../src/remote-terminal-evidence-r200.mjs';
10
+
11
+ const sha=b=>createHash('sha256').update(b).digest('hex');
12
+ async function fixture(t){
13
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r200-terminal-'));
14
+ t.after(()=>rm(dir,{recursive:true,force:true}));
15
+ const keys=generateKeyPairSync('ed25519');
16
+ const der=Buffer.from(keys.publicKey.export({type:'spki',format:'der'}));
17
+ const owner={id:sha(der),spki:der.toString('base64'),privateKey:keys.privateKey};
18
+ let requestCalls=0,lookupCalls=0,latestProof=null;
19
+ const relay={
20
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
21
+ async request(){requestCalls++;return null;},
22
+ async lookupTerminal(){lookupCalls++;return latestProof;}
23
+ };
24
+ const makePlane=()=>createRemoteDevicePlaneR44({stateDir:dir,relay,authorizeCapability:async()=>true,
25
+ relayTimeoutMs:1000,probeRetries:0,reachableTtlMs:10000});
26
+ const plane=await makePlane();
27
+ await plane.pair({publicKeySpkiBase64:owner.spki,displayName:'R200 fixture'});
28
+ await plane.reportOnline({deviceId:owner.id,epoch:1,connectionId:'r200-virtual'});
29
+ await plane.probe({deviceId:owner.id,epoch:1});
30
+ const req={deviceId:owner.id,epoch:1,capabilityId:'machine.fs.write',
31
+ payload:{path:'file.bin',data:'one'},requestId:'a'.repeat(64)};
32
+ const initial=await plane.dispatch(req);
33
+ assert.equal(initial.effect_state,'unknown');
34
+ const proofFor=(terminal,bodyOverrides={})=>{
35
+ const evidence={schema:REMOTE_TERMINAL_EVIDENCE_SCHEMA,
36
+ device_id:owner.id,epoch:1,request_id:req.requestId,
37
+ request_sha256:initial.request_sha256,capability_id:req.capabilityId,
38
+ terminal,...bodyOverrides};
39
+ const signature=sign(null,Buffer.concat([REMOTE_TERMINAL_DOMAIN,
40
+ Buffer.from(canonicalJson(evidence),'utf8')]),owner.privateKey).toString('base64');
41
+ return {evidence,attestation:{algorithm:'ed25519',key_id:owner.id,signature_base64:signature}};
42
+ };
43
+ return {plane,makePlane,owner,req,initial,relay,proofFor,
44
+ setProof:p=>latestProof=p,
45
+ calls:()=>({requestCalls,lookupCalls})};
46
+ }
47
+ test('R200 signed terminal reconciles a lost ACK without sending mutation again',async t=>{
48
+ const f=await fixture(t);
49
+ f.setProof(f.proofFor({status:'completed',result_sha256:'b'.repeat(64)}));
50
+ const r=await f.plane.reconcile({requestId:f.req.requestId});
51
+ assert.equal(r.state,'completed');assert.equal(r.effect_state,'confirmed');
52
+ assert.equal(r.transport_state,'reconciled');
53
+ assert.equal(r.reconciliation_state,'signed_terminal');
54
+ assert.match(r.terminal_evidence_sha256,/^[0-9a-f]{64}$/);
55
+ assert.deepEqual(f.calls(),{requestCalls:1,lookupCalls:1});
56
+ const restarted=await f.makePlane();
57
+ const again=await restarted.dispatch(f.req);
58
+ assert.equal(again.replay,true);assert.equal(again.effect_state,'confirmed');
59
+ assert.deepEqual(f.calls(),{requestCalls:1,lookupCalls:1},'no duplicate remote mutation');
60
+ });
61
+ test('R200 unavailable signed journal entry remains UNKNOWN and cannot be called success',async t=>{
62
+ const f=await fixture(t);
63
+ const r=await f.plane.reconcile({requestId:f.req.requestId});
64
+ assert.equal(r.reconciliation,'unresolved');
65
+ assert.equal(r.effect_state,'unknown');
66
+ assert.equal(f.calls().requestCalls,1);
67
+ const snap=(await f.plane.list()).requests[f.req.requestId];
68
+ assert.equal(snap.effect_state,'unknown');assert.equal(snap.transport_state,'ack_missing');
69
+ });
70
+ test('R200 invalid Ed25519 and mismatched request hashes never alter original outcome',async t=>{
71
+ const f=await fixture(t);
72
+ const proper=f.proofFor({status:'completed',result_sha256:'d'.repeat(64)});
73
+ proper.evidence.terminal.result_sha256='e'.repeat(64);
74
+ f.setProof(proper);
75
+ await assert.rejects(f.plane.reconcile({requestId:f.req.requestId}),/signature invalid/);
76
+ const wrong=f.proofFor({status:'completed',result_sha256:'d'.repeat(64)},
77
+ {request_sha256:'f'.repeat(64)});
78
+ f.setProof(wrong);
79
+ await assert.rejects(f.plane.reconcile({requestId:f.req.requestId}),/request hash mismatch/);
80
+ assert.equal((await f.plane.list()).requests[f.req.requestId].effect_state,'unknown');
81
+ assert.equal(f.calls().requestCalls,1);
82
+ });
83
+ test('R200 signed failure with unresolved side effects stays UNKNOWN',async t=>{
84
+ const f=await fixture(t);
85
+ f.setProof(f.proofFor({status:'failed',error_code:'process_exit',effect_state:'unknown'}));
86
+ const r=await f.plane.reconcile({requestId:f.req.requestId});
87
+ assert.equal(r.state,'failed');assert.equal(r.effect_state,'unknown');
88
+ assert.equal(r.reconciliation_state,'signed_terminal_effect_unknown');
89
+ assert.equal(r.transport_state,'reconciled');
90
+ });
91
+ test('R200 signed not_started failure is a terminal failed effect',async t=>{
92
+ const f=await fixture(t);
93
+ f.setProof(f.proofFor({status:'failed',error_code:'rejected_before_dispatch',effect_state:'not_started'}));
94
+ const r=await f.plane.reconcile({requestId:f.req.requestId});
95
+ assert.equal(r.effect_state,'failed');
96
+ assert.equal(r.reconciliation_state,'signed_terminal');
97
+ assert.equal(r.error_code,'rejected_before_dispatch');
98
+ });
99
+ test('R200 malformed terminal envelope and unexpected fields fail closed',async t=>{
100
+ const f=await fixture(t);
101
+ const proof=f.proofFor({status:'completed',result_sha256:'c'.repeat(64)});
102
+ proof.evidence.untrusted_internal=true;
103
+ f.setProof(proof);
104
+ await assert.rejects(f.plane.reconcile({requestId:f.req.requestId}),/unexpected or missing fields/);
105
+ assert.equal((await f.plane.list()).requests[f.req.requestId].effect_state,'unknown');
106
+ });
@@ -0,0 +1,78 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,createHash} from 'node:crypto';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createRemoteDevicePlaneR44,payloadHash} from '../src/remote-device-r44.mjs';
8
+
9
+ const hash=b=>createHash('sha256').update(b).digest('hex');
10
+ const context={deviceId:'a'.repeat(64),epoch:1,capabilityId:'machine.fs.write'};
11
+ const digest=payload=>payloadHash({...context,payload});
12
+
13
+ test('R201 remote canonical payload rejects non-finite numbers rather than aliasing null',()=>{
14
+ assert.throws(()=>digest({n:NaN}),/remote payload/i);
15
+ assert.throws(()=>digest({n:Infinity}),/remote payload/i);
16
+ assert.throws(()=>digest({n:-Infinity}),/remote payload/i);
17
+ assert.notEqual(digest({n:null}),digest({n:0}));
18
+ });
19
+ test('R201 remote canonical payload rejects undefined, sparse arrays and unsupported values',()=>{
20
+ assert.throws(()=>digest({n:undefined}),/remote payload/i);
21
+ assert.throws(()=>digest({n:()=>{}}),/remote payload/i);
22
+ assert.throws(()=>digest({n:Symbol('x')}),/remote payload/i);
23
+ assert.throws(()=>digest({n:1n}),/remote payload/i);
24
+ assert.throws(()=>digest({a:[,]}),/remote payload/i);
25
+ assert.throws(()=>digest({a:new Date(0)}),/remote payload/i);
26
+ assert.throws(()=>digest({a:new Map()}),/remote payload/i);
27
+ });
28
+ test('R201 remote canonical payload rejects getters, toJSON hooks, cycles before evaluation',()=>{
29
+ let getters=0,serializers=0;
30
+ const tricky={};Object.defineProperty(tricky,'x',{enumerable:true,get(){getters++;return 1;}});
31
+ assert.throws(()=>digest(tricky),/remote payload/i);
32
+ assert.equal(getters,0,'untrusted getter ran inside remote request hashing');
33
+ const hook={toJSON(){serializers++;return {x:1};}};
34
+ assert.throws(()=>digest({hook}),/remote payload/i);
35
+ assert.equal(serializers,0,'untrusted toJSON ran inside remote request hashing');
36
+ const cycle={};cycle.loop=cycle;
37
+ assert.throws(()=>digest(cycle),/remote payload/i);
38
+ });
39
+ test('R201 remote canonical payload enforces depth, byte and node budgets',()=>{
40
+ let deeply={end:true};for(let i=0;i<45;i++)deeply={child:deeply};
41
+ assert.throws(()=>digest(deeply),/remote payload/i);
42
+ assert.throws(()=>digest({string:'z'.repeat(1024*1024+1)}),/remote payload/i);
43
+ assert.throws(()=>digest({array:Array.from({length:4100},(_,i)=>i)}),/remote payload/i);
44
+ });
45
+ test('R201 plain JSON payload stays order-stable and distinguishes nested contents',()=>{
46
+ assert.equal(digest({a:1,b:{x:true,y:[1,null,'ok']}}),digest({b:{y:[1,null,'ok'],x:true},a:1}));
47
+ assert.notEqual(digest({a:1}),digest({a:2}));
48
+ });
49
+ test('R201 caller mutation during async authorization cannot alter payload sent to relay',async t=>{
50
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r201-payload-'));
51
+ t.after(()=>rm(dir,{recursive:true,force:true}));
52
+ const {publicKey}=generateKeyPairSync('ed25519');
53
+ const der=Buffer.from(publicKey.export({type:'spki',format:'der'}));
54
+ const id=hash(der),seen=[];
55
+ let entered,release;
56
+ const atAuth=new Promise(resolve=>entered=resolve);
57
+ const gate=new Promise(resolve=>release=resolve);
58
+ const relay={
59
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
60
+ async request(x){seen.push(structuredClone(x));return {ack:'accepted',request_id:x.request_id,terminal:{status:'completed',result_sha256:'b'.repeat(64)}};}
61
+ };
62
+ const plane=await createRemoteDevicePlaneR44({stateDir:dir,relay,authorizeCapability:async()=>{entered();await gate;return true;},probeRetries:0,reachableTtlMs:10000});
63
+ await plane.pair({publicKeySpkiBase64:der.toString('base64'),displayName:'R201 fake'});
64
+ await plane.reportOnline({deviceId:id,epoch:1,connectionId:'fixture'});
65
+ await plane.probe({deviceId:id,epoch:1});
66
+ const payload={path:'allowed.txt',nested:{value:'old'}};
67
+ const req={deviceId:id,epoch:1,capabilityId:'machine.fs.write',payload,requestId:'c'.repeat(64)};
68
+ const expectedHash=payloadHash({deviceId:id,epoch:1,capabilityId:'machine.fs.write',payload});
69
+ const resultPromise=plane.dispatch(req);
70
+ await atAuth;
71
+ payload.path='other.txt';payload.nested.value='new';
72
+ release();
73
+ const result=await resultPromise;
74
+ assert.equal(result.request_sha256,expectedHash);
75
+ assert.equal(seen.length,1);
76
+ assert.deepEqual(seen[0].payload,{path:'allowed.txt',nested:{value:'old'}});
77
+ assert.equal(result.state,'completed');
78
+ });
@@ -0,0 +1,37 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {payloadHash} from '../src/remote-device-r44.mjs';
4
+
5
+ const ctx={deviceId:'b'.repeat(64),epoch:1,capabilityId:'coding.file_write'};
6
+ const hash=payload=>payloadHash({...ctx,payload});
7
+ test('R201 symbol keys, negative zero and unsafe integers are rejected at the typed boundary',()=>{
8
+ const withSymbol={x:1};withSymbol[Symbol('shadow')]=42;
9
+ assert.throws(()=>hash(withSymbol),/remote payload/i);
10
+ assert.throws(()=>hash({offset:-0}),/remote payload/i);
11
+ assert.throws(()=>hash({offset:Number.MAX_SAFE_INTEGER+1}),/remote payload/i);
12
+ });
13
+ test('R201 deterministic malformed-boundary corpus is fully rejected',()=>{
14
+ const cases=[
15
+ Object.create({inherited:true}),
16
+ {x:NaN},{x:Infinity},{x:undefined},{x:()=>0},{x:1n},{x:Symbol('x')},
17
+ {x:new Date(0)},{x:new Uint8Array([1])},{x:new Map([[1,1]])},
18
+ {x:[]},{x:[,1]},{x:[1,,2]},{x:Array(4097).fill(1)},
19
+ {x:{constructor:0}},{x:{prototype:0}},{x:{toJSON:0}},
20
+ {x:{get y(){throw Error('must not execute')}}}
21
+ ];
22
+ // A dense array is legal; all other fixture values must be invalid.
23
+ for(let i=0;i<cases.length;i++){
24
+ if(i===10)continue;
25
+ assert.throws(()=>hash(cases[i]),/remote payload/i,'case '+i);
26
+ }
27
+ });
28
+ test('R201 seeded valid JSON corpus is deterministic independent of key ordering',()=>{
29
+ let seed=0x6db201;const rand=()=>{seed^=seed<<13;seed^=seed>>>17;seed^=seed<<5;return seed>>>0;};
30
+ for(let i=0;i<200;i++){
31
+ const n=rand()%100000,flag=(rand()&1)!==0,word='v'+(rand()%9999);
32
+ const a={left:n,right:{enabled:flag,name:word,items:[0,1,null,'ok']}};
33
+ const b={right:{items:[0,1,null,'ok'],name:word,enabled:flag},left:n};
34
+ assert.equal(hash(a),hash(b),'semantic hash mismatch case '+i);
35
+ assert.notEqual(hash(a),hash({...a,left:n+1}),'hash collision case '+i);
36
+ }
37
+ });
@@ -0,0 +1,120 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {registerDesktopTools} from '../src/desktop-mcp-tools-r40.mjs';
4
+ import {registerMachineFsTools} from '../src/machine-fs-mcp-tools.mjs';
5
+ import {registerProcessTools} from '../src/process-mcp-tools.mjs';
6
+
7
+ const H=c=>c.repeat(64);
8
+ const frame=H('a'),intent=H('b'),session=H('c'),digest=H('d');
9
+ class FakeServer{
10
+ constructor(){this.tools=new Map();}
11
+ registerTool(name,definition,handler){
12
+ assert.equal(this.tools.has(name),false,'duplicate tool '+name);
13
+ this.tools.set(name,{schema:definition.inputSchema,handler});
14
+ }
15
+ }
16
+ const server=new FakeServer();
17
+ const runtime=new Proxy({}, {get(_,field){return ()=>{throw new Error('R201_PROVIDER_MUST_NOT_BE_CALLED '+String(field));};}});
18
+ registerDesktopTools(server,{runtime});
19
+ registerMachineFsTools(server,{runtime});
20
+ registerProcessTools(server,{runtime});
21
+
22
+ const fixtures={
23
+ desktop_status:{},desktop_observe:{preview:false},
24
+ desktop_wait:{baseline_frame_id:frame,until:'frame_changed'},
25
+ desktop_target:{frame_id:frame,uia_query:{kind:'name_exact',value:'Save',ordinal:0,max_results:8}},
26
+ desktop_action:{intent_id:intent,kind:'click_primary',expected_frame_id:frame,point:{x:10,y:20}},
27
+ machine_fs_stat:{root_id:'projects',path:'.'},
28
+ machine_fs_list:{root_id:'projects',path:'.'},
29
+ machine_fs_read:{root_id:'projects',path:'test.bin',offset:0,max_bytes:32},
30
+ machine_fs_write:{root_id:'projects',path:'test.bin',content:'x',expected_sha256:'absent'},
31
+ machine_fs_edit_exact:{root_id:'projects',path:'test.bin',old_string:'x',new_string:'y',expected_sha256:digest},
32
+ machine_fs_mkdir:{root_id:'projects',path:'tmp-dir'},
33
+ machine_fs_move:{root_id:'projects',source_path:'old',destination_path:'new',expected_source_identity:digest},
34
+ machine_fs_delete:{root_id:'projects',path:'test.bin',expected_source_identity:digest},
35
+ process_session_start:{profile_id:'node',root_id:'projects'},
36
+ process_session_read:{session_id:session},
37
+ process_session_write:{session_id:session,input:'x'},
38
+ process_session_status:{session_id:session},
39
+ process_session_list:{},
40
+ process_session_cancel:{session_id:session},
41
+ process_session_terminate:{session_id:session},
42
+ process_session_action:{session_id:session,action_id:'ping'}
43
+ };
44
+ const reserved=[
45
+ '_internal','__internal','__proto__','constructor','prototype',
46
+ 'authority','grant','grant_token','policy_override','allow_unsafe',
47
+ 'exec','executable','shell','command','pid','process_id','host_root_id',
48
+ 'endpoint','network','capability_override','admin','on_behalf_of','proof'
49
+ ];
50
+ function polluted(base,key,value){
51
+ const obj=Object.create(null);
52
+ for(const [k,v] of Object.entries(base))obj[k]=structuredClone(v);
53
+ obj[key]=value;
54
+ return obj;
55
+ }
56
+ test('R201 entire native tool catalog stays 21 strict schemas with valid defaults',()=>{
57
+ assert.deepEqual([...server.tools.keys()].sort(),Object.keys(fixtures).sort());
58
+ for(const [name,value] of Object.entries(fixtures)){
59
+ const parsed=server.tools.get(name).schema.safeParse(value);
60
+ assert.equal(parsed.success,true,name+' valid input was incorrectly rejected: '+JSON.stringify(parsed.error?.issues));
61
+ }
62
+ });
63
+ test('R201 deterministic reserved-field forgery corpus rejects before provider dispatch',()=>{
64
+ let rejected=0;
65
+ for(const [name,{schema}] of server.tools){
66
+ for(const k of reserved){
67
+ const forged=polluted(fixtures[name],k,k==='__proto__'?{isAdmin:true}:true);
68
+ const result=schema.safeParse(forged);
69
+ if(k==='__proto__'&&result.success){
70
+ // Some Zod versions safely discard this hostile own property rather
71
+ // than rejecting it. Sanitization must never leak to provider output.
72
+ assert.equal(Object.hasOwn(result.data,'__proto__'),false,name+' leaked __proto__');
73
+ assert.equal(Object.getPrototypeOf(result.data),Object.prototype,name+' changed parsed prototype');
74
+ assert.equal(({}).isAdmin,undefined,'global prototype was polluted');
75
+ }else{
76
+ assert.equal(result.success,false,name+' accepted forged key '+k);
77
+ }
78
+ rejected++;
79
+ }
80
+ }
81
+ assert.equal(rejected,21*reserved.length);
82
+ });
83
+ test('R201 malformed type corpus and unknown nested fields fail closed',()=>{
84
+ const invalidValues=[null,undefined,false,true,0,-1,1.5,'',[],[1],{},Object.create(null),Number.NaN,Infinity,-Infinity];
85
+ let covered=0;
86
+ for(const [name,{schema}] of server.tools){
87
+ const base=fixtures[name];
88
+ for(const field of Object.keys(base)){
89
+ for(const value of invalidValues){
90
+ const altered={...base,[field]:value};
91
+ const result=schema.safeParse(altered);
92
+ if(value===undefined || field==='preview')continue; // some schema defaults/boolean are intentional
93
+ if(result.success)continue; // schema-specific legitimate optionality, not a security failure
94
+ covered++;
95
+ }
96
+ }
97
+ }
98
+ assert.ok(covered>=400,'R201 malformed-input corpus insufficient '+covered);
99
+ const target=server.tools.get('desktop_target').schema;
100
+ assert.equal(target.safeParse({frame_id:frame,uia_query:{kind:'name_exact',value:'Save',__internal:true}}).success,false);
101
+ assert.equal(target.safeParse({frame_id:frame,rect:{left:1,top:1,width:10,height:10,admin:true},source:'visual',confidence:1}).success,false);
102
+ const action=server.tools.get('desktop_action').schema;
103
+ assert.equal(action.safeParse({intent_id:intent,kind:'click_primary',expected_frame_id:frame,point:{x:1,y:2,pid:123}}).success,false);
104
+ assert.equal(action.safeParse({intent_id:intent,kind:'text_input',expected_frame_id:frame,text:'hello\0world'}).success,false);
105
+ });
106
+ test('R201 incomplete or wrong authority preconditions cannot be normalized into valid mutations',()=>{
107
+ const checks=[
108
+ ['machine_fs_write',{root_id:'projects',path:'x',content:'y'}],
109
+ ['machine_fs_delete',{root_id:'projects',path:'x'}],
110
+ ['machine_fs_move',{root_id:'projects',source_path:'a',destination_path:'b'}],
111
+ ['machine_fs_edit_exact',{root_id:'projects',path:'x',expected_sha256:'absent',old_string:'a',new_string:'b'}],
112
+ ['desktop_action',{intent_id:intent,kind:'click_primary',expected_frame_id:frame}],
113
+ ['desktop_action',{intent_id:intent,kind:'text_input',expected_frame_id:frame,text:'x',target_id:session}],
114
+ ['process_session_terminate',{pid:123}],
115
+ ['process_session_start',{profile_id:'node',root_id:'projects',executable:'cmd.exe'}]
116
+ ];
117
+ for(const [name,input] of checks){
118
+ assert.equal(server.tools.get(name).schema.safeParse(input).success,false,name+' bypass accepted');
119
+ }
120
+ });
@@ -0,0 +1,92 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
4
+ import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {openRemoteDestinationJournalR202} from '../src/remote-destination-journal-r202.mjs';
8
+ import {REMOTE_TERMINAL_DOMAIN,REMOTE_TERMINAL_EVIDENCE_SCHEMA} from '../src/remote-terminal-evidence-r200.mjs';
9
+ import {canonicalJson} from '../src/canonical-json.mjs';
10
+ import {createRemoteDevicePlaneR44} from '../src/remote-device-r44.mjs';
11
+
12
+ const sha=b=>createHash('sha256').update(b).digest('hex');
13
+ async function fixture(t){
14
+ const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r202-journal-'));
15
+ t.after(()=>rm(base,{recursive:true,force:true}));
16
+ const keys=generateKeyPairSync('ed25519');
17
+ const spki=Buffer.from(keys.publicKey.export({type:'spki',format:'der'}));
18
+ const device={device_id:sha(spki),epoch:1,public_key_spki_base64:spki.toString('base64')};
19
+ const dir=path.join(base,'terminal-journal');
20
+ const journal=await openRemoteDestinationJournalR202({directory:dir,device});
21
+ const request={device_id:device.device_id,epoch:1,request_id:'9'.repeat(64),
22
+ request_sha256:'a'.repeat(64),capability_id:'machine.fs.write'};
23
+ const envelope=(terminal,overrides={})=>{
24
+ const evidence={schema:REMOTE_TERMINAL_EVIDENCE_SCHEMA,...request,terminal,...overrides};
25
+ const sig=sign(null,Buffer.concat([REMOTE_TERMINAL_DOMAIN,Buffer.from(canonicalJson(evidence),'utf8')]),keys.privateKey).toString('base64');
26
+ return {evidence,attestation:{algorithm:'ed25519',key_id:device.device_id,signature_base64:sig}};
27
+ };
28
+ return {base,dir,journal,device,keys,request,envelope};
29
+ }
30
+ const terminal=(hex='b')=>({status:'completed',result_sha256:hex.repeat(64)});
31
+ test('R202 stores only paired signed evidence; lookup survives restart and is idempotent',async t=>{
32
+ const f=await fixture(t),proof=f.envelope(terminal());
33
+ const r=await f.journal.commitSignedTerminal({request:f.request,envelope:proof});
34
+ assert.equal(r.status,'stored');assert.match(r.terminal_evidence_sha256,/^[a-f0-9]{64}$/);
35
+ const r2=await f.journal.commitSignedTerminal({request:f.request,envelope:proof});
36
+ assert.equal(r2.status,'existing');
37
+ const restarted=await openRemoteDestinationJournalR202({directory:f.dir,device:f.device});
38
+ assert.deepEqual(await restarted.lookupTerminal(f.request),proof);
39
+ });
40
+ test('R202 a second signed but conflicting terminal cannot overwrite committed evidence',async t=>{
41
+ const f=await fixture(t),one=f.envelope(terminal('b')),two=f.envelope(terminal('c'));
42
+ await f.journal.commitSignedTerminal({request:f.request,envelope:one});
43
+ await assert.rejects(f.journal.commitSignedTerminal({request:f.request,envelope:two}),/conflicting signed terminal/);
44
+ assert.deepEqual(await f.journal.lookupTerminal(f.request),one);
45
+ });
46
+ test('R202 unsigned, wrong request, epoch and corrupted stored evidence fail closed',async t=>{
47
+ const f=await fixture(t),proof=f.envelope(terminal());
48
+ const altered=structuredClone(proof);altered.evidence.terminal.result_sha256='d'.repeat(64);
49
+ await assert.rejects(f.journal.commitSignedTerminal({request:f.request,envelope:altered}),/signature invalid/);
50
+ await assert.rejects(f.journal.commitSignedTerminal({request:{...f.request,epoch:2},envelope:proof}),/request identity invalid/);
51
+ await f.journal.commitSignedTerminal({request:f.request,envelope:proof});
52
+ const file=path.join(f.dir,f.request.request_id+'.json');
53
+ await writeFile(file,JSON.stringify(altered)+'\n');
54
+ await assert.rejects(f.journal.lookupTerminal(f.request),/signature invalid/);
55
+ });
56
+ test('R202 missing or partially written journal remains UNKNOWN and denies bad selectors',async t=>{
57
+ const f=await fixture(t);
58
+ assert.equal(await f.journal.lookupTerminal(f.request),null);
59
+ await assert.rejects(f.journal.lookupTerminal({...f.request,request_id:'../evil'}),/request identity invalid/);
60
+ await assert.rejects(f.journal.lookupTerminal({...f.request,request_sha256:'f'.repeat(64),device_id:'b'.repeat(64)}),/lookup request identity invalid/);
61
+ const file=path.join(f.dir,f.request.request_id+'.json');
62
+ await writeFile(file,'');
63
+ await assert.rejects(f.journal.lookupTerminal(f.request),/record is not a bounded regular file/);
64
+ });
65
+ test('R202 local relay adapter recovers lost ACK with exactly one remote dispatch',async t=>{
66
+ const f=await fixture(t);
67
+ let calls=0;
68
+ const relay={
69
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
70
+ async request(){calls++;return null;},
71
+ lookupTerminal:args=>f.journal.lookupTerminal(args)
72
+ };
73
+ const stateDir=path.join(f.base,'sender');
74
+ const plane=await createRemoteDevicePlaneR44({stateDir,relay,authorizeCapability:async()=>true,
75
+ relayTimeoutMs:1000,probeRetries:0,reachableTtlMs:10000});
76
+ await plane.pair({publicKeySpkiBase64:f.device.public_key_spki_base64,displayName:'R202 simulated destination'});
77
+ await plane.reportOnline({deviceId:f.device.device_id,epoch:1,connectionId:'R202 local transport'});
78
+ await plane.probe({deviceId:f.device.device_id,epoch:1});
79
+ const args={deviceId:f.device.device_id,epoch:1,capabilityId:f.request.capability_id,
80
+ payload:{path:'readme.txt',content:'one'},requestId:f.request.request_id};
81
+ const first=await plane.dispatch(args);
82
+ assert.equal(first.effect_state,'unknown');
83
+ assert.equal(calls,1);
84
+ const proof=f.envelope(terminal(),{request_sha256:first.request_sha256});
85
+ await f.journal.commitSignedTerminal({request:{...f.request,request_sha256:first.request_sha256},envelope:proof});
86
+ const next=await plane.reconcile({requestId:args.requestId});
87
+ assert.equal(next.effect_state,'confirmed');assert.equal(next.state,'completed');
88
+ assert.equal(next.reconciliation_state,'signed_terminal');
89
+ assert.equal(calls,1,'lookup retried the mutation');
90
+ const replay=await plane.dispatch(args);
91
+ assert.equal(replay.replay,true);assert.equal(calls,1);
92
+ });
@@ -0,0 +1,86 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,readdir,rm,writeFile,stat} from 'node:fs/promises';
5
+ import {spawn} from 'node:child_process';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {openRemoteDestinationJournalR202} from '../src/remote-destination-journal-r202.mjs';
10
+ import {REMOTE_TERMINAL_DOMAIN,REMOTE_TERMINAL_EVIDENCE_SCHEMA} from '../src/remote-terminal-evidence-r200.mjs';
11
+ import {canonicalJson} from '../src/canonical-json.mjs';
12
+ const sha=b=>createHash('sha256').update(b).digest('hex');
13
+ const worker=fileURLToPath(new URL('./fixtures/r203-journal-writer.mjs',import.meta.url));
14
+ async function fixture(t){
15
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r203-journal-'));
16
+ t.after(()=>rm(dir,{recursive:true,force:true}));
17
+ const key=generateKeyPairSync('ed25519'),spki=Buffer.from(key.publicKey.export({format:'der',type:'spki'}));
18
+ const device={device_id:sha(spki),epoch:1,public_key_spki_base64:spki.toString('base64')};
19
+ const journalDir=path.join(dir,'journal');
20
+ const journal=await openRemoteDestinationJournalR202({directory:journalDir,device});
21
+ const request={device_id:device.device_id,epoch:1,request_id:'a'.repeat(64),request_sha256:'b'.repeat(64),capability_id:'machine.fs.write'};
22
+ const signEvidence=terminal=>{
23
+ const evidence={schema:REMOTE_TERMINAL_EVIDENCE_SCHEMA,...request,terminal};
24
+ const signature_base64=sign(null,Buffer.concat([REMOTE_TERMINAL_DOMAIN,Buffer.from(canonicalJson(evidence))]),key.privateKey).toString('base64');
25
+ return {evidence,attestation:{algorithm:'ed25519',key_id:device.device_id,signature_base64}};
26
+ };
27
+ return {dir,journalDir,journal,device,request,signEvidence};
28
+ }
29
+ const completed={status:'completed',result_sha256:'c'.repeat(64)};
30
+ test('R203 orphan unpublished temp never poisons absent destination terminal',async t=>{
31
+ const f=await fixture(t);
32
+ const garbage=path.join(f.journalDir,'.'+f.request.request_id+'.1234567890abcdef12345678.tmp');
33
+ await writeFile(garbage,'{broken');
34
+ assert.equal(await f.journal.lookupTerminal(f.request),null);
35
+ const record=f.signEvidence(completed);
36
+ assert.equal((await f.journal.commitSignedTerminal({request:f.request,envelope:record})).status,'stored');
37
+ assert.deepEqual(await f.journal.lookupTerminal(f.request),record);
38
+ assert.equal((await readFile(garbage,'utf8')),'{broken','the orphan is left to explicit cleanup, not silently deleted');
39
+ });
40
+ test('R203 8 independent Windows processes race for the same terminal; exactly one publication',async t=>{
41
+ const f=await fixture(t),envelope=f.signEvidence(completed);
42
+ const fixturePath=path.join(f.dir,'case.json');
43
+ await writeFile(fixturePath,JSON.stringify({directory:f.journalDir,device:f.device,request:f.request,envelope}));
44
+ const spawnWorker=()=>new Promise((resolve,reject)=>{
45
+ const child=spawn(process.execPath,[worker,fixturePath],{shell:false,windowsHide:true,stdio:['ignore','pipe','pipe']});
46
+ let stdout='',stderr='';
47
+ child.stdout.setEncoding('utf8');child.stderr.setEncoding('utf8');
48
+ child.stdout.on('data',s=>stdout+=s);child.stderr.on('data',s=>stderr+=s);
49
+ child.once('error',reject);child.once('close',code=>code===0?resolve(JSON.parse(stdout.trim())):reject(Error('worker exit '+code+' '+stderr)));
50
+ });
51
+ const results=await Promise.all(Array.from({length:8},spawnWorker));
52
+ assert.equal(results.filter(r=>r.status==='stored').length,1);
53
+ assert.equal(results.filter(r=>r.status==='existing').length,7);
54
+ assert.deepEqual(await f.journal.lookupTerminal(f.request),envelope);
55
+ const children=(await readdir(f.journalDir)).filter(x=>x.endsWith('.tmp'));
56
+ assert.deepEqual(children,[],'all successful writers removed private temp files');
57
+ });
58
+ test('R203 competing distinct signed terminal results cannot overwrite a winner',async t=>{
59
+ const f=await fixture(t);
60
+ const first=f.signEvidence(completed);
61
+ const second=f.signEvidence({status:'completed',result_sha256:'d'.repeat(64)});
62
+ const results=await Promise.allSettled([f.journal.commitSignedTerminal({request:f.request,envelope:first}),
63
+ f.journal.commitSignedTerminal({request:f.request,envelope:second})]);
64
+ assert.equal(results.filter(r=>r.status==='fulfilled').length,1);
65
+ assert.equal(results.filter(r=>r.status==='rejected'&&/conflicting signed terminal/.test(r.reason?.message)).length,1);
66
+ const actual=await f.journal.lookupTerminal(f.request);
67
+ assert.ok([first,second].some(p=>p.evidence.terminal.result_sha256===actual.evidence.terminal.result_sha256));
68
+ });
69
+ test('R203 preexisting directory or oversized forged final record is never overwritten',async t=>{
70
+ const f=await fixture(t),file=path.join(f.journalDir,f.request.request_id+'.json');
71
+ await mkdir(file);
72
+ await assert.rejects(f.journal.commitSignedTerminal({request:f.request,envelope:f.signEvidence(completed)}),/bounded regular file/);
73
+ await rm(file,{recursive:true});
74
+ await writeFile(file,' '.repeat(17000));
75
+ await assert.rejects(f.journal.lookupTerminal(f.request),/bounded regular file/);
76
+ });
77
+ test('R203 signed record read uses bounded handle and detects invalid mutable payload',async t=>{
78
+ const f=await fixture(t),envelope=f.signEvidence(completed);
79
+ await f.journal.commitSignedTerminal({request:f.request,envelope});
80
+ const file=path.join(f.journalDir,f.request.request_id+'.json');
81
+ const initial=await stat(file);
82
+ assert.ok(initial.size>64);
83
+ const bad={...envelope,evidence:{...envelope.evidence,terminal:{...completed,result_sha256:'d'.repeat(64)}}};
84
+ await writeFile(file,JSON.stringify(bad));
85
+ await assert.rejects(f.journal.lookupTerminal(f.request),/signature invalid/);
86
+ });
@@ -0,0 +1,31 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const cli=fileURLToPath(new URL('../scripts/r205-candidate-selection.mjs',import.meta.url));
7
+ const H=c=>c.repeat(64);
8
+ const fixture=H('a'),env=H('b');
9
+ function input(status='PASS'){
10
+ const artifact=H('4');
11
+ const correct=(h,s)=>({status:s,artifact_sha256:h,fixture_sha256:fixture,receipt_sha256:H('2')});
12
+ const benchmark=(h,samples)=>({artifact_sha256:h,fixture_sha256:fixture,environment_sha256:env,receipt_sha256:H('3'),samples_ms:samples});
13
+ return {schema:'deadbyte.candidate-selection.r205.v1',fixture_sha256:fixture,environment_sha256:env,
14
+ baseline:{artifact_sha256:H('1'),correctness:correct(H('1'),'PASS'),benchmark:benchmark(H('1'),Array(7).fill(100))},
15
+ candidates:[{candidate_id:'candidate',artifact_sha256:artifact,correctness:correct(artifact,status),...(status==='PASS'?{benchmark:benchmark(artifact,Array(7).fill(75))}:{})}]};
16
+ }
17
+ const run=stdin=>spawnSync(process.execPath,[cli],{input:stdin,encoding:'utf8',windowsHide:true,timeout:10000,stdio:['pipe','pipe','pipe']});
18
+ test('R205 CLI returns provisional evidence only, no release authorization',()=>{
19
+ const r=run(JSON.stringify(input()));assert.equal(r.status,0,r.stderr);
20
+ const j=JSON.parse(r.stdout);assert.equal(j.state,'PROVISIONAL_CANDIDATE');assert.equal(j.release_authorized,false);assert.equal(j.evidence_verified,false);
21
+ });
22
+ test('R205 CLI exits 2 for ungraded result without turning uncertainty into failure score',()=>{
23
+ const r=run(JSON.stringify(input('UNKNOWN')));assert.equal(r.status,2,r.stderr);
24
+ const j=JSON.parse(r.stdout);assert.equal(j.candidates[0].verdict,'UNGRADED');assert.equal(j.state,'NO_QUALIFIED_CANDIDATE');
25
+ });
26
+ test('R205 CLI refuses malformed JSON with exit 3',()=>{
27
+ const r=run('{invalid');assert.equal(r.status,3);assert.match(r.stderr,/R205_SELECTION_REFUSED/);
28
+ });
29
+ test('R205 CLI refuses oversized stdin before evaluation',()=>{
30
+ const r=run('x'.repeat(65537));assert.equal(r.status,3);assert.match(r.stderr,/R205_INPUT_LIMIT_EXCEEDED/);
31
+ });
@@ -0,0 +1,83 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {CANDIDATE_SELECTION_SCHEMA,buildBlindCandidateViewR205,evaluateCandidateSelectionR205} from '../src/candidate-selection-r205.mjs';
4
+ const H=c=>c.repeat(64);
5
+ const fixture=H('a'),environment=H('b');
6
+ const evidence=(status,artifact,tag)=>({status,artifact_sha256:artifact,fixture_sha256:fixture,receipt_sha256:H(tag)});
7
+ const bench=(artifact,times,tag,env=environment)=>({artifact_sha256:artifact,fixture_sha256:fixture,environment_sha256:env,receipt_sha256:H(tag),samples_ms:times});
8
+ const candidate=(id,artifact,status,times,tag='5')=>({candidate_id:id,artifact_sha256:artifact,correctness:evidence(status,artifact,tag),...(times?{benchmark:bench(artifact,times,'6')}:{})});
9
+ const input=()=>({schema:CANDIDATE_SELECTION_SCHEMA,fixture_sha256:fixture,environment_sha256:environment,
10
+ baseline:{artifact_sha256:H('1'),correctness:evidence('PASS',H('1'),'2'),benchmark:bench(H('1'),[100,100,100,100,100,100,100],'3')},
11
+ candidates:[candidate('fast',H('4'),'PASS',[80,81,79,80,81,79,80]) ]});
12
+ test('R205 fast and correct candidate is provisional, never a release authorization',()=>{
13
+ const r=evaluateCandidateSelectionR205(input());
14
+ assert.equal(r.state,'PROVISIONAL_CANDIDATE');assert.equal(r.winner_candidate_id,'fast');
15
+ assert.equal(r.evidence_verified,false);assert.equal(r.release_authorized,false);assert.match(r.report_sha256,/^[0-9a-f]{64}$/);
16
+ });
17
+ test('R205 faster incorrect candidate is rejected even if its benchmark looks good',()=>{
18
+ const i=input();i.candidates.push(candidate('bad',H('7'),'FAIL',[1,1,1,1,1,1,1]));
19
+ const r=evaluateCandidateSelectionR205(i);
20
+ assert.equal(r.candidates.find(x=>x.candidate_id==='bad').verdict,'REJECT_CORRECTNESS');
21
+ assert.equal(r.winner_candidate_id,'fast');
22
+ });
23
+ test('R205 unknown grader verdict stays UNGRADED not score zero',()=>{
24
+ const i=input();i.candidates=[candidate('unknown',H('7'),'UNKNOWN',null)];
25
+ const r=evaluateCandidateSelectionR205(i);
26
+ assert.equal(r.state,'NO_QUALIFIED_CANDIDATE');assert.equal(r.candidates[0].verdict,'UNGRADED');
27
+ });
28
+ test('R205 invalid and untested grader status stay UNGRADED',()=>{
29
+ for(const state of ['INVALID','UNTESTED']){
30
+ const i=input();i.candidates=[candidate(state,H('7'),state,null)];
31
+ assert.equal(evaluateCandidateSelectionR205(i).candidates[0].verdict,'UNGRADED');
32
+ }
33
+ });
34
+ test('R205 unstable or noisy benchmark cannot win',()=>{
35
+ const i=input();i.candidates=[candidate('noisy',H('7'),'PASS',[75,75,75,75,150,150,150])];
36
+ assert.equal(evaluateCandidateSelectionR205(i).state,'NO_QUALIFIED_CANDIDATE');
37
+ });
38
+ test('R205 no meaningful speedup cannot win',()=>{
39
+ const i=input();i.candidates=[candidate('tiny',H('7'),'PASS',[98,98,98,98,98,98,98])];
40
+ assert.equal(evaluateCandidateSelectionR205(i).state,'NO_QUALIFIED_CANDIDATE');
41
+ });
42
+ test('R205 close candidates produce AMBIGUOUS, never arbitrary promotion',()=>{
43
+ const i=input();i.candidates.push(candidate('near',H('7'),'PASS',[81,81,81,81,81,81,81]));
44
+ const r=evaluateCandidateSelectionR205(i);
45
+ assert.equal(r.state,'AMBIGUOUS');assert.equal(r.winner_candidate_id,null);
46
+ });
47
+ test('R205 candidate ordering and names do not alter blind evaluation hash',()=>{
48
+ const i=input();i.candidates.push(candidate('other',H('7'),'FAIL',null));
49
+ const hash1=evaluateCandidateSelectionR205(i).blind_evaluation_sha256;
50
+ i.candidates.reverse();i.candidates[0].candidate_id='new_other';i.candidates[1].candidate_id='new_fast';
51
+ assert.equal(evaluateCandidateSelectionR205(i).blind_evaluation_sha256,hash1);
52
+ assert.equal(JSON.stringify(buildBlindCandidateViewR205(i)).includes('new_fast'),false);
53
+ });
54
+ test('R205 disallows proposer identity and hidden metadata from evaluation',()=>{
55
+ const i=input();i.candidates[0].proposer='model-b';
56
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/unexpected field proposer/);
57
+ });
58
+ test('R205 refuses forged correctness artifact identity',()=>{
59
+ const i=input();i.candidates[0].correctness.artifact_sha256=H('f');
60
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/identity mismatch/);
61
+ });
62
+ test('R205 refuses benchmark environment drift',()=>{
63
+ const i=input();i.candidates[0].benchmark.environment_sha256=H('f');
64
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/identity mismatch/);
65
+ });
66
+ test('R205 refuses duplicate candidate artifact or baseline artifact',()=>{
67
+ const i=input();i.candidates.push(candidate('copy',H('4'),'PASS',[90,90,90,90,90,90,90]));
68
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/duplicated/);
69
+ i.candidates=[candidate('base',H('1'),'PASS',[70,70,70,70,70,70,70])];
70
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/duplicated/);
71
+ });
72
+ test('R205 refuses missing/insufficient samples rather than accepting ungraded benchmark',()=>{
73
+ const i=input();i.candidates[0].benchmark.samples_ms=[80,80];
74
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/samples invalid/);
75
+ i.candidates=[candidate('no',H('5'),'PASS',null)];
76
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/benchmark missing/);
77
+ });
78
+ test('R205 refuses mismatched paired sample counts and malformed values',()=>{
79
+ const i=input();i.candidates[0].benchmark.samples_ms=[80,80,80,80,80,80,80,80];
80
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/length mismatch/);
81
+ i.candidates[0].benchmark.samples_ms=[80,80,80,80,80,80,NaN];
82
+ assert.throws(()=>evaluateCandidateSelectionR205(i),/samples invalid/);
83
+ });