deadbyte-mcp 0.15.1 → 0.15.3

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 (96) hide show
  1. package/MANIFEST.SHA256 +95 -64
  2. package/PROVENANCE.json +6 -5
  3. package/README.txt +36 -20
  4. package/RELEASE.json +24 -8
  5. package/SBOM.cdx.json +3 -3
  6. package/controller/README.TXT +2 -2
  7. package/controller/deadbyte-controller.ps1 +50 -2
  8. package/controller/deadbyte-process-policy.json +63 -1
  9. package/docs/ARCHITECTURE.md +3 -4
  10. package/package.json +88 -86
  11. package/scripts/autonomous-build.ps1 +1 -0
  12. package/scripts/autonomous-tests.ps1 +1 -0
  13. package/scripts/check-agent-backend-r46.mjs +77 -0
  14. package/scripts/check-atomic-resource-r46.mjs +84 -0
  15. package/scripts/check-autonomous-race-r46.mjs +160 -0
  16. package/scripts/check-coding-task-deadline-r46.mjs +59 -0
  17. package/scripts/check-deadline-cancellation-r46.mjs +49 -0
  18. package/scripts/check-process-deadline-r46.mjs +52 -0
  19. package/scripts/check-r46-background.mjs +5 -0
  20. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  21. package/scripts/extract-r46-failures.mjs +5 -0
  22. package/scripts/final-closure-r46.mjs +98 -0
  23. package/scripts/final-closure-verify-r46.mjs +119 -0
  24. package/scripts/full-regression-r46.mjs +28 -0
  25. package/scripts/gate-windows-r46.ps1 +58 -0
  26. package/scripts/inspect-r46-active.mjs +2 -0
  27. package/scripts/r47-core-failures.mjs +1 -0
  28. package/scripts/refresh-process-policy.mjs +64 -7
  29. package/scripts/release-manifest.mjs +1 -1
  30. package/scripts/release-parity-r46.mjs +187 -0
  31. package/scripts/run-r46-full-regression-background.mjs +7 -0
  32. package/scripts/run-r46-windows-gate-background.mjs +6 -0
  33. package/scripts/run-r46-windows-gate-background.ps1 +10 -0
  34. package/scripts/windows-gate-evidence-r46.mjs +111 -0
  35. package/scripts/workload-contract-regression-r47.mjs +14 -0
  36. package/scripts/workload-stream-regression.mjs +16 -0
  37. package/scripts/workload-substrate-r47-regression.mjs +12 -0
  38. package/src/agent-backend-r44.mjs +75 -10
  39. package/src/atomic-mutation.mjs +55 -0
  40. package/src/autonomous-mcp-tools.mjs +4 -3
  41. package/src/autonomous-output-contracts.mjs +16 -3
  42. package/src/autonomous-runtime.mjs +83 -30
  43. package/src/capability-ledger.mjs +1 -0
  44. package/src/coding-mcp-tools.mjs +2 -2
  45. package/src/coding-output-contracts.mjs +18 -4
  46. package/src/coding-plane.mjs +3 -24
  47. package/src/coding-search.mjs +11 -2
  48. package/src/coding-task-runtime.mjs +41 -6
  49. package/src/compact-gateway-mcp-tools.mjs +7 -5
  50. package/src/deadbyte-cli.mjs +7 -3
  51. package/src/deadline-cancellation.mjs +83 -0
  52. package/src/machine-fs-mcp-tools.mjs +3 -3
  53. package/src/machine-fs-output-contracts.mjs +1 -1
  54. package/src/machine-fs-read-worker.mjs +46 -0
  55. package/src/machine-fs-smoke-client.mjs +1 -1
  56. package/src/machine-fs.mjs +66 -33
  57. package/src/mcp-server.mjs +10 -0
  58. package/src/mcp-smoke-client.mjs +4 -1
  59. package/src/observation-runtime.mjs +25 -4
  60. package/src/observation-smoke-client.mjs +1 -1
  61. package/src/process-mcp-tools.mjs +29 -8
  62. package/src/process-output-contracts.mjs +43 -3
  63. package/src/process-policy.mjs +3 -1
  64. package/src/process-runtime.mjs +152 -23
  65. package/src/process-smoke-client.mjs +67 -22
  66. package/src/release-generation.mjs +22 -3
  67. package/src/remote-device-r44.mjs +53 -15
  68. package/src/remote-mcp-smoke-client.mjs +1 -1
  69. package/src/resource-identity.mjs +59 -0
  70. package/src/runtime-update.mjs +526 -162
  71. package/src/stdio-console-guard.mjs +24 -0
  72. package/src/version.mjs +1 -1
  73. package/src/workload-substrate.mjs +87 -0
  74. package/test/cli-entrypoint.test.mjs +3 -3
  75. package/test/coding-runtime.test.mjs +12 -0
  76. package/test/coding-search-r36.test.mjs +7 -0
  77. package/test/compact-gateway-r36.test.mjs +28 -0
  78. package/test/core.test.mjs +1 -1
  79. package/test/full-max-catalog-r46.test.mjs +92 -0
  80. package/test/helpers/autonomous-r34-fixture.mjs +10 -1
  81. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  82. package/test/machine-fs-runtime.test.mjs +18 -2
  83. package/test/observation-runtime.test.mjs +29 -2
  84. package/test/process-mcp-tools.test.mjs +27 -6
  85. package/test/process-policy-refresh.test.mjs +87 -10
  86. package/test/process-runtime.test.mjs +129 -2
  87. package/test/process-supervision.test.mjs +4 -2
  88. package/test/production-docs.test.mjs +3 -3
  89. package/test/r33-finalization.test.mjs +3 -3
  90. package/test/r45-finalization.test.mjs +1 -1
  91. package/test/release-generation.test.mjs +25 -11
  92. package/test/release-version.test.mjs +99 -44
  93. package/test/remote-device-r44.test.mjs +75 -0
  94. package/test/remote-r24-catalog.test.mjs +2 -2
  95. package/test/runtime-self-update.test.mjs +118 -1
  96. package/test/workload-substrate.test.mjs +22 -0
