deadbyte-mcp 0.15.0 → 0.15.2

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 (110) hide show
  1. package/MANIFEST.SHA256 +109 -64
  2. package/PROVENANCE.json +11 -0
  3. package/R45-PARENT.json +18 -0
  4. package/README.txt +36 -20
  5. package/RELEASE.json +53 -0
  6. package/SBOM.cdx.json +46 -0
  7. package/TEST-MATRIX.json +31 -0
  8. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  9. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  10. package/controller/README.TXT +2 -2
  11. package/controller/deadbyte-controller.ps1 +51 -3
  12. package/controller/deadbyte-process-policy.json +63 -1
  13. package/docs/ARCHITECTURE.md +3 -4
  14. package/native/deadbyte-hardware-observer.cpp +308 -0
  15. package/package.json +14 -7
  16. package/scripts/autonomous-tests.ps1 +2 -2
  17. package/scripts/build-hardware-r45.ps1 +71 -0
  18. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  19. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  20. package/scripts/derive-r45-parent.mjs +24 -0
  21. package/scripts/derive-r45-release.mjs +72 -0
  22. package/scripts/derive-r45-tests.mjs +20 -0
  23. package/scripts/final-closure-r45.mjs +121 -0
  24. package/scripts/final-closure-r46.mjs +98 -0
  25. package/scripts/final-closure-verify-r45.mjs +220 -0
  26. package/scripts/final-closure-verify-r46.mjs +119 -0
  27. package/scripts/finalize-r45.mjs +219 -0
  28. package/scripts/full-regression-r45.mjs +20 -0
  29. package/scripts/full-regression-r46.mjs +20 -0
  30. package/scripts/full-regression-summary-r45.mjs +14 -0
  31. package/scripts/gate-windows-r45.ps1 +44 -0
  32. package/scripts/gate-windows-r46.ps1 +56 -0
  33. package/scripts/init-host-policy.ps1 +39 -1
  34. package/scripts/inspect-active-release-r45.mjs +20 -0
  35. package/scripts/refresh-process-policy.mjs +64 -7
  36. package/scripts/release-manifest.mjs +3 -1
  37. package/scripts/release-parity-r45.mjs +253 -0
  38. package/scripts/release-parity-r46.mjs +187 -0
  39. package/scripts/release-parity-tests-r45.ps1 +8 -0
  40. package/scripts/verify-r45-parent.mjs +142 -0
  41. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  42. package/scripts/windows-gate-evidence-r46.mjs +110 -0
  43. package/src/autonomous-runtime.mjs +12 -7
  44. package/src/capability-ledger.mjs +2 -0
  45. package/src/compact-gateway-mcp-tools.mjs +11 -7
  46. package/src/deadbyte-cli.mjs +24 -12
  47. package/src/doctor-r45.mjs +65 -0
  48. package/src/host-plane.mjs +133 -85
  49. package/src/machine-fs-mcp-tools.mjs +3 -3
  50. package/src/machine-fs-output-contracts.mjs +1 -1
  51. package/src/machine-fs-read-worker.mjs +46 -0
  52. package/src/machine-fs.mjs +63 -10
  53. package/src/mcp-server.mjs +68 -23
  54. package/src/mcp-smoke-client.mjs +4 -1
  55. package/src/native-diff-v2.mjs +9 -4
  56. package/src/native-diff.mjs +29 -7
  57. package/src/observation-mcp-tools.mjs +5 -1
  58. package/src/observation-output-contracts.mjs +8 -0
  59. package/src/observation-runtime.mjs +57 -5
  60. package/src/observation-smoke-client.mjs +6 -2
  61. package/src/output-contracts.mjs +27 -11
  62. package/src/process-mcp-tools.mjs +18 -5
  63. package/src/process-output-contracts.mjs +20 -3
  64. package/src/process-policy.mjs +3 -1
  65. package/src/process-runtime.mjs +99 -19
  66. package/src/process-smoke-client.mjs +67 -22
  67. package/src/r45-compact-disarmed-probe.mjs +74 -0
  68. package/src/r45-full-surface-probe.mjs +34 -0
  69. package/src/release-generation.mjs +23 -3
  70. package/src/remote-device-r44.mjs +53 -15
  71. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  72. package/src/remote-mcp-smoke-client.mjs +1 -1
  73. package/src/runtime-update.mjs +36 -8
  74. package/src/stdio-console-guard.mjs +24 -0
  75. package/src/version.mjs +1 -1
  76. package/test/agent-runtime.test.mjs +1 -0
  77. package/test/autonomous-loop.test.mjs +24 -0
  78. package/test/autonomous-runtime.test.mjs +1 -0
  79. package/test/cli-entrypoint.test.mjs +4 -4
  80. package/test/coding-plane.test.mjs +1 -0
  81. package/test/compact-gateway-r36.test.mjs +28 -0
  82. package/test/compact-max-catalog-r45.test.mjs +99 -0
  83. package/test/contained.test.mjs +23 -11
  84. package/test/controller.test.mjs +5 -1
  85. package/test/core.test.mjs +1 -1
  86. package/test/full-max-catalog-r45.test.mjs +91 -0
  87. package/test/full-max-catalog-r46.test.mjs +92 -0
  88. package/test/hardware-observation-r45.test.mjs +44 -0
  89. package/test/host-inline-r45.test.mjs +67 -0
  90. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  91. package/test/machine-fs-runtime.test.mjs +18 -2
  92. package/test/machine-mcp-integration.test.mjs +1 -0
  93. package/test/observation-mcp-tools.test.mjs +4 -3
  94. package/test/observation-runtime.test.mjs +29 -2
  95. package/test/output-schema.test.mjs +1 -0
  96. package/test/package-boundary.test.mjs +1 -1
  97. package/test/process-mcp-tools.test.mjs +23 -5
  98. package/test/process-policy-refresh.test.mjs +87 -10
  99. package/test/process-runtime.test.mjs +111 -1
  100. package/test/process-supervision.test.mjs +4 -2
  101. package/test/production-docs.test.mjs +3 -3
  102. package/test/r33-closeout-regression.test.mjs +20 -19
  103. package/test/r33-finalization.test.mjs +3 -3
  104. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  105. package/test/r45-finalization.test.mjs +84 -0
  106. package/test/release-generation.test.mjs +26 -11
  107. package/test/release-version.test.mjs +112 -46
  108. package/test/remote-device-r44.test.mjs +75 -0
  109. package/test/remote-r24-catalog.test.mjs +2 -2
  110. package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
