deadbyte-mcp 0.15.74 → 0.15.75

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 (103) hide show
  1. package/MANIFEST.SHA256 +102 -42
  2. package/PROVENANCE.json +19 -21
  3. package/README.txt +23 -18
  4. package/RELEASE.json +51 -18
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +22 -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 +4 -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/init-desktop-policy-r199.ps1 +85 -0
  32. package/scripts/npm-release-r48.mjs +17 -0
  33. package/scripts/npm-release-readiness-r195.mjs +72 -0
  34. package/scripts/npm-release-readiness-r206.mjs +76 -0
  35. package/scripts/r193-owner-request-live.mjs +5 -6
  36. package/scripts/r193-owner-uac-broker.mjs +3 -3
  37. package/scripts/r205-candidate-selection.mjs +22 -0
  38. package/src/candidate-selection-evidence-r206.mjs +71 -0
  39. package/src/candidate-selection-r205.mjs +96 -0
  40. package/src/desktop-postcondition-r196.mjs +30 -0
  41. package/src/desktop-runtime-r40.mjs +44 -7
  42. package/src/desktop-uia-identity-r199.mjs +51 -0
  43. package/src/desktop-uia-r40.mjs +9 -1
  44. package/src/machine-fs-handle-read-r196.mjs +43 -0
  45. package/src/machine-fs-native-read-r197.mjs +73 -0
  46. package/src/machine-fs-native-read-r198.mjs +76 -0
  47. package/src/machine-fs-read-worker.mjs +5 -25
  48. package/src/machine-fs.mjs +31 -16
  49. package/src/r195-canonical-owner-observation.mjs +15 -0
  50. package/src/remote-destination-journal-r202.mjs +120 -0
  51. package/src/remote-device-r44.mjs +89 -14
  52. package/src/remote-payload-boundary-r201.mjs +71 -0
  53. package/src/remote-terminal-evidence-r200.mjs +60 -0
  54. package/src/version.mjs +1 -1
  55. package/test/desktop-runtime-r40.test.mjs +111 -0
  56. package/test/desktop-uia-r40.test.mjs +15 -0
  57. package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
  58. package/test/fixtures/r203-journal-writer.mjs +6 -0
  59. package/test/process-policy-refresh.test.mjs +4 -4
  60. package/test/r130-release-security-contract.test.mjs +8 -8
  61. package/test/r132-release-identity.test.mjs +7 -7
  62. package/test/r153-prepublish-identity.test.mjs +1 -1
  63. package/test/r160-release-contract.test.mjs +3 -3
  64. package/test/r165-live-cli-skew.test.mjs +1 -1
  65. package/test/r167-release-contract.test.mjs +11 -11
  66. package/test/r168-release-contract.test.mjs +4 -4
  67. package/test/r172-release-contract.test.mjs +6 -6
  68. package/test/r173-release-contract.test.mjs +16 -16
  69. package/test/r179-release-contract.test.mjs +12 -12
  70. package/test/r190-release-contract.test.mjs +10 -10
  71. package/test/r191-release-contract.test.mjs +12 -12
  72. package/test/r192-release-identity.test.mjs +9 -9
  73. package/test/r193-release-identity.test.mjs +24 -22
  74. package/test/r193-release-readiness.test.mjs +2 -2
  75. package/test/r193-windows-gate.test.mjs +8 -8
  76. package/test/r194-release-identity.test.mjs +19 -15
  77. package/test/r194-release-readiness.test.mjs +2 -2
  78. package/test/r194-windows-gate.test.mjs +7 -6
  79. package/test/r195-canonical-owner-observation.test.mjs +32 -0
  80. package/test/r195-release-readiness.test.mjs +22 -0
  81. package/test/r196-desktop-postcondition.test.mjs +53 -0
  82. package/test/r196-handle-bound-read.test.mjs +99 -0
  83. package/test/r196-remote-outcome.test.mjs +94 -0
  84. package/test/r197-namespace-race.test.mjs +41 -0
  85. package/test/r197-native-dispatch-race.test.mjs +49 -0
  86. package/test/r197-native-file-read.test.mjs +60 -0
  87. package/test/r197-native-root-open.test.mjs +61 -0
  88. package/test/r198-root-identity.test.mjs +88 -0
  89. package/test/r199-native-uia-worker.test.mjs +28 -0
  90. package/test/r199-uia-identity.test.mjs +43 -0
  91. package/test/r200-remote-reconcile.test.mjs +106 -0
  92. package/test/r201-payload-boundary.test.mjs +78 -0
  93. package/test/r201-payload-fuzz.test.mjs +37 -0
  94. package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
  95. package/test/r202-destination-journal.test.mjs +92 -0
  96. package/test/r203-atomic-journal.test.mjs +86 -0
  97. package/test/r205-candidate-selection-cli.test.mjs +31 -0
  98. package/test/r205-candidate-selection.test.mjs +83 -0
  99. package/test/r206-candidate-evidence.test.mjs +64 -0
  100. package/test/r206-release-contract.test.mjs +42 -0
  101. package/test/r206-release-readiness.test.mjs +23 -0
  102. package/test/r59-tool-surface-compat.test.mjs +1 -1
  103. package/test/r64-machine-fs-large-read.test.mjs +26 -12