@@ -0,0 +1,24 @@
1
+ import { format } from 'node:util';
2
+
3
+ export function installStdioConsoleGuard({ stderr = process.stderr } = {}) {
4
+ if (!stderr || typeof stderr.write !== 'function') throw new Error('stdio console guard requires writable stderr');
5
+ const original = {
6
+ log: console.log,
7
+ info: console.info,
8
+ debug: console.debug
9
+ };
10
+ const write = (level, args) => {
11
+ const text = format(...args);
12
+ stderr.write(`DEADBYTE MCP ${level.toUpperCase()} ${text}${text.endsWith('\n') ? '' : '\n'}`);
13
+ };
14
+ console.log = (...args) => write('log', args);
15
+ console.info = (...args) => write('info', args);
16
+ console.debug = (...args) => write('debug', args);
17
+ return Object.freeze({
18
+ restore() {
19
+ console.log = original.log;
20
+ console.info = original.info;
21
+ console.debug = original.debug;
22
+ }
23
+ });
24
+ }
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.1';
1
+ export const DEADBYTE_VERSION = '0.15.2';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -0,0 +1,87 @@
1
+ const HEX64=/^[0-9a-f]{64}$/;
2
+
3
+ function boundedInt(value,name,min,max){
4
+ if(!Number.isInteger(value)||value<min||value>max) throw new Error(`${name} must be in [${min}, ${max}]`);
5
+ }
6
+
7
+ export function createSequenceStream({streamId,generation=1,maxItems=10000,maxBytes=8*1024*1024}={}){
8
+ if(typeof streamId!=='string'||streamId.length<1) throw new Error('streamId required');
9
+ boundedInt(generation,'generation',1,Number.MAX_SAFE_INTEGER);
10
+ boundedInt(maxItems,'maxItems',1,1_000_000);
11
+ boundedInt(maxBytes,'maxBytes',1,1024*1024*1024);
12
+ const items=[];
13
+ let nextSeq=1;
14
+ let retainedBytes=0;
15
+ let logicalBytes=0;
16
+ let droppedItems=0;
17
+ let droppedBytes=0;
18
+ let status='open';
19
+
20
+ const sizeOf=(value,explicitBytes=null)=>{
21
+ if(Number.isInteger(explicitBytes)&&explicitBytes>=0) return explicitBytes;
22
+ return Buffer.byteLength(JSON.stringify(value),'utf8');
23
+ };
24
+ const coverage=()=>({
25
+ logical_first_seq:1,
26
+ logical_last_seq:nextSeq-1,
27
+ retained_first_seq:items[0]?.seq??nextSeq,
28
+ retained_last_seq:items.at(-1)?.seq??0,
29
+ complete:droppedItems===0,
30
+ truncated:droppedItems>0,
31
+ missing_ranges:droppedItems>0?[{from:1,to:items[0]?.seq-1??0}]:[],
32
+ logical_item_count:(nextSeq-1),
33
+ retained_item_count:items.length,
34
+ logical_bytes:logicalBytes,
35
+ retained_bytes:retainedBytes,
36
+ dropped_items:droppedItems,
37
+ dropped_bytes:droppedBytes,
38
+ status
39
+ });
40
+
41
+ function append(value,{producer='default',channel=null,sizeBytes=null}={}){
42
+ if(status!=='open') throw new Error('stream is not open');
43
+ const item={seq:nextSeq++,producer,channel,value,_sizeBytes:Number.isInteger(sizeBytes)&&sizeBytes>=0 ? sizeBytes : null};
44
+ const bytes=item._sizeBytes ?? sizeOf(item);
45
+ item._sizeBytes=bytes;
46
+ items.push(item); retainedBytes+=bytes; logicalBytes+=bytes;
47
+ while(items.length>maxItems||retainedBytes>maxBytes){
48
+ const evicted=items.shift();
49
+ const evictedBytes=evicted._sizeBytes ?? sizeOf(evicted);
50
+ retainedBytes-=evictedBytes;
51
+ droppedItems+=1; droppedBytes+=evictedBytes;
52
+ }
53
+ return item;
54
+ }
55
+ function read(fromSeq=1,limit=100){
56
+ boundedInt(fromSeq,'fromSeq',1,Number.MAX_SAFE_INTEGER);
57
+ boundedInt(limit,'limit',1,10000);
58
+ const c=coverage();
59
+ if(fromSeq<c.retained_first_seq) return {items:[],coverage:c,missing:true,from_seq:fromSeq};
60
+ const out=items.filter(item=>item.seq>=fromSeq).slice(0,limit);
61
+ return {items:out,coverage:c,missing:false,from_seq:fromSeq,next_seq:out.length?out.at(-1).seq+1:fromSeq};
62
+ }
63
+ function snapshot(){return Object.freeze({stream_id:streamId,generation,next_seq:nextSeq,logical_bytes:logicalBytes,retained_first_seq:coverage().retained_first_seq,retained_last_seq:coverage().retained_last_seq,status});}
64
+ function delta(previous){
65
+ if(!previous||previous.stream_id!==streamId||previous.generation!==generation) throw new Error('stream snapshot identity mismatch');
66
+ const from=previous.next_seq;
67
+ return read(from,10000);
68
+ }
69
+ function close(){status='closed';}
70
+ return Object.freeze({streamId,generation,append,read,snapshot,delta,coverage,close});
71
+ }
72
+
73
+ export function createJournalStream({streamId,generation=1,events=[],maxItems=10000,maxBytes=8*1024*1024,producer='journal',channel='event'}={}){
74
+ const stream=createSequenceStream({streamId,generation,maxItems,maxBytes});
75
+ for(const event of events){
76
+ const value=event?.payload && typeof event.payload==='object'
77
+ ? { seq:event.seq, type:event.type, payload:event.payload, event_sha256:event.event_sha256 }
78
+ : event;
79
+ stream.append(value,{producer,channel,sizeBytes:Buffer.byteLength(JSON.stringify(value),'utf8')});
80
+ }
81
+ return stream;
82
+ }
83
+
84
+ export function assertSequenceStreamSnapshot(value){
85
+ if(!value||typeof value!=='object'||typeof value.stream_id!=='string'||!Number.isInteger(value.next_seq)) throw new Error('invalid stream snapshot');
86
+ return value;
87
+ }
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
21
21
  for(const arg of ['--version','-v','version']){
22
22
  const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
23
23
  assert.equal(result.status,0,result.stderr);
24
- assert.equal(result.stdout.trim(),'0.15.1');
24
+ assert.equal(result.stdout.trim(),'0.15.2');
25
25
  }