@@ -24,6 +24,8 @@ test('R36 compact gateway schemas reject generic arbitrary dispatch',()=>{
24
24
  assert.equal(codingInspectInputSchema.safeParse({op:'call',tool_name:'host_exec',args:{}}).success,false);
25
25
  assert.equal(machineFsGatewayInputSchema.safeParse({op:'exec',command:'whoami'}).success,false);
26
26
  assert.equal(processSessionGatewayInputSchema.safeParse({op:'stdin',session_id:'0'.repeat(64),input:'raw'}).success,false);
27
+ assert.equal(processSessionGatewayInputSchema.safeParse({op:'write',session_id:'0'.repeat(64),input:'raw',append_newline:true}).success,true);
28
+ assert.equal(processSessionGatewayInputSchema.safeParse({op:'write',session_id:'0'.repeat(64),input:'raw',pid:1234}).success,false);
27
29
  assert.equal(machineObserveInputSchema.safeParse({op:'revoke',reason:'no'}).success,false);
28
30
  assert.equal(desktopControlInputSchema.safeParse({op:'call',tool_name:'host_exec'}).success,false);
29
31
  });
@@ -36,6 +38,7 @@ test('R36 compact schemas preserve representative preconditions',()=>{
36
38
  op:'move',root_id:'core',source_path:'a.txt',destination_path:'b.txt'
37
39
  }).success,false);
38
40
  assert.equal(processSessionGatewayInputSchema.safeParse({op:'action',session_id:'b'.repeat(64),action_id:'ping'}).success,true);
41
+ assert.equal(processSessionGatewayInputSchema.safeParse({op:'read',session_id:'b'.repeat(64),offset:-4096,max_bytes:4096,wait_ms:5000,wait_for:'READY>'}).success,true);
39
42
  });
40
43
 
