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.
- package/MANIFEST.SHA256 +95 -64
- package/PROVENANCE.json +6 -5
- package/README.txt +36 -20
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +3 -3
- package/controller/README.TXT +2 -2
- package/controller/deadbyte-controller.ps1 +50 -2
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/package.json +88 -86
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/deferred-slot-operation-r46.mjs +126 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/final-closure-r46.mjs +98 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/full-regression-r46.mjs +28 -0
- package/scripts/gate-windows-r46.ps1 +58 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/release-parity-r46.mjs +187 -0
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +111 -0
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/capability-ledger.mjs +1 -0
- package/src/coding-mcp-tools.mjs +2 -2
- package/src/coding-output-contracts.mjs +18 -4
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +11 -2
- package/src/coding-task-runtime.mjs +41 -6
- package/src/compact-gateway-mcp-tools.mjs +7 -5
- package/src/deadbyte-cli.mjs +7 -3
- package/src/deadline-cancellation.mjs +83 -0
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +66 -33
- package/src/mcp-server.mjs +10 -0
- package/src/mcp-smoke-client.mjs +4 -1
- package/src/observation-runtime.mjs +25 -4
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +29 -8
- package/src/process-output-contracts.mjs +43 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +152 -23
- package/src/process-smoke-client.mjs +67 -22
- package/src/release-generation.mjs +22 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/stdio-console-guard.mjs +24 -0
- package/src/version.mjs +1 -1
- package/src/workload-substrate.mjs +87 -0
- package/test/cli-entrypoint.test.mjs +3 -3
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/compact-gateway-r36.test.mjs +28 -0
- package/test/core.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +92 -0
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/observation-runtime.test.mjs +29 -2
- package/test/process-mcp-tools.test.mjs +27 -6
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +129 -2
- package/test/process-supervision.test.mjs +4 -2
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-generation.test.mjs +25 -11
- package/test/release-version.test.mjs +99 -44
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-self-update.test.mjs +118 -1
- 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
|
@@ -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.
|
|
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\.
|
|
54
|
-
assert.match(readme,/npx -y deadbyte-mcp@0\.15\.
|
|
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
|
+
});
|
package/test/core.test.mjs
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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'),
|
|
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('
|
|
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('
|
|
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
|
+
});
|