26
26
  });
27
27
 
@@ -50,6 +50,6 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
50
50
 
51
51
  test('README makes npx the primary operator UX',async()=>{
52
52
  const readme=await readFile(path.join(root,'README.txt'),'utf8');
53
- assert.match(readme,/npx -y deadbyte-mcp@0\.15\.1 remote/);
54
- assert.match(readme,/npx -y deadbyte-mcp@0\.15\.1 audit verify/);
53
+ assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 remote/);
54
+ assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 audit verify/);
55
55
  });
@@ -147,6 +147,18 @@ test('durable coding task chains edit command benchmark and verifies task receip
147
147
  const run = await f.tasks.run({ taskId: submitted.task_id, maxSteps: 8 });
148
148
  assert.equal(run.status, 'completed');
149
149
  assert.equal(run.state, 'succeeded');
150
+ const status = await f.tasks.status({ taskId: submitted.task_id });
151
+ assert.equal(status.stream_id, submitted.task_id);
152
+ assert.equal(status.stream_generation, 1);
153
+ assert.equal(status.stream_next_seq, 10);
154
+ assert.equal(status.stream_coverage.logical_item_count, 9);
155
+ assert.equal(status.stream_coverage.retained_item_count, 9);
156
+ assert.equal(status.stream_coverage.truncated, false);
157
+ const page = await f.tasks.events({ taskId: submitted.task_id, fromSeq: 4, limit: 3 });
158
+ assert.equal(page.stream_id, submitted.task_id);
159
+ assert.equal(page.events.length, 3);
160
+ assert.equal(page.events[0].seq, 4);
161
+ assert.equal(page.events[2].seq, 6);
150
162
  const verified = await f.tasks.verify({ taskId: submitted.task_id });
151
163
  assert.equal(verified.ok, true);
152
164
  assert.equal(verified.action_receipts_verified, 3);
@@ -38,6 +38,13 @@ test('R36 regex search returns multiple matches per file with deterministic sign
38
38
  assert.match(first.query_sha256,/^[0-9a-f]{64}$/);
39
39
  assert.match(first.snapshot_sha256,/^[0-9a-f]{64}$/);
40
40
  assert.match(first.page_sha256,/^[0-9a-f]{64}$/);
41
+ assert.equal(first.stream_id,first.query_sha256);
42
+ assert.equal(first.stream_generation,1);
43
+ assert.equal(first.stream_next_seq,6);
44
+ assert.equal(first.stream_coverage.logical_item_count,5);
45
+ assert.equal(first.stream_coverage.retained_item_count,5);
46
+ assert.equal(first.stream_coverage.complete,true);
47
+ assert.equal(first.stream_coverage.truncated,false);
41
48
  assert.ok(first.next_cursor);
42
49
  const second=await f.search({rootId:'core',path:'.',target:'content',mode:'regex',pattern:'foo\\d',caseSensitive:false,pageSize:2,maxResults:20,cursor:first.next_cursor});
43
50
  assert.equal(second.matches.length,2);
@@ -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
+ });
@@ -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.1');
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,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_cancel','process_session_write'].sort();
80
+ assert.equal(names.length,84,'expected exact full R46 catalog 84, 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
+ });
@@ -75,8 +75,17 @@ export async function createR34Fixture({authority='armed',withFailingProfile=fal
75
75
  const nativeDiffRuntime = nativeDiffRuntimeFactory ? nativeDiffRuntimeFactory(baseNativeDiffRuntime) : baseNativeDiffRuntime;
76
76
  const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime,desktopMemoryProviderOverride=desktopMemoryProvider,memoryProviderOverride=memoryProvider,desktopRuntimeOverride=desktopRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,machineFsRuntime,
77
77
  desktopRuntime:desktopRuntimeOverride,desktopMemoryProvider:desktopMemoryProviderOverride,memoryProvider:memoryProviderOverride,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
78
+ const rawRuntime=createRuntime();
79
+ const runtime={...rawRuntime,events:async ({goalId,fromSeq=1,limit=100})=>{
80
+ const page=await rawRuntime.events({goalId,fromSeq,limit});
81
+ const eventsDir=path.join(policy.evidence_dir,'goals',goalId,'events');
82
+ const names=(await (await import('node:fs/promises')).readdir(eventsDir)).filter(name=>/^[0-9]{6}\.json$/.test(name)).sort();
83
+ const raw=[];
84
+ for(const name of names.slice(fromSeq-1,fromSeq-1+limit)) raw.push(JSON.parse(await readFile(path.join(eventsDir,name),'utf8')));
85
+ return {...page,events:raw};
86
+ }};
78
87
  return {
79
- root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime:createRuntime(),
88
+ root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime,
80
89
  initial_sha256:sha(await readFile(target)),arm,disarm,
81
90
  cleanup:()=>rm(root,{recursive:true,force:true})
82
91
  };
@@ -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
+ });
@@ -7,7 +7,7 @@ import { generateTrustKeypair } from '../src/trust.mjs';
7
7
  import { loadCodingPolicy } from '../src/coding-plane.mjs';