41
44
  test('R40 registers exactly six compact gateway names including desktop_control',()=>{
@@ -116,3 +119,28 @@ test('R40 desktop_control is stable when runtime is absent and routes typed read
116
119
  assert.equal(unavailable.isError,true);
117
120
  assert.equal(unavailable.structuredContent.status,'unavailable');
118
121
  });
122
+
123
+
124
+ test('R46 compact process_session routes guarded write and wait/tail read without adding a tool name',async()=>{
125
+ const calls=[];
126
+ const processRuntime={
127
+ write:async args=>{calls.push(['write',args]);return {logical_bytes:4,logical_sha256:'d'.repeat(64),append_newline:true,session:{session_id:'a'.repeat(64)}};},
128
+ read:async args=>{calls.push(['read',args]);return {session_id:'a'.repeat(64),offset:0,next_offset:0,earliest_offset:0,stream_head:0,stream_sha256:'b'.repeat(64),truncated:false,wait_satisfied:true,data_base64:'',state:'running',exit_code:null};}
129
+ };
130
+ const server=fakeServer();
131
+ registerCompactGatewayTools(server,{
132
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
133
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
134
+ catalogProvider:()=>[...server.tools.keys()]
135
+ });
136
+ const tool=server.tools.get('process_session');
137
+ const wrote=await tool.handler({op:'write',session_id:'a'.repeat(64),input:'x=1',append_newline:true});
138
+ assert.equal(wrote.isError,false);
139
+ const read=await tool.handler({op:'read',session_id:'a'.repeat(64),offset:-1024,max_bytes:4096,wait_ms:250,wait_for:'done'});
140
+ assert.equal(read.isError,false);
141
+ assert.deepEqual(calls,[
142
+ ['write',{sessionId:'a'.repeat(64),input:'x=1',appendNewline:true}],
143
+ ['read',{sessionId:'a'.repeat(64),offset:-1024,maxBytes:4096,waitMs:250,waitFor:'done'}]
144
+ ]);
145
+ assert.equal(server.tools.size,6);
146
+ });
@@ -0,0 +1,99 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R45 real max-runtime compact stdio catalog is exactly 43 with verified skill_catalog while all authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r40-max-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'compact',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'0',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ assert.equal(names.length,43,'expected exact compact max catalog 43, got '+names.length+': '+names.join(','));
79
+ for(const name of ['capability_manifest','desktop_control','coding_inspect','machine_fs','machine_observe','process_session',
80
+ 'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
81
+ const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
82
+ assert.equal(manifest.isError,false);
83
+ const m=manifest.structuredContent.result;
84
+ assert.equal(m.schema,'deadbyte.capability-manifest.v2');
85
+ assert.equal(m.profile,'compact');
86
+ assert.equal(m.tool_count,43);
87
+ assert.deepEqual(m.tool_catalog,names);
88
+ assert.match(m.catalog_sha256,/^[0-9a-f]{64}$/);
89
+ const hardware=m.capabilities.find(item=>item.id==='observation.hardware_query');
90
+ assert.ok(hardware);assert.equal(hardware.authority,'none');assert.equal(hardware.compact_tool,'machine_observe');assert.equal(hardware.compact_op,'hardware_query');
91
+ const skill=m.capabilities.find(item=>item.id==='skill.catalog');
92
+ assert.ok(skill);assert.equal(skill.authority,'none');assert.equal(skill.mutating,false);
93
+ console.log(JSON.stringify({tool_count:m.tool_count,catalog_sha256:m.catalog_sha256,ledger_sha256:m.ledger_sha256,desktop:true,hardware_query:true,skill:true,authority:'DISARMED'}));
94
+ } finally {
95
+ await client.close().catch(()=>{});
96
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
97
+ await f.cleanup();
98
+ }
99
+ });
@@ -352,6 +352,10 @@ test('contained execution source has a compile-time worker registry and no user
352
352
  const executor = await readFile(fileURLToPath(new URL('../src/contained-executor.mjs', import.meta.url)), 'utf8');
353
353
  const registry = await readFile(fileURLToPath(new URL('../src/worker-registry.mjs', import.meta.url)), 'utf8');
354
354
  const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
355
+ const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
356
+ const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
357
+ const contained = server.slice(containedStart, containedEnd);
358
+ assert.ok(containedStart >= 0 && containedEnd > containedStart);
355
359
  assert.match(registry, /contained-transform-worker\.exe/);
356
360
  assert.match(registry, /contained-reverse-worker\.exe/);
357
361
  assert.match(registry, /CONTAINED_EXECUTION_REGISTRY/);
@@ -360,9 +364,9 @@ test('contained execution source has a compile-time worker registry and no user
360
364
  assert.match(registry, /lowercase_ascii/);
361
365
  assert.match(registry, /reverse_bytes/);
362
366
  assert.match(executor, /loadVerifiedWorkerRegistry/);
363
- assert.match(server, /'contained_transform'/);
364
- assert.match(server, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
365
- assert.doesNotMatch(server, /executable:\s*z\./);
367
+ assert.match(contained, /'contained_transform'/);
368
+ assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
369
+ assert.doesNotMatch(contained, /executable:\s*z\./);
366
370
  assert.doesNotMatch(executor, /--worker/);
367
371
  assert.doesNotMatch(server, /registerTool\(\s*['"](?:command_run|shell_execute|powershell_run)['"]/);
368
372
  });
@@ -706,12 +710,16 @@ test('contained execution validator rejects missing or altered worker manifest e
706
710
 
707
711
  test('MCP contained_transform schema is a compile-time operation enum with no executable or invocation-control surface', async () => {
708
712
  const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
709
- assert.match(server, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
710
- assert.equal((server.match(/inputSchema: z\.strictObject\(\{/g) ?? []).length, 8);
713
+ const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
714
+ const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
715
+ const contained = server.slice(containedStart, containedEnd);
716
+ assert.ok(containedStart >= 0 && containedEnd > containedStart);
717
+ assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
718
+ assert.match(contained, /inputSchema: z\.strictObject\(\{/);
711
719
  assert.match(server, /if \(hostPolicy\)/);
712
720
  for (const name of ['host_file_read', 'host_file_write', 'host_exec']) assert.match(server, new RegExp(`'${name}'`));
713
- assert.doesNotMatch(server, /inputSchema: z\.object\(\{/);
714
- assert.doesNotMatch(server, /executable:\s*z\.|argv:\s*z\.|environment:\s*z\.|challenge:\s*z\.|request_digest:\s*z\.|worker_id:\s*z\./);
721
+ assert.doesNotMatch(contained, /inputSchema: z\.object\(\{/);
722
+ assert.doesNotMatch(contained, /executable:\s*z\.|argv:\s*z\.|environment:\s*z\.|challenge:\s*z\.|request_digest:\s*z\.|worker_id:\s*z\./);
715
723
  });
716
724
 
717
725
  test('R27 Windows batch gate builds native payload before freezing and verifying the candidate', async () => {
@@ -914,13 +922,17 @@ test('contained execution validator fails closed on active network evidence', ()
914
922
 
915
923
  test('execution authority is strict at MCP and native argv boundaries', async () => {
916
924
  const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
925
+ const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
926
+ const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
927
+ const contained = server.slice(containedStart, containedEnd);
917
928
  const launcher = await readFile(fileURLToPath(new URL('../native/deadbyte-exec.cpp', import.meta.url)), 'utf8');
918
929
  const helper = await readFile(fileURLToPath(new URL('../native/contained-network-proof.h', import.meta.url)), 'utf8');
919
930
  const authority = await readFile(fileURLToPath(new URL('../src/authority-smoke.mjs', import.meta.url)), 'utf8');
920
- assert.match(server, /inputSchema: z\.strictObject\(/);
921
- assert.doesNotMatch(server, /worker_id:\s*z\./);
922
- assert.doesNotMatch(server, /executable:\s*z\./);
923
- assert.doesNotMatch(server, /argv:\s*z\./);
931
+ assert.ok(containedStart >= 0 && containedEnd > containedStart);
932
+ assert.match(contained, /inputSchema: z\.strictObject\(/);
933
+ assert.doesNotMatch(contained, /worker_id:\s*z\./);
934
+ assert.doesNotMatch(contained, /executable:\s*z\./);
935
+ assert.doesNotMatch(contained, /argv:\s*z\./);
924
936
  assert.match(launcher, /argc != 7/);
925
937
  assert.match(launcher, /invalid fixed argument contract/);
926
938
  assert.match(helper, /HasExactContainedWorkerArguments/);
@@ -82,11 +82,15 @@ test('controller keeps privileged policy optional and propagates it only to the
82
82
 
83
83
  test('Windows host policy bootstrap pins executables and creates only explicit local roots', async () => {
84
84
  const init = await readFile(fileURLToPath(new URL('../scripts/init-host-policy.ps1', import.meta.url)), 'utf8');
85
- assert.match(init, /deadbyte\.host-policy\.v1/);
85
+ assert.match(init, /deadbyte\.host-policy\.v2/);
86
86
  assert.match(init, /Get-FileHash/);
87
87
  assert.match(init, /powershell_script/);
88
88
  assert.match(init, /cmd_script/);
89
89
  assert.match(init, /node_script/);
90
+ assert.match(init, /powershell_inline/);
91
+ assert.match(init, /cmd_inline/);
92
+ assert.match(init, /node_inline/);
93
+ assert.match(init, /native_exec/);
90
94
  assert.match(init, /D:\\Projects/);
91
95
  assert.match(init, /State\s+: DISARMED/);
92
96
  });
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.15.0');
108
+ assert.equal(result.receipt.runtime.package_version, '0.15.2');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
@@ -0,0 +1,91 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R45 real max-runtime full stdio catalog is exactly 82 with skill and four Desktop primitives while authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r45-full-catalog',version:'0.15.1'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'full',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'1',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ const {R45_EXPECTED_TOOLS}=await import('../src/remote-mcp-r45-parity-client.mjs');
79
+ assert.equal(names.length,82,'expected exact full R45 catalog 82, got '+names.length+': '+names.join(','));
80
+ assert.deepEqual(names,R45_EXPECTED_TOOLS);
81
+ for(const name of ['machine_hardware_query','desktop_status','desktop_observe','desktop_target','desktop_action',
82
+ 'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
83
+ for(const tool of listed.tools) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
84
+ const catalog_sha256=sha(Buffer.from(JSON.stringify(names),'utf8'));
85
+ console.log(JSON.stringify({tool_count:names.length,catalog_sha256,desktop_tools:4,skill:true,authority:'DISARMED'}));
86
+ } finally {
87
+ await client.close().catch(()=>{});
88
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
89
+ await f.cleanup();
90
+ }
91
+ });
@@ -0,0 +1,92 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R46 real max-runtime full stdio catalog is exactly 83 with skill and four Desktop primitives while authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r46-full-catalog',version:'0.15.2'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'full',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'1',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ const {R45_EXPECTED_TOOLS}=await import('../src/remote-mcp-r45-parity-client.mjs');
79
+ const R46_EXPECTED_TOOLS=[...R45_EXPECTED_TOOLS,'process_session_write'].sort();
80
+ assert.equal(names.length,83,'expected exact full R46 catalog 83, got '+names.length+': '+names.join(','));
81
+ assert.deepEqual(names,R46_EXPECTED_TOOLS);
82
+ for(const name of ['machine_hardware_query','desktop_status','desktop_observe','desktop_target','desktop_action',
83
+ 'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
84
+ for(const tool of listed.tools) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
85
+ const catalog_sha256=sha(Buffer.from(JSON.stringify(names),'utf8'));
86
+ console.log(JSON.stringify({tool_count:names.length,catalog_sha256,desktop_tools:4,skill:true,authority:'DISARMED'}));
87
+ } finally {
88
+ await client.close().catch(()=>{});
89
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
90
+ await f.cleanup();
91
+ }
92
+ });
@@ -0,0 +1,44 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { generateTrustKeypair } from '../src/trust.mjs';
7
+ import { loadCodingPolicy } from '../src/coding-plane.mjs';
8
+ import { createObservationRuntime, verifyObservationReceiptFile } from '../src/observation-runtime.mjs';
9
+
10
+ async function fixture(){
11
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-hardware-query-r45-'));
12
+ const project=path.join(root,'project');await mkdir(project);
13
+ const policyPath=path.join(root,'coding-policy.json');
14
+ await writeFile(policyPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
15
+ lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'evidence'),
16
+ roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},commands:{},
17
+ max_read_bytes:65536,max_write_bytes:65536}));
18
+ const policy=await loadCodingPolicy(policyPath);
19
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
20
+ const calls=[];
21
+ const hardwareSnapshotProvider=async query=>{calls.push(query);return {schema:'deadbyte.hardware-query.v1',query,
22
+ data:query==='cpu'?{brand:'TEST CPU',logical_processors:16}:{fixture:true}};};
23
+ const runtime=createObservationRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath,
24
+ stateRoot:root,processSnapshotProvider:async()=>[],systemSnapshotProvider:async()=>({hostname:'x',platform:'win32',architecture:'x64',
25
+ uptime_seconds:1,total_memory_bytes:1,free_memory_bytes:1,cpu_count:1,load_average:[0,0,0]}),
26
+ ownedRuntimePidProvider:async()=>new Set(),hardwareSnapshotProvider});
27
+ return {root,policy,keys,runtime,calls};
28
+ }
29
+
30
+ test('R45 hardware query is typed read-only observation with signed evidence and no host authority',async()=>{
31
+ const f=await fixture();
32
+ const out=await f.runtime.hardwareQuery({query:'cpu'});
33
+ assert.equal(out.query,'cpu');assert.equal(out.data.brand,'TEST CPU');assert.deepEqual(f.calls,['cpu']);
34
+ assert.match(out.request_sha256,/^[0-9a-f]{64}$/);assert.match(out.result_head_sha256,/^[0-9a-f]{64}$/);
35
+ assert.match(out.receipt_id,/^[0-9a-f]{24}$/);assert.match(out.receipt_sha256,/^[0-9a-f]{64}$/);
36
+ const verified=await verifyObservationReceiptFile({policy:f.policy,verifyKeyPath:f.keys.publicKeyPath,receiptId:out.receipt_id});
37
+ assert.equal(verified.ok,true);assert.equal(verified.signature_valid,true);assert.equal(verified.kind,'machine_hardware_query');
38
+ });
39
+
40
+ test('R45 hardware query rejects untyped arbitrary query names before provider dispatch',async()=>{
41
+ const f=await fixture();
42
+ await assert.rejects(()=>f.runtime.hardwareQuery({query:'run_powershell'}),/hardware query/i);
43
+ assert.deepEqual(f.calls,[]);
44
+ });
@@ -0,0 +1,67 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {
8
+ HOST_GRANT_SCHEMA, HOST_POLICY_SCHEMA_V2, hostExecInline, hostExecNative, loadHostPolicy
9
+ } from '../src/host-plane.mjs';
10
+
11
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
12
+
13
+ async function fixture(){
14
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-host-inline-r45-'));
15
+ const root=path.join(dir,'root');await mkdir(root);
16
+ const arm=path.join(dir,'arm.json'), policyPath=path.join(dir,'policy.json');
17
+ const exeSha=sha(await readFile(process.execPath));
18
+ const raw={
19
+ schema:HOST_POLICY_SCHEMA_V2,enabled:true,arm_file:arm,
20
+ roots:{core:{path:root,read:true,write:true,exec:true}},
21
+ commands:{
22
+ node_inline:{kind:'inline',executable:process.execPath,executable_sha256:exeSha,root_ids:['core'],
23
+ prefix_args:['-e'],timeout_ms:5000,max_output_bytes:65536,max_command_chars:32768}
24
+ },
25
+ native_exec:{enabled:true,root_ids:['core'],timeout_ms:5000,max_output_bytes:65536,max_args:64,max_arg_chars:8192},
26
+ max_read_bytes:262144,max_write_bytes:1048576
27
+ };
28
+ await writeFile(policyPath,JSON.stringify(raw,null,2));
29
+ const policy=await loadHostPolicy(policyPath);
30
+ await writeFile(arm,JSON.stringify({schema:HOST_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,
31
+ instance_nonce:randomBytes(32).toString('hex'),armed_at_utc:new Date().toISOString()},null,2));
32
+ return {dir,root,arm,policy,exeSha};
33
+ }
34
+
35
+ test('R45 host inline executes in argv memory with zero staging artifact and hashes command/output',async t=>{
36
+ const f=await fixture();t.after(()=>rm(f.dir,{recursive:true,force:true}));
37
+ const before=(await readdir(f.root)).sort();
38
+ const command='process.stdout.write("INLINE_OK")';
39
+ const out=await hostExecInline(f.policy,{commandId:'node_inline',rootId:'core',command,cwd:'.',timeoutMs:2000});
40
+ const after=(await readdir(f.root)).sort();
41
+ assert.deepEqual(after,before,'inline execution must not stage a script file');
42
+ assert.equal(out.exit_code,0);assert.equal(out.stdout,'INLINE_OK');assert.equal(out.stderr,'');
43
+ assert.equal(out.command_sha256,sha(Buffer.from(command,'utf8')));
44
+ assert.equal(out.stdout_sha256,sha(Buffer.from('INLINE_OK','utf8')));
45
+ assert.equal(out.stderr_sha256,sha(Buffer.alloc(0)));
46
+ assert.match(out.argv_sha256,/^[0-9a-f]{64}$/);assert.equal(out.executable_sha256,f.exeSha);
47
+ });
48
+
49
+ test('R45 native argv execution accepts arbitrary absolute hash-pinned executable only while Host is armed',async t=>{
50
+ const f=await fixture();t.after(()=>rm(f.dir,{recursive:true,force:true}));
51
+ const before=(await readdir(f.root)).sort();
52
+ const out=await hostExecNative(f.policy,{rootId:'core',executable:process.execPath,executableSha256:f.exeSha,
53
+ args:['-e','process.stdout.write("NATIVE_OK")'],cwd:'.',timeoutMs:2000});
54
+ assert.equal(out.exit_code,0);assert.equal(out.stdout,'NATIVE_OK');
55
+ assert.deepEqual((await readdir(f.root)).sort(),before,'native argv execution must not create staging files');
56
+ await assert.rejects(()=>hostExecNative(f.policy,{rootId:'core',executable:process.execPath,executableSha256:'f'.repeat(64),
57
+ args:['--version'],cwd:'.'}),/executable hash mismatch/i);
58
+ });
59
+
60
+ test('R45 inline execution observes authority revocation during a live child and fails closed',async t=>{
61
+ const f=await fixture();t.after(()=>rm(f.dir,{recursive:true,force:true}));
62
+ const running=hostExecInline(f.policy,{commandId:'node_inline',rootId:'core',
63
+ command:'setTimeout(()=>process.stdout.write("SHOULD_NOT_COMPLETE"),1500)',cwd:'.',timeoutMs:4000,authorityPollMs:50});
64
+ await new Promise(r=>setTimeout(r,150));
65
+ await rm(f.arm,{force:true});
66
+ await assert.rejects(()=>running,/host authority changed during operation/i);
67
+ });
@@ -11,7 +11,7 @@ function runtime() {
11
11
  return {
12
12
  stat: async () => ({ status:'ok', root_id:'core', path:'a.txt', kind:'file', bytes:1, mtime_utc:new Date(0).toISOString(), sha256:hex('a'), identity_sha256:hex('b') }),
13
13
  list: async () => ({ status:'ok', root_id:'core', path:'.', entries:[], next_cursor:null }),
14
- read: async () => ({ status:'ok', root_id:'core', path:'a.txt', offset:0, next_offset:1, file_size:1, eof:true, data_base64:'YQ==', data_sha256:hex('a'), file_sha256:hex('a') }),
14
+ read: async () => ({ status:'ok', root_id:'core', path:'a.txt', offset:0, next_offset:1, file_size:1, eof:true, data_base64:'YQ==', data_sha256:hex('a'), file_sha256:hex('a'), timeout_ms:30000 }),
15
15
  write: async () => ({ status:'committed', root_id:'core', path:'a.txt', previous_sha256:null, sha256:hex('a'), bytes:1, created:true, receipt_id:'a'.repeat(24), receipt_sha256:hex('b'), request_sha256:hex('c') }),
16
16
  editExact: async () => ({ status:'committed', root_id:'core', path:'a.txt', previous_sha256:hex('a'), sha256:hex('b'), bytes:1, replacements:1, receipt_id:'a'.repeat(24), receipt_sha256:hex('b'), request_sha256:hex('c') }),
17
17
  mkdir: async () => ({ status:'committed', root_id:'core', path:'d', identity_sha256:hex('a'), receipt_id:'a'.repeat(24), receipt_sha256:hex('b'), request_sha256:hex('c') }),
@@ -35,6 +35,9 @@ test('R31 registers exactly eight bounded machine filesystem tools', () => {
35
35
  test('R31 machine filesystem schemas expose root-relative IDs/preconditions only', () => {
36
36
  const server = new FakeServer();
37
37
  registerMachineFsTools(server, { runtime: runtime() });
38
+ const read = server.tools.get('machine_fs_read').spec.inputSchema;
39
+ assert.equal(read.safeParse({ root_id:'core', path:'a.txt', offset:0, max_bytes:65536, hash_file:false, timeout_ms:30000 }).success, true);
40
+ assert.equal(read.safeParse({ root_id:'core', path:'a.txt', timeout_ms:99 }).success, false);
38
41
  const write = server.tools.get('machine_fs_write').spec.inputSchema;
39
42
  const move = server.tools.get('machine_fs_move').spec.inputSchema;
40
43
  const del = server.tools.get('machine_fs_delete').spec.inputSchema;
@@ -10,7 +10,7 @@ import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machi
10
10
 
11
11
  const sha = bytes => createHash('sha256').update(bytes).digest('hex');
12
12
 
13
- async function fixture() {
13
+ async function fixture({ readWorkerPath = undefined } = {}) {
14
14
  const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-'));
15
15
  const project = path.join(root, 'project');
16
16
  await mkdir(project);
@@ -30,7 +30,7 @@ async function fixture() {
30
30
  privateKeyPath: path.join(root, 'private.pem'), publicKeyPath: path.join(root, 'public.pem')
31
31
  });
32
32
  const runtime = createMachineFsRuntime({
33
- policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath
33
+ policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath, readWorkerPath
34
34
  });
35
35
  return { root, project, policy, keys, runtime };
36
36
  }
@@ -52,6 +52,7 @@ test('R31 machine fs supports exact mkdir/write/stat/list/range-read/edit/move/d
52
52
  const read = await f.runtime.read({ rootId: 'project', relativePath: 'dir/a.txt', offset: 6, maxBytes: 4 });
53
53
  assert.equal(Buffer.from(read.data_base64, 'base64').toString('utf8'), 'beta');
54
54
  assert.equal(read.next_offset, 10);
55
+ assert.equal(read.timeout_ms, 30000);
55
56
  const edited = await f.runtime.editExact({
56
57
  rootId: 'project', relativePath: 'dir/a.txt', expectedSha256: written.sha256,
57
58
  oldString: 'beta', newString: 'BETA', expectedReplacements: 1
@@ -103,3 +104,18 @@ test('R31 machine fs fails closed on traversal, denied paths and stale source id
103
104
  assert.equal(verified.signature_valid, true);
104
105
  assert.equal(verified.operation, 'machine_fs_mkdir');
105
106
  });
107
+
108
+
109
+ test('R46 machine fs read timeout kills a hung read worker instead of abandoning live I/O', async () => {
110
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-hang-worker-'));
111
+ const worker = path.join(root, 'hang-worker.mjs');
112
+ await writeFile(worker, "setInterval(()=>{},1000);\n");
113
+ const f = await fixture({ readWorkerPath: worker });
114
+ await writeFile(path.join(f.project, 'hang.txt'), 'never read');
115
+ const started = Date.now();
116
+ await assert.rejects(
117
+ () => f.runtime.read({ rootId:'project', relativePath:'hang.txt', offset:0, maxBytes:16, timeoutMs:100 }),
118
+ /timed out after 100ms/
119
+ );
120
+ assert.ok(Date.now() - started < 3000, 'hung worker was not terminated promptly');
121
+ });
@@ -51,6 +51,7 @@ async function fixture() {
51
51
  env: {
52
52
  ...getDefaultEnvironment(), DEADBYTE_STATE_ROOT: root, DEADBYTE_MACHINE_RUNTIME: '1',
53
53
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath, DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
54
+ DEADBYTE_PRIVILEGED_POLICY: '',
54
55
  DEADBYTE_AGENT_RUNTIME: '0', DEADBYTE_CODING_RUNTIME: '0', DEADBYTE_AUTONOMOUS_RUNTIME: '0'
55
56
  }, stderr: 'pipe'
56
57
  });