@@ -0,0 +1,60 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {readFileSync} from 'node:fs';
5
+ import {mkdtemp,mkdir,rm,writeFile,lstat,rename,symlink} from 'node:fs/promises';
6
+ import {spawnSync} from 'node:child_process';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ const exe=fileURLToPath(new URL('../bin/deadbyte-fs-read-r197-fixed.exe',import.meta.url));
11
+ const pinned='7477ca3d94168c8b75711b734df5b9c20a43ecbd9338cfc234700d7e27982b63';
12
+ const hash=b=>createHash('sha256').update(b).digest('hex');
13
+ function native(root,rel,offset='0',length='64',whole='1'){
14
+ assert.equal(hash(readFileSync(exe)),pinned,'native executable hash drift');
15
+ return spawnSync(exe,[root,rel,offset,length,whole],{windowsHide:true,shell:false,encoding:'utf8',timeout:10000,maxBuffer:9*1024*1024});
16
+ }
17
+ async function fixture(t){
18
+ const tmp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-fsread-'));
19
+ t.after(()=>rm(tmp,{force:true,recursive:true}));
20
+ const root=path.join(tmp,'root'),outside=path.join(tmp,'outside');
21
+ await mkdir(root);await mkdir(outside);await mkdir(path.join(root,'inner'));
22
+ const bytes=Buffer.from('R197-allowed-data');
23
+ await writeFile(path.join(root,'inner','read.bin'),bytes);
24
+ await writeFile(path.join(outside,'read.bin'),'OUTSIDE-private-data');
25
+ return {tmp,root,outside,bytes};
26
+ }
27
+ test('R197 native reader returns bounded bytes/hash and exact opened file identity',async t=>{
28
+ if(process.platform!=='win32')return t.skip();
29
+ const f=await fixture(t);
30
+ const r=native(f.root,'inner/read.bin');
31
+ assert.equal(r.status,0,r.stderr);
32
+ const data=JSON.parse(r.stdout),info=await lstat(path.join(f.root,'inner','read.bin'),{bigint:true});
33
+ assert.deepEqual(Buffer.from(data.data_base64,'base64'),f.bytes);
34
+ assert.equal(data.file_sha256,hash(f.bytes));
35
+ assert.equal(data.file_size,f.bytes.length);
36
+ assert.equal(data.file_dev,String(info.dev));
37
+ assert.equal(data.file_ino,String(info.ino));
38
+ });
39
+ test('R197 native refuses a replaced directory junction before reading external file',async t=>{
40
+ if(process.platform!=='win32')return t.skip();
41
+ const f=await fixture(t);
42
+ await rename(path.join(f.root,'inner'),path.join(f.root,'old'));
43
+ await symlink(f.outside,path.join(f.root,'inner'),'junction');
44
+ const r=native(f.root,'inner/read.bin');
45
+ assert.notEqual(r.status,0,'outside file was opened');
46
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
47
+ assert.doesNotMatch(r.stdout,/OUTSIDE/);
48
+ });
49
+ test('R197 native bounded offset and un-hashed read',async t=>{
50
+ if(process.platform!=='win32')return t.skip();
51
+ const f=await fixture(t);
52
+ const r=native(f.root,'inner/read.bin','5','4','0');
53
+ assert.equal(r.status,0,r.stderr);
54
+ const data=JSON.parse(r.stdout);
55
+ assert.equal(Buffer.from(data.data_base64,'base64').toString('utf8'),f.bytes.subarray(5,9).toString('utf8'));
56
+ assert.equal(data.file_sha256,null);
57
+ const invalid=native(f.root,'inner/read.bin','0','4194305','0');
58
+ assert.notEqual(invalid.status,0);
59
+ assert.match(invalid.stderr,/range_budget/);
60
+ });
@@ -0,0 +1,61 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {readFileSync} from 'node:fs';
5
+ import {mkdtemp,mkdir,rm,writeFile,rename,symlink} from 'node:fs/promises';
6
+ import {spawnSync} from 'node:child_process';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ const exe=fileURLToPath(new URL('../bin/deadbyte-root-relative-probe-r197.exe',import.meta.url));
11
+ const expected='7cd989bd396a2871d322e1a9bb02fdbc299614e89d59d9b03d03e92a77038fd1';
12
+ function probe(root,rel){
13
+ const h=createHash('sha256').update(readFileSync(exe)).digest('hex');
14
+ assert.equal(h,expected,'native executable SHA-256 changed');
15
+ return spawnSync(exe,[root,rel],{windowsHide:true,shell:false,encoding:'utf8',timeout:5000,maxBuffer:100000});
16
+ }
17
+ async function fixture(t){
18
+ const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-native-'));
19
+ t.after(()=>rm(scratch,{recursive:true,force:true}));
20
+ const root=path.join(scratch,'root'),outside=path.join(scratch,'outside');
21
+ await mkdir(root);await mkdir(outside);
22
+ await mkdir(path.join(root,'sub'));
23
+ await writeFile(path.join(root,'sub','x.bin'),'root-data');
24
+ await writeFile(path.join(outside,'x.bin'),'secret-data');
25
+ return {scratch,root,outside};
26
+ }
27
+ test('R197 anchored native open accepts a regular in-root file',async t=>{
28
+ if(process.platform!=='win32')return t.skip();
29
+ const f=await fixture(t);
30
+ const r=probe(f.root,'sub/x.bin');
31
+ assert.equal(r.status,0,r.stderr);
32
+ assert.match(r.stdout,/R197_NATIVE_HANDLE_RELATIVE_OPEN_PASS bytes=9 components=2/);
33
+ });
34
+ test('R197 anchored native open refuses existing junction before any content read',async t=>{
35
+ if(process.platform!=='win32')return t.skip();
36
+ const f=await fixture(t);
37
+ await symlink(f.outside,path.join(f.root,'link'),'junction');
38
+ const r=probe(f.root,'link/x.bin');
39
+ assert.notEqual(r.status,0,'junction was followed');
40
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
41
+ });
42
+ test('R197 anchored native open refuses swapped directory junction after earlier nominal pathname',async t=>{
43
+ if(process.platform!=='win32')return t.skip();
44
+ const f=await fixture(t);
45
+ await rename(path.join(f.root,'sub'),path.join(f.root,'sub-old'));
46
+ await symlink(f.outside,path.join(f.root,'sub'),'junction');
47
+ const r=probe(f.root,'sub/x.bin');
48
+ assert.notEqual(r.status,0,'swapped junction was followed');
49
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
50
+ });
51
+ test('R197 anchored native open refuses traversal syntax and root reparse',async t=>{
52
+ if(process.platform!=='win32')return t.skip();
53
+ const f=await fixture(t);
54
+ const traversal=probe(f.root,'../outside/x.bin');
55
+ assert.notEqual(traversal.status,0);
56
+ assert.match(traversal.stderr,/invalid_segment/);
57
+ await symlink(f.root,path.join(f.scratch,'root-link'),'junction');
58
+ const reparseRoot=probe(path.join(f.scratch,'root-link'),'sub/x.bin');
59
+ assert.notEqual(reparseRoot.status,0);
60
+ assert.match(reparseRoot.stderr,/root_reparse_or_unverified/);
61
+ });
@@ -0,0 +1,88 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import cp from 'node:child_process';
5
+ import {createHash} from 'node:crypto';
6
+ import {mkdtemp,mkdir,rm,writeFile,lstat} from 'node:fs/promises';
7
+ import {syncBuiltinESMExports} from 'node:module';
8
+ import {tmpdir} from 'node:os';
9
+ import path from 'node:path';
10
+ import {fileURLToPath} from 'node:url';
11
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
12
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
13
+ import {generateTrustKeypair} from '../src/trust.mjs';
14
+
15
+ const exe=fileURLToPath(new URL('../bin/deadbyte-fs-read-r198.exe',import.meta.url));
16
+ const digest=b=>createHash('sha256').update(b).digest('hex');
17
+ const pinned='4325ae4f033bba195b34d2c49b48493a1a468f3443e369a9bb887a7a129eac16';
18
+ const filename='leaf.bin';
19
+ async function fixture(t){
20
+ const scratch=await mkdtemp(path.join(tmpdir(),'deadbyte-r198-root-'));
21
+ t.after(()=>rm(scratch,{force:true,recursive:true}));
22
+ const root=path.join(scratch,'allowed'),data=Buffer.from('R198-ALLOWED-DATA');
23
+ await mkdir(root);
24
+ await writeFile(path.join(root,filename),data);
25
+ const policyPath=path.join(scratch,'policy.json');
26
+ await writeFile(policyPath,JSON.stringify({
27
+ schema:'deadbyte.coding-policy.v1',enabled:true,
28
+ lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
29
+ roots:{project:{path:root,read:true,write:false,exec:false,deny_paths:[]}},
30
+ commands:{},max_read_bytes:65536,max_write_bytes:65536
31
+ }));
32
+ const policy=await loadCodingPolicy(policyPath);
33
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
34
+ return {scratch,root,data,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
35
+ }
36
+ function native(args){
37
+ assert.equal(digest(fs.readFileSync(exe)),pinned,'R198 native binary hash drift');
38
+ return cp.spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:10000});
39
+ }
40
+ test('R198 native accepts exactly the root directory object prechecked by caller',async t=>{
41
+ if(process.platform!=='win32')return t.skip();
42
+ const f=await fixture(t),info=await lstat(f.root,{bigint:true});
43
+ const result=native([f.root,filename,'0','64','1',String(info.dev),String(info.ino)]);
44
+ assert.equal(result.status,0,result.stderr);
45
+ const response=JSON.parse(result.stdout);
46
+ assert.deepEqual(Buffer.from(response.data_base64,'base64'),f.data);
47
+ assert.equal(response.file_sha256,digest(f.data));
48
+ });
49
+ test('R198 native rejects a mismatched root identity BEFORE opening leaf',async t=>{
50
+ if(process.platform!=='win32')return t.skip();
51
+ const f=await fixture(t),info=await lstat(f.root,{bigint:true});
52
+ const result=native([f.root,filename,'0','64','1',String(info.dev),String(info.ino+1n)]);
53
+ assert.notEqual(result.status,0,'expected root identity did not gate native read');
54
+ assert.match(result.stderr,/root_identity_mismatch/);
55
+ assert.doesNotMatch(result.stdout,/data_base64|file_sha256/);
56
+ });
57
+ test('R198 runtime refuses root replacement after precheck before native open',async t=>{
58
+ if(process.platform!=='win32')return t.skip();
59
+ const f=await fixture(t);
60
+ const external=path.join(f.scratch,'external');
61
+ await mkdir(external);
62
+ // Same file length, different bytes and object ID. Not a junction: this tests root identity.
63
+ await writeFile(path.join(external,filename),Buffer.from('R198-ESCAPED-DATA'));
64
+ assert.equal(fs.statSync(path.join(external,filename)).size,f.data.length);
65
+ const original=cp.spawn;
66
+ let swaps=0;
67
+ cp.spawn=(command,args,opts)=>{
68
+ if(path.basename(String(command)).toLowerCase()==='deadbyte-fs-read-r198.exe'){
69
+ swaps++;
70
+ fs.renameSync(f.root,path.join(f.scratch,'original-root'));
71
+ fs.renameSync(external,f.root);
72
+ }
73
+ return original(command,args,opts);
74
+ };
75
+ syncBuiltinESMExports();
76
+ try{
77
+ await assert.rejects(f.runtime.read({rootId:'project',relativePath:filename,offset:0,
78
+ maxBytes:64,hashFile:true,timeoutMs:5000}),/root_identity_mismatch/);
79
+ }finally{cp.spawn=original;syncBuiltinESMExports();}
80
+ assert.equal(swaps,1,'root was not actually exchanged after precheck');
81
+ });
82
+ test('R198 ordinary runtime read remains operational with root pinned',async t=>{
83
+ if(process.platform!=='win32')return t.skip();
84
+ const f=await fixture(t);
85
+ const result=await f.runtime.read({rootId:'project',relativePath:filename,maxBytes:64,hashFile:true});
86
+ assert.deepEqual(Buffer.from(result.data_base64,'base64'),f.data);
87
+ assert.equal(result.file_sha256,digest(f.data));
88
+ });
@@ -0,0 +1,28 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {createHash} from 'node:crypto';
5
+ import {readFileSync} from 'node:fs';
6
+ import {fileURLToPath} from 'node:url';
7
+ const exe=fileURLToPath(new URL('../bin/deadbyte-desktop-uia-r199-fixed.exe',import.meta.url));
8
+ const pinned='583f5a2cdddb8640586d13c0066de4f872af543b467078b2e75a466092421972';
9
+ const sha=v=>createHash('sha256').update(v).digest('hex');
10
+ function run(args,options={}){
11
+ assert.equal(sha(readFileSync(exe)),pinned,'R199 native UIA executable changed');
12
+ return spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:10000,maxBuffer:500000,...options});
13
+ }
14
+ test('R199 native UIA worker pinned binary completes bounded selftest',t=>{
15
+ if(process.platform!=='win32')return t.skip('Windows only');
16
+ const r=run(['--selftest']);
17
+ assert.equal(r.status,0,r.stderr);
18
+ assert.deepEqual(JSON.parse(r.stdout),{schema:'deadbyte.desktop-uia-selftest.v1',status:'passed'});
19
+ });
20
+ test('R199 native UIA worker refuses malformed protocol before COM query',t=>{
21
+ if(process.platform!=='win32')return t.skip('Windows only');
22
+ for(const bad of ['', 'BAD_MAGIC\n', 'DEADBYTE_DESKTOP_UIA_V1\nexpected_hwnd=0x1\n']){
23
+ const r=run([],{input:bad});
24
+ assert.notEqual(r.status,0);
25
+ assert.match(r.stderr,/deadbyte-desktop-uia:/);
26
+ assert.equal(r.stdout,'');
27
+ }
28
+ });
@@ -0,0 +1,43 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {bindUiaElementR199,assertFreshUiaElementR199,normalizeUiaRuntimeIdR199} from '../src/desktop-uia-identity-r199.mjs';
5
+
6
+ const H=x=>createHash('sha256').update(x).digest('hex');
7
+ const frame={frame_id:H('frame'),desktop_epoch:H('epoch'),active_window:{hwnd:'0x1234',pid:42}};
8
+ const q={kind:'name_exact',value:'SECRET SAVE',max_results:8,ordinal:0};
9
+ const rect={left:20,top:30,width:90,height:40};
10
+ function ground(id=[1,-20,3],overrides={}){
11
+ return {status:'ok',query_kind:q.kind,query_sha256:H('query'),ordinal:0,truncated:false,visited:5,
12
+ matches:[{runtime_id:id,control_type:50000,rect,enabled:true,offscreen:false}],...overrides};
13
+ }
14
+ const binding=()=>bindUiaElementR199({frame,grounded:ground(),candidate:ground().matches[0],query:q});
15
+
16
+ test('R199 identity binding is ephemeral, bounded and has content-addressed token',()=>{
17
+ const b=binding();assert.equal(b.schema,'deadbyte.uia-element-binding-r199.v1');
18
+ assert.match(b.token_sha256,/^[0-9a-f]{64}$/);
19
+ assert.deepEqual(b.runtime_id,[1,-20,3]);
20
+ assert.ok(Object.isFrozen(b.runtime_id));assert.ok(Object.isFrozen(b.query));
21
+ assert.equal(JSON.stringify({token:b.token_sha256}).includes('SECRET'),false);
22
+ });
23
+ test('R199 exact native RuntimeId permits read-only revalidation',()=>{
24
+ const proof=assertFreshUiaElementR199(binding(),ground());
25
+ assert.equal(proof.verified,true);assert.match(proof.runtime_id_sha256,/^[0-9a-f]{64}$/);
26
+ });
27
+ test('R199 recreated element with identical rectangle is refused',()=>{
28
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground([1,-20,4])),/element identity changed/);
29
+ });
30
+ test('R199 runtime identity refuses query drift and incomplete scan',()=>{
31
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{query_sha256:H('other')})),/query identity/);
32
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{truncated:true})),/scan invalid or incomplete/);
33
+ });
34
+ test('R199 state, disappearance and rectangle changes must refuse mutation',()=>{
35
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[]})),/stale target/);
36
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[{...ground().matches[0],enabled:false}]})),/control state changed/);
37
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[{...ground().matches[0],rect:{...rect,left:21}}]})),/rectangle changed/);
38
+ });
39
+ test('R199 runtime identity refuses forged or oversized numeric arrays',()=>{
40
+ for(const id of [[],[1.5],[2147483648],Array(65).fill(1),['1'],null]){
41
+ assert.throws(()=>normalizeUiaRuntimeIdR199(id),/RuntimeId/);
42
+ }
43
+ });
@@ -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
+ });