8
8
  import { createObservationRuntime, verifyObservationReceiptFile } from '../src/observation-runtime.mjs';
9
9
 
10
- async function fixture() {
10
+ async function fixture(runtimeOptions={}) {
11
11
  const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-observation-'));
12
12
  const project = path.join(root, 'project');
13
13
  await mkdir(project); await mkdir(path.join(project, 'blocked'));
@@ -36,7 +36,8 @@ async function fixture() {
36
36
  const runtime = createObservationRuntime({
37
37
  policy, signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath,
38
38
  processRuntime, stateRoot:root, processSnapshotProvider, systemSnapshotProvider,
39
- ownedRuntimePidProvider: async () => new Set([111])
39
+ ownedRuntimePidProvider: async () => new Set([111]),
40
+ ...runtimeOptions
40
41
  });
41
42
  return { root, project, policy, keys, runtime, getProcessCalls:() => processCalls };
42
43
  }
@@ -121,3 +122,29 @@ test('R32 system observation is timestamped and signed without mutation authorit
121
122
  assert.equal(verified.ok,true);
122
123
  assert.equal(verified.result_head_sha256,status.result_head_sha256);
123
124
  });
125
+
126
+
127
+ test('R46 observation search sessions expire after bounded terminal TTL and free capacity', async () => {
128
+ let now=1000;
129
+ const f=await fixture({searchSessionTtlMs:50,maxSearchSessions:1,clockMs:()=>now});
130
+ await writeFile(path.join(f.project,'ttl.txt'),'needle\n');
131
+ const first=await f.runtime.searchStart({rootId:'project',relativePath:'.',pattern:'needle',maxResults:10,maxFiles:10,maxBytes:65536});
132
+ const done=await waitTerminal(f.runtime,first.search_id);
133
+ assert.equal(done.state,'completed');
134
+ now+=60;
135
+ await assert.rejects(f.runtime.searchStatus({searchId:first.search_id}),/unknown or expired search/);
136
+ const second=await f.runtime.searchStart({rootId:'project',relativePath:'.',pattern:'needle',maxResults:10,maxFiles:10,maxBytes:65536});
137
+ assert.match(second.search_id,/^[0-9a-f]{64}$/);
138
+ });
139
+
140
+ test('R46 observation search hard cap fails closed while all retained sessions are active', async () => {
141
+ const f=await fixture({searchSessionTtlMs:5000,maxSearchSessions:2});
142
+ await writeFile(path.join(f.project,'cap.txt'),'needle\n');
143
+ const a=await f.runtime.searchStart({rootId:'project',relativePath:'.',pattern:'needle'});
144
+ const b=await f.runtime.searchStart({rootId:'project',relativePath:'.',pattern:'needle'});
145
+ assert.match(a.search_id,/^[0-9a-f]{64}$/);
146
+ assert.match(b.search_id,/^[0-9a-f]{64}$/);
147
+ await assert.rejects(f.runtime.searchStart({rootId:'project',relativePath:'.',pattern:'needle'}),/session limit reached/);
148
+ await waitTerminal(f.runtime,a.search_id).catch(()=>{});
149
+ await waitTerminal(f.runtime,b.search_id).catch(()=>{});
150
+ });
@@ -1,4 +1,4 @@
1
- import test from 'node:test';
1
+ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { registerProcessTools } from '../src/process-mcp-tools.mjs';
4
4
 
@@ -12,7 +12,10 @@ function session(state = 'running') {
12
12
  return {
13
13
  session_id: hex('a'), profile_id: 'hold', root_id: 'core', cwd: '.', child_pid: 1234,
14
14
  creation_filetime: '133700000000000000', state, exit_code: state === 'running' ? null : 0,
15
- earliest_offset: 0, stream_head: 4, stream_sha256: hex('b'), journal_head_sha256: hex('c')
15
+ earliest_offset: 0, stream_head: 4, stream_sha256: hex('b'),
16
+ stream_id: hex('f'), stream_generation: 1, stream_next_seq: 2,
17
+ stream_coverage: { logical_first_seq: 1, logical_last_seq: 1, retained_first_seq: 1, retained_last_seq: 1, complete: true, truncated: false, missing_ranges: [], logical_item_count: 1, retained_item_count: 1, logical_bytes: 4, retained_bytes: 4, dropped_items: 0, dropped_bytes: 0, status: 'open' },
18
+ termination: null, deadline: null, journal_head_sha256: hex('c')
16
19
  };
17
20
  }
18
21
 
@@ -20,18 +23,19 @@ function runtime() {
20
23
  return {
21
24
  start: async () => session(), read: async () => ({
22
25
  session_id: hex('a'), offset: 0, next_offset: 4, earliest_offset: 0, stream_head: 4,
23
- stream_sha256: hex('b'), truncated: false, data_base64: 'VEVTVA==', state: 'running', exit_code: null
26
+ stream_sha256: hex('b'), truncated: false, wait_satisfied: true, data_base64: 'VEVTVA==', state: 'running', exit_code: null
24
27
  }),
25
28
  status: async () => session(), list: async () => ({ sessions: [session()] }),
29
+ write: async ({ input, appendNewline }) => ({ logical_bytes: Buffer.byteLength(input + (appendNewline ? '\r\n' : '')), logical_sha256: hex('e'), append_newline: appendNewline, session: session() }),
26
30
  action: async ({ actionId }) => ({ action_id: actionId, logical_bytes: 4, logical_sha256: hex('d'), session: session() }),
27
31
  terminate: async () => session('terminated')
28
32
  };
29
33
  }
30
- test('R32 registers exactly six bounded process supervision tools', () => {
34
+ test('R46 registers exactly seven bounded process supervision tools', () => {
31
35
  const server = new FakeServer();
32
36
  registerProcessTools(server, { runtime: runtime() });
33
37
  assert.deepEqual([...server.tools.keys()].sort(), [
34
- 'process_session_action','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate'
38
+ 'process_session_action','process_session_cancel','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate','process_session_write'
35
39
  ]);
36
40
  for (const [name, tool] of server.tools) {
37
41
  assert.ok(tool.spec.inputSchema, `${name} input schema missing`);
@@ -41,7 +45,7 @@ test('R32 registers exactly six bounded process supervision tools', () => {
41
45
  }
42
46
  });
43
47
 
44
- test('R32 process tool inputs expose IDs and bounds but no executable or raw input field', () => {
48
+ test('R46 process tool inputs expose guarded raw input only through process_session_write', () => {
45
49
  const server = new FakeServer();
46
50
  registerProcessTools(server, { runtime: runtime() });
47
51
  const start = server.tools.get('process_session_start').spec.inputSchema;
@@ -52,6 +56,10 @@ test('R32 process tool inputs expose IDs and bounds but no executable or raw inp
52
56
  assert.equal(start.safeParse({ profile_id: 'hold', root_id: 'core', input: 'whoami' }).success, false);
53
57
  assert.equal(terminate.safeParse({ session_id: hex('a') }).success, true);
54
58
  assert.equal(terminate.safeParse({ pid: 1234 }).success, false);
59
+ const write = server.tools.get('process_session_write').spec.inputSchema;
60
+ assert.equal(write.safeParse({ session_id: hex('a'), input: 'Write-Output 42', append_newline: true }).success, true);
61
+ assert.equal(write.safeParse({ session_id: hex('a'), input: '' }).success, false);
62
+ assert.equal(write.safeParse({ session_id: hex('a'), input: 'x', pid: 1234 }).success, false);
55
63
  assert.equal(action.safeParse({ session_id: hex('a'), action_id: 'ping' }).success, true);
56
64
  for (const forbidden of ['input','command','executable','environment','pid']) {
57
65
  assert.equal(action.safeParse({ session_id: hex('a'), action_id: 'ping', [forbidden]: 'forbidden' }).success, false);
@@ -69,3 +77,16 @@ test('R32 process action handler returns only strict logical identity and update
69
77
  assert.equal(result.structuredContent.session.state, 'running');
70
78
  assert.equal(tool.spec.outputSchema.safeParse({ ...result.structuredContent, raw_input: 'forbidden' }).success, false);
71
79
  });
80
+
81
+
82
+ test('R46 process write handler returns only input identity and session state', async () => {
83
+ const server = new FakeServer();
84
+ registerProcessTools(server, { runtime: runtime() });
85
+ const tool = server.tools.get('process_session_write');
86
+ const result = await tool.handler({ session_id: hex('a'), input: 'Write-Output 42', append_newline: true });
87
+ assert.equal(result.isError, false);
88
+ assert.deepEqual(Object.keys(result.structuredContent).sort(), ['append_newline','logical_bytes','logical_sha256','session','status']);
89
+ assert.equal(result.structuredContent.append_newline, true);
90
+ assert.equal(result.structuredContent.session.state, 'running');
91
+ assert.equal(tool.spec.outputSchema.safeParse({ ...result.structuredContent, raw_input: 'forbidden' }).success, false);
92
+ });