deadbyte-mcp 0.15.23 → 0.15.25
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 +49 -40
- package/PROVENANCE.json +11 -10
- package/README.txt +7 -7
- package/RELEASE.json +19 -38
- package/TEST-MATRIX.json +32 -96
- package/package.json +3 -3
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +122 -0
- package/scripts/commander-hardening-gate-r47.mjs +10 -1
- package/scripts/commander-real-performance-benchmark-r47.mjs +12 -3
- package/scripts/gate-windows-r48.ps1 +45 -0
- package/scripts/init-host-policy.ps1 +9 -1
- package/scripts/npm-release-r48.mjs +2 -1
- package/scripts/windows-gate-evidence-r48.mjs +27 -0
- package/src/atomic-mutation.mjs +12 -4
- package/src/coding-mcp-tools.mjs +15 -1
- package/src/coding-output-contracts.mjs +9 -3
- package/src/commander-office-reader-r48.mjs +36 -3
- package/src/commander-pdf-r48.mjs +36 -1
- package/src/commander-wire-adapter-r47.mjs +72 -2
- package/src/host-plane.mjs +10 -31
- package/src/machine-fs-mcp-tools.mjs +7 -1
- package/src/machine-fs-output-contracts.mjs +6 -0
- package/src/machine-fs-smoke-client.mjs +6 -17
- package/src/mcp-host-smoke-client.mjs +3 -2
- package/src/mcp-server.mjs +14 -2
- package/src/mcp-smoke-client.mjs +4 -9
- package/src/observation-smoke-client.mjs +5 -10
- package/src/output-contracts.mjs +6 -4
- package/src/process-smoke-client.mjs +3 -3
- package/src/runtime-catalog-expectation.mjs +37 -0
- package/src/stdio-protocol-guard.mjs +71 -0
- package/src/tunnel-stdio-compat-smoke.mjs +5 -9
- package/src/version.mjs +1 -1
- package/test/atomic-mutation.test.mjs +61 -0
- package/test/coding-catalog-consistency.test.mjs +9 -1
- package/test/coding-mcp-unknown-effect.test.mjs +42 -0
- package/test/commander-biff-reference-r48.test.mjs +38 -0
- package/test/commander-core-bridge-r47.test.mjs +19 -0
- package/test/commander-office-reader-r48.test.mjs +18 -2
- package/test/commander-pdf-r48.test.mjs +35 -0
- package/test/commander-protocol-adversarial-r48.test.mjs +166 -0
- package/test/commander-real-parity-r47.test.mjs +41 -3
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/commander-wire-contract-r47.test.mjs +32 -0
- package/test/contained.test.mjs +4 -6
- package/test/controller.test.mjs +2 -1
- package/test/machine-fs-mcp-tools.test.mjs +12 -0
- package/test/output-schema.test.mjs +20 -1
- package/test/r48-release-contract.test.mjs +27 -27
- package/test/tunnel-stdio-compat.test.mjs +7 -7
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {atomicMutation} from '../src/atomic-mutation.mjs';
|
|
8
|
+
|
|
9
|
+
const digest=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
test('post-rename observation failure is explicit unknown effect and committed bytes are not replayed',async()=>{
|
|
12
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-unknown-'));
|
|
13
|
+
const target=path.join(root,'value.txt');
|
|
14
|
+
await writeFile(target,'old');
|
|
15
|
+
let reads=0;
|
|
16
|
+
const readState=async file=>{
|
|
17
|
+
reads++;
|
|
18
|
+
if(reads===3)throw new Error('injected post-rename observation loss');
|
|
19
|
+
const bytes=await readFile(file);
|
|
20
|
+
return {exists:true,sha256:digest(bytes)};
|
|
21
|
+
};
|
|
22
|
+
try{
|
|
23
|
+
const error=await atomicMutation({
|
|
24
|
+
target,bytes:Buffer.from('new'),expected:digest(Buffer.from('old')),readState,
|
|
25
|
+
assertExpected(state,expected){assert.equal(state.sha256,expected);}
|
|
26
|
+
}).then(()=>null,error=>error);
|
|
27
|
+
assert.equal(error?.outcome,'FAILED_AFTER_UNKNOWN_EFFECT');
|
|
28
|
+
assert.equal(error?.side_effect_state,'unknown');
|
|
29
|
+
assert.equal(error?.expected_after_sha256,digest(Buffer.from('new')));
|
|
30
|
+
assert.equal(await readFile(target,'utf8'),'new');
|
|
31
|
+
assert.equal(reads,3);
|
|
32
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('pre-effect authority denial remains failed-before-effect',async()=>{
|
|
36
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-before-'));
|
|
37
|
+
const target=path.join(root,'value.txt');await writeFile(target,'old');
|
|
38
|
+
const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:digest(bytes)};};
|
|
39
|
+
try{
|
|
40
|
+
const error=await atomicMutation({target,bytes:Buffer.from('new'),expected:digest(Buffer.from('old')),readState,
|
|
41
|
+
assertExpected(state,expected){assert.equal(state.sha256,expected);},authorityCheck:async()=>{throw new Error('revoked');}
|
|
42
|
+
}).then(()=>null,error=>error);
|
|
43
|
+
assert.equal(error.message,'revoked');
|
|
44
|
+
assert.equal(error.side_effect_state,undefined);
|
|
45
|
+
assert.equal(await readFile(target,'utf8'),'old');
|
|
46
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('postcondition verifier loss after exact commit is explicit unknown effect',async()=>{
|
|
50
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-verify-'));
|
|
51
|
+
const target=path.join(root,'value.txt');await writeFile(target,'old');
|
|
52
|
+
const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:digest(bytes)};};
|
|
53
|
+
try{
|
|
54
|
+
const error=await atomicMutation({target,bytes:Buffer.from('new'),expected:digest(Buffer.from('old')),readState,
|
|
55
|
+
assertExpected(state,expected){assert.equal(state.sha256,expected);},verifyPostcondition:async()=>{throw new Error('verifier lost');}
|
|
56
|
+
}).then(()=>null,error=>error);
|
|
57
|
+
assert.equal(error.outcome,'FAILED_AFTER_UNKNOWN_EFFECT');
|
|
58
|
+
assert.equal(error.side_effect_state,'unknown');
|
|
59
|
+
assert.equal(await readFile(target,'utf8'),'new');
|
|
60
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
61
|
+
});
|
|
@@ -16,9 +16,17 @@ function extract(source){
|
|
|
16
16
|
assert.ok(m,'coding catalog array missing');
|
|
17
17
|
return [...m[1].matchAll(/'([^']+)'/g)].map(x=>x[1]).sort();
|
|
18
18
|
}
|
|
19
|
-
for(const rel of ['src/
|
|
19
|
+
for(const rel of ['src/remote-mcp-coding-smoke-client.mjs','src/remote-mcp-smoke-client.mjs']){
|
|
20
20
|
test(`${rel} uses the exact R36 25-tool coding catalog`,async()=>{
|
|
21
21
|
const source=await readFile(path.join(root,...rel.split('/')),'utf8');
|
|
22
22
|
assert.deepEqual(extract(source),expected);
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
for(const rel of ['src/machine-fs-smoke-client.mjs','src/observation-smoke-client.mjs']){
|
|
26
|
+
test(`${rel} derives its exact R48 catalog from the canonical runtime catalog`,async()=>{
|
|
27
|
+
const source=await readFile(path.join(root,...rel.split('/')),'utf8');
|
|
28
|
+
assert.match(source,/expectedFullCatalogForEnv/);
|
|
29
|
+
assert.match(source,/assertExactCatalog/);
|
|
30
|
+
assert.doesNotMatch(source,/const (?:codingTools|coding)\s*=\s*\[/);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { registerCodingTools } from '../src/coding-mcp-tools.mjs';
|
|
4
|
+
|
|
5
|
+
class FakeServer {
|
|
6
|
+
constructor() { this.tools = new Map(); }
|
|
7
|
+
registerTool(name, spec, handler) { this.tools.set(name, { spec, handler }); }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function unknownEffectError() {
|
|
11
|
+
const error = new Error('post-commit verification unavailable');
|
|
12
|
+
error.outcome = 'FAILED_AFTER_UNKNOWN_EFFECT';
|
|
13
|
+
error.side_effect_state = 'unknown';
|
|
14
|
+
error.expected_after_sha256 = 'a'.repeat(64);
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test('coding mutation uncertainty survives the public structured error boundary', async () => {
|
|
19
|
+
const server = new FakeServer();
|
|
20
|
+
registerCodingTools(server, {
|
|
21
|
+
codingRuntime: { async fileWrite() { throw unknownEffectError(); } },
|
|
22
|
+
taskRuntime: {},
|
|
23
|
+
semanticRuntime: {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const response = await server.tools.get('coding_file_write').handler({
|
|
27
|
+
root_id: 'core',
|
|
28
|
+
path: 'a.txt',
|
|
29
|
+
content: 'x',
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
expected_sha256: 'absent'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
assert.equal(response.isError, true);
|
|
35
|
+
assert.deepEqual(response.structuredContent, {
|
|
36
|
+
status: 'failed',
|
|
37
|
+
error: 'Error: post-commit verification unavailable',
|
|
38
|
+
outcome: 'FAILED_AFTER_UNKNOWN_EFFECT',
|
|
39
|
+
side_effect_state: 'unknown',
|
|
40
|
+
expected_after_sha256: 'a'.repeat(64)
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {createRequire} from 'node:module';
|
|
5
|
+
import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const oracleRoot='C:\\Users\\DEADBYTE\\.deadbyte-mcp\\evidence\\commander-biff-oracle';
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
|
|
12
|
+
test('pinned reference rejects a real BIFF8 workbook and writes OOXML bytes under .xls',async()=>{
|
|
13
|
+
const require=createRequire(import.meta.url);
|
|
14
|
+
const XLSX=require(path.join(oracleRoot,'node_modules','xlsx'));
|
|
15
|
+
const workbook=XLSX.utils.book_new();
|
|
16
|
+
XLSX.utils.book_append_sheet(workbook,XLSX.utils.aoa_to_sheet([['name','value'],['alpha',42]]),'Data');
|
|
17
|
+
const biff=Buffer.from(XLSX.write(workbook,{type:'buffer',bookType:'biff8'}));
|
|
18
|
+
assert.equal(biff.subarray(0,8).toString('hex'),'d0cf11e0a1b11ae1');
|
|
19
|
+
assert.equal(biff.length,3584);
|
|
20
|
+
assert.equal(sha256(biff),'096168376d0abf282546749fdf6765309f895d12fe81d45c52a2c54bb6c0d139');
|
|
21
|
+
|
|
22
|
+
const ExcelJS=require(path.join(oracleRoot,'node_modules','exceljs'));
|
|
23
|
+
const temp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-biff-reference-'));
|
|
24
|
+
try{
|
|
25
|
+
const input=path.join(temp,'real-biff8.xls');
|
|
26
|
+
await writeFile(input,biff);
|
|
27
|
+
const reader=new ExcelJS.Workbook();
|
|
28
|
+
await assert.rejects(()=>reader.xlsx.readFile(input),/central directory|zip file/i);
|
|
29
|
+
|
|
30
|
+
const output=path.join(temp,'reference-output.xls');
|
|
31
|
+
const writer=new ExcelJS.Workbook();
|
|
32
|
+
writer.addWorksheet('Sheet1').addRows([['name','value'],['alpha',42]]);
|
|
33
|
+
await writer.xlsx.writeFile(output);
|
|
34
|
+
const emitted=await readFile(output);
|
|
35
|
+
assert.equal(emitted.subarray(0,2).toString('ascii'),'PK');
|
|
36
|
+
assert.notEqual(emitted.subarray(0,8).toString('hex'),'d0cf11e0a1b11ae1');
|
|
37
|
+
}finally{await rm(temp,{recursive:true,force:true});}
|
|
38
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { registerCommanderCoreBridge } from '../src/commander-core-bridge.mjs';
|
|
4
|
+
import { createDocxBytes } from '../src/commander-office-reader-r48.mjs';
|
|
4
5
|
|
|
5
6
|
test('search adapter drains bounded native pages instead of silently losing later matches',async()=>{
|
|
6
7
|
const tools=new Map(), cursors=[];
|
|
@@ -58,3 +59,21 @@ test('search adapter timeout aborts native work, completes cleanly, and rejects
|
|
|
58
59
|
page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:100});
|
|
59
60
|
assert.equal(page.results.length,0);
|
|
60
61
|
});
|
|
62
|
+
|
|
63
|
+
test('search adapter natively searches DOCX document and header parts when the reference file pattern enables it',async()=>{
|
|
64
|
+
const tools=new Map(),doc=createDocxBytes('Needle body').bytes;
|
|
65
|
+
const machineFsRuntime={
|
|
66
|
+
policy:{roots:{project:{real_path:'D:\\RDC'}},max_read_bytes:64*1024*1024},
|
|
67
|
+
async list(){return {entries:[{path:'sample.docx',kind:'file'}],next_cursor:null};},
|
|
68
|
+
async read(){return {data_base64:doc.toString('base64'),file_sha256:'a'.repeat(64),eof:true};}
|
|
69
|
+
};
|
|
70
|
+
const searchRuntime=async()=>({matches:[],next_cursor:null});
|
|
71
|
+
registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime,machineFsRuntime});
|
|
72
|
+
const started=await tools.get('start_search').handler({path:'D:\\RDC',pattern:'needle',searchType:'content',ignoreCase:true,literalSearch:true,filePattern:'*.docx',maxResults:10});
|
|
73
|
+
let page;
|
|
74
|
+
for(let i=0;i<20;i++){page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:10});if(page.isComplete)break;await new Promise(r=>setTimeout(r,1));}
|
|
75
|
+
assert.equal(page.isError,false,page.error);
|
|
76
|
+
assert.equal(page.totalResults,1);
|
|
77
|
+
assert.match(page.results[0].file,/sample\.docx$/);
|
|
78
|
+
assert.equal(page.results[0].match,'Needle body');
|
|
79
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { readDocxBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from '../src/commander-office-reader-r48.mjs';
|
|
3
|
+
import { readDocxBytes, searchDocxTextBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from '../src/commander-office-reader-r48.mjs';
|
|
4
4
|
|
|
5
5
|
function crc32(buf){
|
|
6
6
|
let c=0xffffffff;
|
|
@@ -29,7 +29,9 @@ function zipStored(entries){
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const DOCX=zipStored({
|
|
32
|
-
'word/document.xml':'<?xml version="1.0"?><w:document xmlns:w="x"><w:body><w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Hello & world</w:t></w:r></w:p><w:p><w:r><w:t>Second</w:t></w:r><w:tab/><w:r><w:t>line</w:t></w:r></w:p><w:tbl><w:tr><w:tc><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc></w:tr></w:tbl></w:body></w:document>'
|
|
32
|
+
'word/document.xml':'<?xml version="1.0"?><w:document xmlns:w="x"><w:body><w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Hello & world</w:t></w:r></w:p><w:p><w:r><w:t>Second</w:t></w:r><w:tab/><w:r><w:t>line</w:t></w:r></w:p><w:p><w:r><w:t>Caption</w:t></w:r><w:drawing/></w:p><w:tbl><w:tr><w:tc><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc></w:tr></w:tbl></w:body></w:document>',
|
|
33
|
+
'word/header1.xml':'<w:hdr xmlns:w="x"><w:p><w:r><w:t>Header text</w:t></w:r></w:p></w:hdr>',
|
|
34
|
+
'word/footer1.xml':'<w:ftr xmlns:w="x"><w:p><w:r><w:t>Footer text</w:t></w:r></w:p></w:ftr>'
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
const XLSX=zipStored({
|
|
@@ -47,12 +49,26 @@ test('R48 native DOCX reader produces text-bearing outline and paged raw XML',()
|
|
|
47
49
|
assert.match(outline.content,/Second\tline/);
|
|
48
50
|
assert.match(outline.content,/w:tbl/);
|
|
49
51
|
assert.match(outline.content,/A \| B/);
|
|
52
|
+
assert.match(outline.content,/drawing\/image/);
|
|
53
|
+
assert.match(outline.content,/Headers\/Footers:/);
|
|
54
|
+
assert.match(outline.content,/header1\.xml: "Header text"/);
|
|
55
|
+
assert.match(outline.content,/footer1\.xml: "Footer text"/);
|
|
50
56
|
const raw=readDocxBytes(DOCX,{path:'demo.docx',offset:1,length:3});
|
|
51
57
|
assert.equal(raw.mimeType,'application/xml');
|
|
52
58
|
assert.match(raw.content,/\[DOCX XML: lines 1-/);
|
|
53
59
|
assert.equal(raw.metadata.isDocx,true);
|
|
54
60
|
});
|
|
55
61
|
|
|
62
|
+
test('R48 native DOCX search covers document headers and footers with literal bounded semantics',()=>{
|
|
63
|
+
assert.deepEqual(searchDocxTextBytes(DOCX,{pattern:'text',ignoreCase:true,maxResults:10}),[
|
|
64
|
+
{part:'word/footer1.xml',line:1,match:'Footer text'},
|
|
65
|
+
{part:'word/header1.xml',line:1,match:'Header text'}
|
|
66
|
+
]);
|
|
67
|
+
assert.deepEqual(searchDocxTextBytes(DOCX,{pattern:'Hello & world',ignoreCase:false,maxResults:1}),[
|
|
68
|
+
{part:'word/document.xml',line:1,match:'Hello & world'}
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
56
72
|
test('R48 native XLSX reader preserves types, sheet selection, A1 range, and row pagination',()=>{
|
|
57
73
|
const range=readXlsxBytes(XLSX,{path:'book.xlsx',sheet:'Data',range:'A1:B2',offset:0,length:100});
|
|
58
74
|
assert.deepEqual(range.data,[['Name','Count'],['Alpha',42]]);
|
|
@@ -5,6 +5,41 @@ import path from 'node:path';
|
|
|
5
5
|
import {pathToFileURL} from 'node:url';
|
|
6
6
|
import { createDeadbytePdf, parseDeadbytePdf, editDeadbytePdf, readPdfText } from '../src/commander-pdf-r48.mjs';
|
|
7
7
|
|
|
8
|
+
function incrementalPdf(){
|
|
9
|
+
const nl='\n',objects=new Map([
|
|
10
|
+
[1,'<< /Type /Catalog /Pages 2 0 R >>'],
|
|
11
|
+
[2,'<< /Type /Pages /Kids [3 0 R] /Count 1 >>'],
|
|
12
|
+
[3,'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 300] /Contents 4 0 R >>'],
|
|
13
|
+
[4,'<< /Length 20 >>\nstream\nBT (old) Tj ET\nendstream']
|
|
14
|
+
]);
|
|
15
|
+
let out=`%PDF-1.4${nl}`,offsets=[];
|
|
16
|
+
for(const [id,body] of objects){offsets[id]=Buffer.byteLength(out,'latin1');out+=`${id} 0 obj${nl}${body}${nl}endobj${nl}`;}
|
|
17
|
+
const baseXref=Buffer.byteLength(out,'latin1');
|
|
18
|
+
out+=`xref${nl}0 5${nl}0000000000 65535 f ${nl}`;
|
|
19
|
+
for(let id=1;id<=4;id++)out+=`${String(offsets[id]).padStart(10,'0')} 00000 n ${nl}`;
|
|
20
|
+
out+=`trailer${nl}<< /Size 5 /Root 1 0 R >>${nl}startxref${nl}${baseXref}${nl}%%EOF${nl}`;
|
|
21
|
+
const revisedPage=Buffer.byteLength(out,'latin1');
|
|
22
|
+
out+=`3 0 obj${nl}<< /Type /Page /Parent 2 0 R /MediaBox [0 0 321 456] /Contents 5 0 R >>${nl}endobj${nl}`;
|
|
23
|
+
const newContent=Buffer.byteLength(out,'latin1');
|
|
24
|
+
out+=`5 0 obj${nl}<< /Length 20 >>${nl}stream${nl}BT (new) Tj ET${nl}endstream${nl}endobj${nl}`;
|
|
25
|
+
const incrementalXref=Buffer.byteLength(out,'latin1');
|
|
26
|
+
out+=`xref${nl}3 1${nl}${String(revisedPage).padStart(10,'0')} 00000 n ${nl}5 1${nl}${String(newContent).padStart(10,'0')} 00000 n ${nl}`;
|
|
27
|
+
out+=`trailer${nl}<< /Size 6 /Root 1 0 R /Prev ${baseXref} >>${nl}startxref${nl}${incrementalXref}${nl}%%EOF${nl}`;
|
|
28
|
+
return Buffer.from(out,'latin1');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test('R48 valid incremental update selects the latest object revision and survives oracle reload',async()=>{
|
|
32
|
+
const oracle=process.env.DEADBYTE_PDF_TEST_ORACLE??path.join(os.homedir(),'.deadbyte-mcp','evidence','commander-oracle','node_modules','pdf-lib','cjs','index.js');
|
|
33
|
+
const {PDFDocument}=await import(pathToFileURL(oracle).href);
|
|
34
|
+
const fixture=incrementalPdf();
|
|
35
|
+
const loaded=await PDFDocument.load(fixture);
|
|
36
|
+
assert.equal(loaded.getPage(0).getWidth(),321);
|
|
37
|
+
assert.match(readPdfText(fixture).pages[0],/new/);
|
|
38
|
+
const edited=await editDeadbytePdf(fixture,[]);
|
|
39
|
+
const reloaded=await PDFDocument.load(edited.bytes);
|
|
40
|
+
assert.equal(reloaded.getPage(0).getWidth(),321);
|
|
41
|
+
});
|
|
42
|
+
|
|
8
43
|
test('R48 ordinary PDF with compressed object streams preserves pages through independent oracle reload',async()=>{
|
|
9
44
|
const oracle=process.env.DEADBYTE_PDF_TEST_ORACLE??path.join(os.homedir(),'.deadbyte-mcp','evidence','commander-oracle','node_modules','pdf-lib','cjs','index.js');
|
|
10
45
|
const {PDFDocument}=await import(pathToFileURL(oracle).href);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const repoRoot=path.resolve(here,'..');
|
|
10
|
+
const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
|
|
11
|
+
|
|
12
|
+
function isolatedEnv(){
|
|
13
|
+
return {
|
|
14
|
+
...getDefaultEnvironment(),
|
|
15
|
+
DEADBYTE_MACHINE_RUNTIME:'0',
|
|
16
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'0',
|
|
17
|
+
DEADBYTE_OBSERVATION_RUNTIME:'0',
|
|
18
|
+
DEADBYTE_CODING_RUNTIME:'0',
|
|
19
|
+
DEADBYTE_PROCESS_RUNTIME:'0',
|
|
20
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'0',
|
|
21
|
+
DEADBYTE_DESKTOP_RUNTIME:'0',
|
|
22
|
+
DEADBYTE_AUDIT_RUNTIME:'0'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fixture(){
|
|
27
|
+
const child=spawn(process.execPath,[serverPath],{
|
|
28
|
+
cwd:repoRoot,
|
|
29
|
+
env:isolatedEnv(),
|
|
30
|
+
stdio:['pipe','pipe','pipe'],
|
|
31
|
+
windowsHide:true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const messages=[];
|
|
35
|
+
let stdoutBuffer='';
|
|
36
|
+
let stderr='';
|
|
37
|
+
child.stdout.setEncoding('utf8');
|
|
38
|
+
child.stderr.setEncoding('utf8');
|
|
39
|
+
child.stdout.on('data',chunk=>{
|
|
40
|
+
stdoutBuffer+=chunk;
|
|
41
|
+
const lines=stdoutBuffer.split('\n');
|
|
42
|
+
stdoutBuffer=lines.pop()??'';
|
|
43
|
+
for(const line of lines){
|
|
44
|
+
if(!line.trim()) continue;
|
|
45
|
+
try{ messages.push(JSON.parse(line)); }
|
|
46
|
+
catch{ messages.push({__raw:line}); }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
child.stderr.on('data',chunk=>{
|
|
50
|
+
if(stderr.length<65536) stderr+=chunk;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const meta={
|
|
54
|
+
'io.modelcontextprotocol/protocolVersion':'2026-07-28',
|
|
55
|
+
'io.modelcontextprotocol/clientInfo':{name:'deadbyte-protocol-adversarial-r48',version:'0.15.23'},
|
|
56
|
+
'io.modelcontextprotocol/clientCapabilities':{}
|
|
57
|
+
};
|
|
58
|
+
const request=(id,method,params={})=>({jsonrpc:'2.0',id,method,params:{...params,_meta:meta}});
|
|
59
|
+
const send=value=>{
|
|
60
|
+
assert.equal(child.exitCode,null,`server exited before send: ${stderr}`);
|
|
61
|
+
child.stdin.write(typeof value==='string'?value+'\n':JSON.stringify(value)+'\n');
|
|
62
|
+
};
|
|
63
|
+
const waitFor=async(predicate,timeoutMs=5000)=>{
|
|
64
|
+
const deadline=Date.now()+timeoutMs;
|
|
65
|
+
while(Date.now()<deadline){
|
|
66
|
+
const found=messages.find(predicate);
|
|
67
|
+
if(found) return found;
|
|
68
|
+
if(child.exitCode!==null) assert.fail(`server exited code=${child.exitCode}; stderr=${stderr}; messages=${JSON.stringify(messages)}`);
|
|
69
|
+
await new Promise(resolve=>setTimeout(resolve,10));
|
|
70
|
+
}
|
|
71
|
+
assert.fail(`timed out waiting for raw protocol response; stderr=${stderr}; messages=${JSON.stringify(messages)}`);
|
|
72
|
+
};
|
|
73
|
+
const close=async()=>{
|
|
74
|
+
if(child.exitCode!==null) return;
|
|
75
|
+
try{ child.stdin.end(); }catch{}
|
|
76
|
+
await Promise.race([
|
|
77
|
+
new Promise(resolve=>child.once('close',resolve)),
|
|
78
|
+
new Promise(resolve=>setTimeout(resolve,500))
|
|
79
|
+
]);
|
|
80
|
+
if(child.exitCode===null){
|
|
81
|
+
try{ child.kill(); }catch{}
|
|
82
|
+
await Promise.race([
|
|
83
|
+
new Promise(resolve=>child.once('close',resolve)),
|
|
84
|
+
new Promise(resolve=>setTimeout(resolve,500))
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
send(request(1,'tools/list'));
|
|
90
|
+
const pinned=await waitFor(message=>message?.id===1);
|
|
91
|
+
assert.equal(pinned.error,undefined,JSON.stringify(pinned));
|
|
92
|
+
assert.ok(Array.isArray(pinned.result?.tools));
|
|
93
|
+
messages.length=0;
|
|
94
|
+
|
|
95
|
+
return {child,messages,request,send,waitFor,close};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test('R48 raw stdio returns JSON-RPC parse error and remains usable after malformed JSON',async()=>{
|
|
99
|
+
const f=await fixture();
|
|
100
|
+
try{
|
|
101
|
+
f.send('{"jsonrpc":"2.0","id":700,"method":');
|
|
102
|
+
const parseError=await f.waitFor(message=>message?.error?.code===-32700);
|
|
103
|
+
assert.equal(parseError.jsonrpc,'2.0');
|
|
104
|
+
assert.equal(parseError.id,null);
|
|
105
|
+
assert.equal(parseError.error.message,'Parse error');
|
|
106
|
+
|
|
107
|
+
f.send(f.request(701,'tools/list'));
|
|
108
|
+
const control=await f.waitFor(message=>message?.id===701);
|
|
109
|
+
assert.equal(control.error,undefined,JSON.stringify(control));
|
|
110
|
+
assert.ok(Array.isArray(control.result?.tools));
|
|
111
|
+
assert.equal(f.child.exitCode,null);
|
|
112
|
+
}finally{
|
|
113
|
+
await f.close();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('R48 raw stdio returns Invalid Request for valid JSON outside MCP message schema and remains usable',async()=>{
|
|
118
|
+
const f=await fixture();
|
|
119
|
+
try{
|
|
120
|
+
f.send({jsonrpc:'2.0',id:710,params:{}});
|
|
121
|
+
const invalid=await f.waitFor(message=>message?.error?.code===-32600);
|
|
122
|
+
assert.equal(invalid.jsonrpc,'2.0');
|
|
123
|
+
assert.equal(invalid.id,null);
|
|
124
|
+
assert.equal(invalid.error.message,'Invalid Request');
|
|
125
|
+
|
|
126
|
+
f.send(f.request(711,'prompts/list'));
|
|
127
|
+
const control=await f.waitFor(message=>message?.id===711);
|
|
128
|
+
assert.equal(control.error,undefined,JSON.stringify(control));
|
|
129
|
+
assert.ok(Array.isArray(control.result?.prompts));
|
|
130
|
+
assert.equal(f.child.exitCode,null);
|
|
131
|
+
}finally{
|
|
132
|
+
await f.close();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('R48 raw stdio preserves method/tool errors and request correlation under duplicate/concurrent ids',async()=>{
|
|
137
|
+
const f=await fixture();
|
|
138
|
+
try{
|
|
139
|
+
f.send(f.request(720,'deadbyte/unknown'));
|
|
140
|
+
f.send(f.request(721,'tools/call',{name:'definitely_missing_tool',arguments:{}}));
|
|
141
|
+
f.send(f.request(722,'tools/list'));
|
|
142
|
+
f.send(f.request(722,'prompts/list'));
|
|
143
|
+
f.send(f.request(723,'resources/list'));
|
|
144
|
+
f.send(f.request(724,'prompts/list'));
|
|
145
|
+
|
|
146
|
+
const unknownMethod=await f.waitFor(message=>message?.id===720);
|
|
147
|
+
assert.equal(unknownMethod.error?.code,-32601);
|
|
148
|
+
|
|
149
|
+
const unknownTool=await f.waitFor(message=>message?.id===721);
|
|
150
|
+
assert.equal(unknownTool.error?.code,-32602);
|
|
151
|
+
|
|
152
|
+
await f.waitFor(()=>f.messages.filter(message=>message?.id===722).length===2);
|
|
153
|
+
const duplicate=f.messages.filter(message=>message?.id===722);
|
|
154
|
+
assert.equal(duplicate.length,2);
|
|
155
|
+
assert.equal(duplicate.some(message=>Array.isArray(message.result?.tools)),true);
|
|
156
|
+
assert.equal(duplicate.some(message=>Array.isArray(message.result?.prompts)),true);
|
|
157
|
+
|
|
158
|
+
const resources=await f.waitFor(message=>message?.id===723);
|
|
159
|
+
const prompts=await f.waitFor(message=>message?.id===724);
|
|
160
|
+
assert.ok(Array.isArray(resources.result?.resources));
|
|
161
|
+
assert.ok(Array.isArray(prompts.result?.prompts));
|
|
162
|
+
assert.equal(f.child.exitCode,null);
|
|
163
|
+
}finally{
|
|
164
|
+
await f.close();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
@@ -9,6 +9,7 @@ import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
|
9
9
|
import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machine-fs.mjs';
|
|
10
10
|
import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
|
|
11
11
|
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
12
|
+
import { createCommanderWireAdapter } from '../src/commander-wire-adapter-r47.mjs';
|
|
12
13
|
|
|
13
14
|
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
14
15
|
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
|
@@ -31,15 +32,17 @@ async function processFixture(){
|
|
|
31
32
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-real-process-'));
|
|
32
33
|
const project=path.join(root,'project'),trust=path.join(root,'trust'); await mkdir(project); await mkdir(trust);
|
|
33
34
|
const host=path.resolve('bin/deadbyte-process-host.exe'), exe='C:\\Windows\\System32\\cmd.exe';
|
|
34
|
-
const
|
|
35
|
+
const powershell='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
36
|
+
const h=sha(await readFile(host)), e=sha(await readFile(exe)),ps=sha(await readFile(powershell));
|
|
35
37
|
const grantFile=path.join(root,'grant.json'), stateDir=path.join(root,'state'), policyPath=path.join(root,'policy.json');
|
|
36
38
|
await writeFile(policyPath,JSON.stringify({schema:PROCESS_POLICY_SCHEMA,enabled:true,grant_file:grantFile,state_dir:stateDir,
|
|
37
39
|
native_host:{path:host,sha256:h},roots:{core:{path:project,deny_paths:[]}},profiles:{
|
|
38
40
|
echo:{executable:exe,executable_sha256:e,root_ids:['core'],prefix_args:['/d','/c'],max_args:4,max_arg_bytes:256,max_sessions:2,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}},
|
|
39
|
-
hold:{executable:exe,executable_sha256:e,root_ids:['core'],transport:'pipe',prefix_args:['/d','/q'],max_args:0,max_arg_bytes:256,max_sessions:2,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}}
|
|
41
|
+
hold:{executable:exe,executable_sha256:e,root_ids:['core'],transport:'pipe',prefix_args:['/d','/q'],max_args:0,max_arg_bytes:256,max_sessions:2,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}},
|
|
42
|
+
powershell_repl:{executable:powershell,executable_sha256:ps,root_ids:['core'],transport:'pipe',prefix_args:['-NoLogo','-NoProfile','-NonInteractive','-Command','-'],allow_raw_input:true,max_args:0,max_arg_bytes:256,max_sessions:2,max_input_bytes:8192,max_retained_output_bytes:2*1024*1024,env_allow:['PATH','TEMP','TMP'],input_actions:{}}
|
|
40
43
|
}}));
|
|
41
44
|
const policy=await loadProcessPolicy(policyPath);
|
|
42
|
-
await writeFile(grantFile,JSON.stringify({schema:PROCESS_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:'a'.repeat(64),profile_ids:['echo','hold'],root_ids:['core'],granted_at_utc:new Date().toISOString()}));
|
|
45
|
+
await writeFile(grantFile,JSON.stringify({schema:PROCESS_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:'a'.repeat(64),profile_ids:['echo','hold','powershell_repl'],root_ids:['core'],granted_at_utc:new Date().toISOString()}));
|
|
43
46
|
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
44
47
|
return {root,policy,keys,runtime:createProcessRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
45
48
|
}
|
|
@@ -59,10 +62,45 @@ test('real Commander-parity filesystem lifecycle is transactional and signed',as
|
|
|
59
62
|
assert.equal(m.status,'committed'); assert.equal(final.sha256,w.sha256); assert.equal(proof.ok,true);
|
|
60
63
|
assert.deepEqual(page.entries.map(x=>x.path),['x/a.txt']);
|
|
61
64
|
assert.equal(Buffer.from(r.data_base64,'base64').toString(),'alpha');
|
|
65
|
+
const adapter=createCommanderWireAdapter({machineFsRuntime:f.runtime});
|
|
66
|
+
const directory=await adapter.handlers.read_file({path:path.join(f.project,'x'),isUrl:false,offset:0,length:100});
|
|
67
|
+
assert.equal(directory.structuredContent.isDirectory,true);
|
|
68
|
+
assert.match(directory.structuredContent.content,/\[FILE\] b\.txt/);
|
|
69
|
+
assert.doesNotMatch(directory.structuredContent.content,/\[FILE\] x[\\/]b\.txt/);
|
|
62
70
|
assert.ok(performance.now()-t0>=0);
|
|
63
71
|
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
64
72
|
});
|
|
65
73
|
|
|
74
|
+
test('real Commander adapter preserves PowerShell command bytes through the bounded REPL', {skip:process.platform!=='win32'}, async()=>{
|
|
75
|
+
const f=await processFixture();
|
|
76
|
+
let adapter=null,pid=null;
|
|
77
|
+
try{
|
|
78
|
+
adapter=createCommanderWireAdapter({processRuntime:f.runtime});
|
|
79
|
+
const command=`[Console]::OutputEncoding=[Text.UTF8Encoding]::new(); $x='a b'; "DB:$x"; 'pipe' | ForEach-Object { "P:$_" }; "U:สวัสดี"`;
|
|
80
|
+
const started=await adapter.handlers.start_process({command,timeout_ms:10000});
|
|
81
|
+
pid=started.structuredContent.pid;
|
|
82
|
+
let output='';
|
|
83
|
+
for(let i=0;i<50&&!(output.includes('DB:a b')&&output.includes('P:pipe')&&output.includes('U:สวัสดี'));i++){
|
|
84
|
+
const page=await adapter.handlers.read_process_output({pid,offset:0,length:100,timeout_ms:100});
|
|
85
|
+
output+=page.content[0].text;
|
|
86
|
+
}
|
|
87
|
+
assert.match(output,/DB:a b/);
|
|
88
|
+
assert.match(output,/P:pipe/);
|
|
89
|
+
assert.match(output,/U:สวัสดี/);
|
|
90
|
+
const large=await adapter.handlers.interact_with_process({
|
|
91
|
+
pid,input:"[Console]::Out.Write(('X'*1100000)); [Console]::Out.WriteLine('END')",timeout_ms:10000,wait_for_prompt:true
|
|
92
|
+
});
|
|
93
|
+
const largeText=large.content[0].text;
|
|
94
|
+
assert.ok(Buffer.byteLength(largeText,'utf8')>1024*1024);
|
|
95
|
+
assert.match(largeText,/END/);
|
|
96
|
+
const ended=await adapter.handlers.force_terminate({pid});
|
|
97
|
+
assert.equal(ended.structuredContent.termination.verified,true);
|
|
98
|
+
}finally{
|
|
99
|
+
if(adapter&&pid!==null) await adapter.handlers.force_terminate({pid}).catch(()=>{});
|
|
100
|
+
await rm(f.root,{recursive:true,force:true});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
66
104
|
test('real stale mutation is rejected before commit and leaves source intact',async()=>{
|
|
67
105
|
const f=await fsFixture();
|
|
68
106
|
try{
|
|
@@ -5,7 +5,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
5
5
|
import {createCommanderWireAdapter} from '../src/commander-wire-adapter-r47.mjs';
|
|
6
6
|
import {registerCommanderExtendedBridge} from '../src/commander-extended-bridge-r47.mjs';
|
|
7
7
|
|
|
8
|
-
const referencePath='D:\\Projects\\
|
|
8
|
+
const referencePath='D:\\Projects\\desktop-commander-stage-20260922\\desktop-commander\\dist\\tools\\schemas.js';
|
|
9
9
|
const expectedHash='f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140';
|
|
10
10
|
async function referenceSchemas(){
|
|
11
11
|
let source=await readFile(referencePath,'utf8');
|
|
@@ -146,6 +146,38 @@ test('R48 RDC read_file preserves image/text wire behavior and natively reads DO
|
|
|
146
146
|
assert.equal(withOptions.isError,undefined,JSON.stringify(withOptions));
|
|
147
147
|
assert.deepEqual(withOptions.structuredContent.ignoredOptions,['includeImages','mode']);
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
test('R48 read_file preserves the reference directory-listing fallback',async()=>{
|
|
151
|
+
let reads=0;
|
|
152
|
+
const fs={
|
|
153
|
+
policy:{roots:{project:{real_path:'D:\\RDC-COMPAT-TEST'}},max_read_bytes:64*1024*1024},
|
|
154
|
+
async stat(){return {kind:'directory'};},
|
|
155
|
+
async list({relativePath}){return relativePath==='folder'?{entries:[
|
|
156
|
+
{path:'folder/a.txt',kind:'file'},
|
|
157
|
+
{path:'folder/nested',kind:'directory'}
|
|
158
|
+
],next_cursor:null}:{entries:[{path:'folder/nested/b.txt',kind:'file'}],next_cursor:null};},
|
|
159
|
+
async read(){reads++;throw new Error('directory must not enter file read');}
|
|
160
|
+
};
|
|
161
|
+
const a=createCommanderWireAdapter({machineFsRuntime:fs});
|
|
162
|
+
const result=await a.handlers.read_file({path:'D:\\RDC-COMPAT-TEST\\folder',isUrl:false,offset:0,length:100});
|
|
163
|
+
assert.equal(reads,0);
|
|
164
|
+
assert.equal(result.structuredContent.isDirectory,true);
|
|
165
|
+
assert.equal(result.structuredContent.content,
|
|
166
|
+
'This is a directory, not a file. Use the list_directory tool instead of read_file for directories.\n\n[FILE] a.txt\n[DIR] nested\n[FILE] nested\\b.txt');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('R48 read_file identifies unknown binary content instead of lossy UTF-8 decoding',async()=>{
|
|
170
|
+
const bytes=Buffer.from([0,255,1,2,0,3]);
|
|
171
|
+
const fs={
|
|
172
|
+
policy:{roots:{project:{real_path:'D:\\RDC-COMPAT-TEST'}},max_read_bytes:65536},
|
|
173
|
+
async read(){return {data_base64:bytes.toString('base64'),file_sha256:'9'.repeat(64),eof:true};}
|
|
174
|
+
};
|
|
175
|
+
const a=createCommanderWireAdapter({machineFsRuntime:fs});
|
|
176
|
+
const result=await a.handlers.read_file({path:'D:\\RDC-COMPAT-TEST\\payload.bin',isUrl:false,offset:0,length:100});
|
|
177
|
+
assert.equal(result.structuredContent.isBinary,true);
|
|
178
|
+
assert.match(result.structuredContent.content,/Cannot read binary file as text: payload\.bin/);
|
|
179
|
+
assert.doesNotMatch(result.structuredContent.content,/�/u);
|
|
180
|
+
});
|
|
149
181
|
test('R48 read_multiple_files reuses rich read_file semantics per path',async()=>{
|
|
150
182
|
const multiPdf=createDeadbytePdf(['multi page']);
|
|
151
183
|
const fs={
|
package/test/contained.test.mjs
CHANGED
|
@@ -456,13 +456,11 @@ test('receipt and verifier bind contained worker, launcher and independent trans
|
|
|
456
456
|
assert.match(verifier, /contained transform semantic mismatch/);
|
|
457
457
|
});
|
|
458
458
|
|
|
459
|
-
test('MCP protocol smoke
|
|
459
|
+
test('MCP protocol smoke derives the exact full catalog and explicitly disables Host', async () => {
|
|
460
460
|
const smoke = await readFile(fileURLToPath(new URL('../src/mcp-smoke-client.mjs', import.meta.url)), 'utf8');
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
assert.match(smoke, /
|
|
464
|
-
assert.match(smoke, /baseTools/);
|
|
465
|
-
assert.match(smoke, /privilegedTools/);
|
|
461
|
+
assert.match(smoke, /expectedFullCatalogForEnv/);
|
|
462
|
+
assert.match(smoke, /assertExactCatalog/);
|
|
463
|
+
assert.match(smoke, /DEADBYTE_PRIVILEGED_POLICY: ''/);
|
|
466
464
|
assert.doesNotMatch(smoke, /agent_task_/);
|
|
467
465
|
});
|
|
468
466
|
|
package/test/controller.test.mjs
CHANGED
|
@@ -83,7 +83,8 @@ test('controller keeps privileged policy optional and propagates it only to the
|
|
|
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
85
|
assert.match(init, /deadbyte\.host-policy\.v2/);
|
|
86
|
-
assert.match(init, /
|
|
86
|
+
assert.match(init, /System\.Security\.Cryptography\.SHA256/);
|
|
87
|
+
assert.match(init, /System\.IO\.File.*OpenRead/);
|
|
87
88
|
assert.match(init, /powershell_script/);
|
|
88
89
|
assert.match(init, /cmd_script/);
|
|
89
90
|
assert.match(init, /node_script/);
|
|
@@ -49,3 +49,15 @@ test('R31 machine filesystem schemas expose root-relative IDs/preconditions only
|
|
|
49
49
|
assert.equal(move.safeParse({ root_id:'core', source_path:'a', destination_path:'b', pid:1234, expected_source_identity:hex('a') }).success, false);
|
|
50
50
|
assert.equal(del.safeParse({ root_id:'core', path:'a', expected_source_identity:hex('a') }).success, true);
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
test('post-effect mutation uncertainty survives the public structured error boundary',async()=>{
|
|
54
|
+
const server=new FakeServer(),fake=runtime();
|
|
55
|
+
fake.write=async()=>{const error=new Error('post-commit verifier lost');error.outcome='FAILED_AFTER_UNKNOWN_EFFECT';error.side_effect_state='unknown';error.expected_after_sha256=hex('e');throw error;};
|
|
56
|
+
registerMachineFsTools(server,{runtime:fake});
|
|
57
|
+
const result=await server.tools.get('machine_fs_write').handler({root_id:'core',path:'a.txt',content:'x',encoding:'utf8',expected_sha256:'absent'});
|
|
58
|
+
assert.equal(result.isError,true);
|
|
59
|
+
assert.equal(result.structuredContent.status,'failed');
|
|
60
|
+
assert.equal(result.structuredContent.outcome,'FAILED_AFTER_UNKNOWN_EFFECT');
|
|
61
|
+
assert.equal(result.structuredContent.side_effect_state,'unknown');
|
|
62
|
+
assert.equal(result.structuredContent.expected_after_sha256,hex('e'));
|
|
63
|
+
});
|