deadbyte-mcp 0.15.15 → 0.15.17
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 +136 -102
- package/PROVENANCE.json +6 -6
- package/README.txt +6 -6
- package/RELEASE.json +12 -7
- package/SBOM.cdx.json +7 -33
- package/TEST-MATRIX.json +10 -25
- package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +49 -0
- package/docs/COMMANDER_REFERENCE_INVENTORY.json +70 -0
- package/docs/R47-FULL-TOOL-CATALOG.json +123 -0
- package/node_modules/@modelcontextprotocol/client/dist/{index-CHhhk6PQ.d.cts → index-BCtJi0p5.d.mts} +38 -6
- package/node_modules/@modelcontextprotocol/client/dist/index-BCtJi0p5.d.mts.map +1 -0
- package/node_modules/@modelcontextprotocol/client/dist/{index-D4xIIEF6.d.mts → index-CEk3qkZM.d.cts} +41 -3
- package/node_modules/@modelcontextprotocol/client/dist/index-CEk3qkZM.d.cts.map +1 -0
- package/node_modules/@modelcontextprotocol/client/dist/index.cjs +375 -60
- package/node_modules/@modelcontextprotocol/client/dist/index.cjs.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/index.d.cts +227 -11
- package/node_modules/@modelcontextprotocol/client/dist/index.d.cts.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/index.d.mts +227 -11
- package/node_modules/@modelcontextprotocol/client/dist/index.d.mts.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/index.mjs +369 -61
- package/node_modules/@modelcontextprotocol/client/dist/index.mjs.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/shimsWorkerd.cjs +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/shimsWorkerd.mjs +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/{src-NAgB4Mp8.cjs → src-D0qEN74R.cjs} +111 -38
- package/node_modules/@modelcontextprotocol/client/dist/src-D0qEN74R.cjs.map +1 -0
- package/node_modules/@modelcontextprotocol/client/dist/{src-D_zzAWoS.mjs → src-DDAAhLnO.mjs} +106 -33
- package/node_modules/@modelcontextprotocol/client/dist/src-DDAAhLnO.mjs.map +1 -0
- package/node_modules/@modelcontextprotocol/client/dist/stdio.cjs +8 -2
- package/node_modules/@modelcontextprotocol/client/dist/stdio.cjs.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/stdio.d.cts +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/stdio.d.cts.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/stdio.d.mts +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/stdio.d.mts.map +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/stdio.mjs +8 -2
- package/node_modules/@modelcontextprotocol/client/dist/stdio.mjs.map +1 -1
- package/node_modules/@modelcontextprotocol/client/package.json +4 -4
- package/node_modules/@modelcontextprotocol/core/dist/{auth-BWdKR39I.d.mts → auth--n_Yzytt.d.cts} +6 -1
- package/node_modules/@modelcontextprotocol/core/dist/{auth-BWdKR39I.d.mts.map → auth--n_Yzytt.d.cts.map} +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/{auth-CfDMXiII.cjs → auth-4-zilcqg.cjs} +3 -2
- package/node_modules/@modelcontextprotocol/core/dist/{auth-CfDMXiII.cjs.map → auth-4-zilcqg.cjs.map} +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/{auth-CUe6YdwF.mjs → auth-CGP0BDVq.mjs} +3 -2
- package/node_modules/@modelcontextprotocol/core/dist/{auth-CUe6YdwF.mjs.map → auth-CGP0BDVq.mjs.map} +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/{auth-hYtaIUZm.d.cts → auth-YUQV3RRv.d.mts} +6 -1
- package/node_modules/@modelcontextprotocol/core/dist/{auth-hYtaIUZm.d.cts.map → auth-YUQV3RRv.d.mts.map} +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/index.cjs +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/index.d.cts +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/index.d.mts +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/index.mjs +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/internal.cjs +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/internal.d.cts +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/internal.d.mts +1 -1
- package/node_modules/@modelcontextprotocol/core/dist/internal.mjs +1 -1
- package/node_modules/@modelcontextprotocol/core/package.json +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/{createMcpHandler-dBHMsxwf.d.cts → createMcpHandler-BLVe3qmU.d.cts} +110 -7
- package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-BLVe3qmU.d.cts.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/{createMcpHandler-CLhGwQTn.d.mts → createMcpHandler-Bt6U_Fqb.d.mts} +110 -7
- package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-Bt6U_Fqb.d.mts.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/index.cjs +144 -248
- package/node_modules/@modelcontextprotocol/server/dist/index.cjs.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/index.d.cts +38 -3
- package/node_modules/@modelcontextprotocol/server/dist/index.d.cts.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/index.d.mts +38 -3
- package/node_modules/@modelcontextprotocol/server/dist/index.d.mts.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/index.mjs +136 -243
- package/node_modules/@modelcontextprotocol/server/dist/index.mjs.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/{mcp-D7GmuPnv.cjs → mcp-DUcCQSTW.cjs} +426 -21
- package/node_modules/@modelcontextprotocol/server/dist/mcp-DUcCQSTW.cjs.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/{mcp-DXXb3Vv3.mjs → mcp-Dw2OlZ1f.mjs} +367 -22
- package/node_modules/@modelcontextprotocol/server/dist/mcp-Dw2OlZ1f.mjs.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.cjs +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/shimsWorkerd.mjs +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/{src-STyD_Vvf.cjs → src-CgFsfg3E.cjs} +114 -36
- package/node_modules/@modelcontextprotocol/server/dist/src-CgFsfg3E.cjs.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/{src-CX2iR2pK.mjs → src-D-y6h4N7.mjs} +114 -36
- package/node_modules/@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs.map +1 -0
- package/node_modules/@modelcontextprotocol/server/dist/stdio.cjs +30 -3
- package/node_modules/@modelcontextprotocol/server/dist/stdio.cjs.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/stdio.d.cts +9 -1
- package/node_modules/@modelcontextprotocol/server/dist/stdio.d.cts.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/stdio.d.mts +9 -1
- package/node_modules/@modelcontextprotocol/server/dist/stdio.d.mts.map +1 -1
- package/node_modules/@modelcontextprotocol/server/dist/stdio.mjs +30 -3
- package/node_modules/@modelcontextprotocol/server/dist/stdio.mjs.map +1 -1
- package/node_modules/@modelcontextprotocol/server/package.json +3 -3
- package/package.json +91 -90
- package/scripts/commander-hardening-gate-r47.mjs +36 -0
- package/scripts/commander-parity-benchmark.mjs +6 -0
- package/scripts/commander-real-performance-benchmark-r47.mjs +41 -0
- package/scripts/deploy-r47.mjs +67 -8
- package/scripts/full-regression-r47.mjs +28 -0
- package/scripts/inventory-reference-dist.mjs +17 -0
- package/scripts/npm-release-r47.mjs +4 -1
- package/scripts/r47-syntax-check.mjs +6 -0
- package/scripts/r47-wire-remote-plane.mjs +23 -0
- package/src/atomic-mutation.mjs +59 -29
- package/src/coding-plane.mjs +24 -0
- package/src/commander-compat-contracts.mjs +90 -0
- package/src/commander-compat-runtime.mjs +91 -0
- package/src/commander-core-bridge.mjs +26 -0
- package/src/commander-extended-bridge-r47.mjs +17 -0
- package/src/commander-extended-runtime.mjs +75 -0
- package/src/commander-observability.mjs +33 -0
- package/src/commander-remote-bridge-r47.mjs +41 -0
- package/src/commander-remote-relay-r47.mjs +44 -0
- package/src/commander-wire-adapter-r47.mjs +341 -0
- package/src/machine-fs.mjs +114 -28
- package/src/mcp-server.mjs +22 -2
- package/src/release-slot.mjs +3 -1
- package/src/runtime-update.mjs +61 -7
- package/src/tool-audit.mjs +15 -2
- package/src/tunnel-stdio-compat.mjs +3 -3
- package/src/version.mjs +1 -2
- package/test/OBSERVED-COMMANDER-TOOLS.json +1 -0
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/coding-search-r36.test.mjs +14 -0
- package/test/commander-compat-runtime.test.mjs +34 -0
- package/test/commander-core-bridge-r47.test.mjs +31 -0
- package/test/commander-exhaustive-surface-r47.test.mjs +32 -0
- package/test/commander-extended-runtime-r47.test.mjs +46 -0
- package/test/commander-observability-r47.test.mjs +34 -0
- package/test/commander-parity-gate-r47.test.mjs +49 -0
- package/test/commander-protocol-r47.test.mjs +33 -0
- package/test/commander-real-parity-r47.test.mjs +121 -0
- package/test/commander-remote-bridge-r47.test.mjs +39 -0
- package/test/commander-remote-plane-r47.test.mjs +25 -0
- package/test/commander-torture-r47.test.mjs +146 -0
- package/test/commander-wire-contract-r47.test.mjs +143 -0
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +116 -1
- package/test/output-schema.test.mjs +1 -1
- package/test/process-runtime.test.mjs +58 -0
- package/test/r47-release-contract.test.mjs +37 -0
- package/test/release-activation-gate.test.mjs +30 -0
- package/test/release-version.test.mjs +1 -1
- package/node_modules/@modelcontextprotocol/client/dist/index-CHhhk6PQ.d.cts.map +0 -1
- package/node_modules/@modelcontextprotocol/client/dist/index-D4xIIEF6.d.mts.map +0 -1
- package/node_modules/@modelcontextprotocol/client/dist/src-D_zzAWoS.mjs.map +0 -1
- package/node_modules/@modelcontextprotocol/client/dist/src-NAgB4Mp8.cjs.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-CLhGwQTn.d.mts.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/createMcpHandler-dBHMsxwf.d.cts.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/mcp-D7GmuPnv.cjs.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/mcp-DXXb3Vv3.mjs.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/src-CX2iR2pK.mjs.map +0 -1
- package/node_modules/@modelcontextprotocol/server/dist/src-STyD_Vvf.cjs.map +0 -1
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const sha=v=>createHash('sha256').update(v).digest('hex');
|
|
7
|
+
|
|
8
|
+
test('Commander exhaustive surface is internally consistent and natively implemented',async()=>{
|
|
9
|
+
const catalog=JSON.parse(await readFile(new URL('../docs/COMMANDER_EXHAUSTIVE_SURFACE.json',import.meta.url),'utf8'));
|
|
10
|
+
assert.equal(catalog.surface.length,40);
|
|
11
|
+
assert.equal(catalog.status_counts.total,40);
|
|
12
|
+
assert.equal(catalog.status_counts.implemented,40);
|
|
13
|
+
assert.equal(catalog.status_counts.adapter_required,0);
|
|
14
|
+
assert.equal(new Set(catalog.surface.map(x=>x.name)).size,40);
|
|
15
|
+
assert.ok(catalog.surface.every(x=>x.status==='implemented'));
|
|
16
|
+
assert.ok(catalog.surface.every(x=>typeof x.native==='string'&&x.native.length>0));
|
|
17
|
+
assert.equal(sha(Buffer.from(JSON.stringify(catalog.reference))),sha(Buffer.from(JSON.stringify({package:'@wonderwhy-er/desktop-commander',version:'0.2.51',license:'MIT'}))));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Commander extended native source has no Commander dependency',async()=>{
|
|
21
|
+
const files=[
|
|
22
|
+
'src/commander-compat-contracts.mjs',
|
|
23
|
+
'src/commander-compat-runtime.mjs',
|
|
24
|
+
'src/commander-extended-runtime.mjs',
|
|
25
|
+
'src/commander-remote-relay-r47.mjs'
|
|
26
|
+
];
|
|
27
|
+
for(const file of files){
|
|
28
|
+
const text=await readFile(new URL('../'+file,import.meta.url),'utf8');
|
|
29
|
+
assert.equal(text.includes('@wonderwhy-er/desktop-commander'),false,file);
|
|
30
|
+
assert.equal(text.includes('desktop-commander/dist'),false,file);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, readFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createCommanderExtendedRuntime } from '../src/commander-extended-runtime.mjs';
|
|
7
|
+
|
|
8
|
+
test('Commander extended config is hash-preconditioned and bounded',async()=>{
|
|
9
|
+
const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
|
|
10
|
+
const rt=createCommanderExtendedRuntime({stateRoot:root});
|
|
11
|
+
const initial=await rt.getConfig();
|
|
12
|
+
const committed=await rt.setConfigValue({key:'theme',value:'classic',expected_sha256:initial.config_sha256});
|
|
13
|
+
assert.equal(committed.status,'committed');
|
|
14
|
+
await assert.rejects(()=>rt.setConfigValue({key:'theme',value:'modern',expected_sha256:initial.config_sha256}),/precondition/);
|
|
15
|
+
assert.equal((await rt.getConfig({key:'theme'})).config.theme,'classic');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Commander extended feedback is bounded and durably recorded',async()=>{
|
|
19
|
+
const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
|
|
20
|
+
const rt=createCommanderExtendedRuntime({stateRoot:root});
|
|
21
|
+
const result=await rt.feedback({category:'test',message:'operator feedback'});
|
|
22
|
+
assert.equal(result.status,'committed');
|
|
23
|
+
const files=await import('node:fs/promises').then(fs=>fs.readdir(path.join(root,'feedback')));
|
|
24
|
+
assert.equal(files.length,1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Commander extended prompt/resource catalogs are deterministic and bounded',()=>{
|
|
28
|
+
const rt=createCommanderExtendedRuntime({stateRoot:'x',prompts:[{name:'p'}],resources:[{uri:'r'}]});
|
|
29
|
+
assert.deepEqual(rt.getPrompts().prompts,[{name:'p'}]);
|
|
30
|
+
assert.deepEqual(rt.getResources().resources,[{uri:'r'}]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('Commander extended preview is bounded and PDF output is self-contained',async()=>{
|
|
34
|
+
const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
|
|
35
|
+
const file=path.join(root,'note.txt');
|
|
36
|
+
await import('node:fs/promises').then(fs=>fs.writeFile(file,'abcdef','utf8'));
|
|
37
|
+
const rt=createCommanderExtendedRuntime({stateRoot:root});
|
|
38
|
+
const preview=await rt.filePreview({rootId:root,path:'note.txt',offset:1,maxBytes:3});
|
|
39
|
+
assert.equal(preview.text,'bcd');
|
|
40
|
+
assert.equal(preview.truncated,true);
|
|
41
|
+
const pdf=rt.writePdf({text:'hello'});
|
|
42
|
+
assert.equal(pdf.mime_type,'application/pdf');
|
|
43
|
+
assert.match(Buffer.from(pdf.bytes_base64,'base64').toString('utf8'),/^%PDF-1\.4/);
|
|
44
|
+
assert.equal(pdf.bytes_sha256.length,64);
|
|
45
|
+
assert.ok(pdf.bytes>0);
|
|
46
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createOperationHistory } from '../src/operation-history.mjs';
|
|
4
|
+
import { createCommanderObservability } from '../src/commander-observability.mjs';
|
|
5
|
+
|
|
6
|
+
test('Commander observability exposes bounded usage stats with integrity evidence',()=>{
|
|
7
|
+
const history=createOperationHistory({operationId:'obs-1',maxEvents:32});
|
|
8
|
+
history.append('tool.call',{tool:'read_file'});
|
|
9
|
+
history.append('tool.result',{tool:'read_file'});
|
|
10
|
+
history.append('tool.call',{tool:'write_file'});
|
|
11
|
+
history.append('tool.error',{tool:'write_file'});
|
|
12
|
+
const obs=createCommanderObservability({history});
|
|
13
|
+
const stats=obs.usageStats();
|
|
14
|
+
assert.equal(stats.tool_calls,2);
|
|
15
|
+
assert.equal(stats.tool_results,1);
|
|
16
|
+
assert.equal(stats.tool_errors,1);
|
|
17
|
+
assert.deepEqual(stats.tool_counts,{read_file:2,write_file:2});
|
|
18
|
+
assert.equal(stats.integrity.ok,true);
|
|
19
|
+
assert.match(stats.head_event_hash,/^[0-9a-f]{64}$/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('Commander observability recent calls preserves bounded history semantics',()=>{
|
|
23
|
+
const history=createOperationHistory({operationId:'obs-2',maxEvents:8});
|
|
24
|
+
for(let i=0;i<5;i++) history.append('tool.call',{tool:'x',i});
|
|
25
|
+
history.append('other',{x:1});
|
|
26
|
+
const obs=createCommanderObservability({history});
|
|
27
|
+
const page=obs.recentToolCalls({afterSequence:0,limit:3});
|
|
28
|
+
assert.deepEqual(page.events.map(e=>e.sequence),[1,2,3]);
|
|
29
|
+
assert.throws(()=>obs.recentToolCalls({limit:1001}),/limit/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Commander observability rejects forged history providers',()=>{
|
|
33
|
+
assert.throws(()=>createCommanderObservability({history:{read(){},verify(){}}}),/operation history/);
|
|
34
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import {
|
|
5
|
+
COMMANDER_COMPAT_TOOLS
|
|
6
|
+
} from '../src/commander-compat-contracts.mjs';
|
|
7
|
+
import {
|
|
8
|
+
commanderCompatibilityCoverage,
|
|
9
|
+
commanderCompatibilityContract,
|
|
10
|
+
assertNoCommanderDependency
|
|
11
|
+
} from '../src/commander-compat-runtime.mjs';
|
|
12
|
+
|
|
13
|
+
test('R47 Commander parity gate: all observed core capabilities map to native DEADBYTE surfaces', async () => {
|
|
14
|
+
const coverage = commanderCompatibilityCoverage();
|
|
15
|
+
assert.equal(coverage.total, COMMANDER_COMPAT_TOOLS.length);
|
|
16
|
+
assert.equal(coverage.covered, coverage.total);
|
|
17
|
+
assert.deepEqual(coverage.missing, []);
|
|
18
|
+
for (const tool of COMMANDER_COMPAT_TOOLS) {
|
|
19
|
+
const contract = commanderCompatibilityContract(tool);
|
|
20
|
+
assert.equal(typeof contract.native, 'string', tool);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('R47 Commander parity gate: native source tree has no Commander package dependency', async () => {
|
|
25
|
+
const tree = [
|
|
26
|
+
'src/commander-compat-contracts.mjs',
|
|
27
|
+
'src/machine-fs.mjs',
|
|
28
|
+
'src/process-runtime.mjs',
|
|
29
|
+
'src/coding-search.mjs'
|
|
30
|
+
];
|
|
31
|
+
for (const file of tree) {
|
|
32
|
+
const source = await readFile(new URL('../' + file, import.meta.url), 'utf8');
|
|
33
|
+
assertNoCommanderDependency(source);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('R47 Commander parity gate: mutation contracts require explicit state binding', () => {
|
|
38
|
+
const mutations = COMMANDER_COMPAT_TOOLS
|
|
39
|
+
.map(name => [name, commanderCompatibilityContract(name)])
|
|
40
|
+
.filter(([, contract]) => contract.mutation);
|
|
41
|
+
assert.ok(mutations.length >= 7);
|
|
42
|
+
for (const [name, contract] of mutations) {
|
|
43
|
+
if (['create_directory', 'start_process', 'interact_with_process', 'force_terminate', 'kill_process', 'host_exec', 'give_feedback_to_desktop_commander'].includes(name)) continue;
|
|
44
|
+
assert.ok(
|
|
45
|
+
contract.required.includes('expected_sha256') || contract.required.includes('expected_source_identity'),
|
|
46
|
+
name
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
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 { generateTrustKeypair } from '../src/trust.mjs';
|
|
10
|
+
|
|
11
|
+
const repoRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
12
|
+
const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
|
|
13
|
+
|
|
14
|
+
test('Commander R47 exact 40-name surface is registered over real MCP stdio',async()=>{
|
|
15
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-protocol-'));
|
|
16
|
+
const trust=path.join(root,'trust'); await mkdir(trust,{recursive:true});
|
|
17
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
18
|
+
const client=new Client({name:'deadbyte-commander-protocol-r47',version:'0.15.15'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
19
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env:{...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:root,DEADBYTE_SIGNING_KEY:keys.privateKeyPath,DEADBYTE_VERIFY_KEY:keys.publicKeyPath,DEADBYTE_PRIVILEGED_POLICY:path.join(repoRoot,'controller','deadbyte-host-policy.json'),DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_CODING_RUNTIME:'0',DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0',DEADBYTE_AUDIT_RUNTIME:'0'},stderr:'pipe'});
|
|
20
|
+
const expected=['contained_transform','create_directory','edit_block','evidence_verify','file_preview','file_read','force_terminate','get_config','get_file_info','get_more_search_results','get_prompts','get_recent_tool_calls','get_usage_stats','give_feedback_to_desktop_commander','host_exec','host_file_read','host_file_write','interact_with_process','kill_process','list_directory','list_processes','list_searches','list_sessions','markdown_editor','move_file','proof_execute','read_file','read_multiple_files','read_process_output','remote_device','remote_mcp','resources','set_config_value','skill_catalog','start_process','start_search','stop_search','workspace_open','write_file','write_pdf'];
|
|
21
|
+
try{
|
|
22
|
+
await client.connect(transport);
|
|
23
|
+
assert.equal(client.getProtocolEra(),'modern');
|
|
24
|
+
assert.equal(client.getNegotiatedProtocolVersion(),'2026-07-28');
|
|
25
|
+
assert.ok(client.getDiscoverResult()?.supportedVersions?.includes('2026-07-28'));
|
|
26
|
+
const listed=await client.listTools();
|
|
27
|
+
const names=listed.tools.map(x=>x.name);
|
|
28
|
+
names.sort();
|
|
29
|
+
for(const name of expected) assert.ok(names.includes(name),'missing tool: '+name);
|
|
30
|
+
assert.equal(new Set(expected).size,40);
|
|
31
|
+
for(const name of expected){const t=listed.tools.find(x=>x.name===name);assert.ok(t.inputSchema,'missing schema: '+name);}
|
|
32
|
+
}finally{await client.close().catch(()=>{});await rm(root,{recursive:true,force:true}).catch(()=>{});}
|
|
33
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
|
+
import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
9
|
+
import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machine-fs.mjs';
|
|
10
|
+
import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
|
|
11
|
+
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
12
|
+
|
|
13
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
14
|
+
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
|
15
|
+
|
|
16
|
+
async function fsFixture(){
|
|
17
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-real-fs-'));
|
|
18
|
+
const project=path.join(root,'project'); await mkdir(project);
|
|
19
|
+
const policyPath=path.join(root,'policy.json'), grantPath=path.join(root,'grant.json');
|
|
20
|
+
const policy=await (async()=>{await writeFile(policyPath,JSON.stringify({
|
|
21
|
+
schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:grantPath,evidence_dir:path.join(root,'evidence'),
|
|
22
|
+
roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:['blocked']}},
|
|
23
|
+
commands:{},max_read_bytes:65536,max_write_bytes:65536
|
|
24
|
+
})); return loadCodingPolicy(policyPath)})();
|
|
25
|
+
await writeFile(grantPath,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:policy.policy_sha256,instance_nonce:'b'.repeat(64),root_ids:['project'],granted_at_utc:new Date().toISOString()}));
|
|
26
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
|
|
27
|
+
return {root,project,policy,keys,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function processFixture(){
|
|
31
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-real-process-'));
|
|
32
|
+
const project=path.join(root,'project'),trust=path.join(root,'trust'); await mkdir(project); await mkdir(trust);
|
|
33
|
+
const host=path.resolve('bin/deadbyte-process-host.exe'), exe='C:\\Windows\\System32\\cmd.exe';
|
|
34
|
+
const h=sha(await readFile(host)), e=sha(await readFile(exe));
|
|
35
|
+
const grantFile=path.join(root,'grant.json'), stateDir=path.join(root,'state'), policyPath=path.join(root,'policy.json');
|
|
36
|
+
await writeFile(policyPath,JSON.stringify({schema:PROCESS_POLICY_SCHEMA,enabled:true,grant_file:grantFile,state_dir:stateDir,
|
|
37
|
+
native_host:{path:host,sha256:h},roots:{core:{path:project,deny_paths:[]}},profiles:{
|
|
38
|
+
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:{}}
|
|
40
|
+
}}));
|
|
41
|
+
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()}));
|
|
43
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
44
|
+
return {root,policy,keys,runtime:createProcessRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('real Commander-parity filesystem lifecycle is transactional and signed',async()=>{
|
|
48
|
+
const f=await fsFixture();
|
|
49
|
+
try{
|
|
50
|
+
const t0=performance.now();
|
|
51
|
+
const made=await f.runtime.mkdir({rootId:'project',relativePath:'x'});
|
|
52
|
+
const w=await f.runtime.write({rootId:'project',relativePath:'x/a.txt',content:'alpha',expectedSha256:'absent'});
|
|
53
|
+
const s=await f.runtime.stat({rootId:'project',relativePath:'x/a.txt',hash:true});
|
|
54
|
+
const page=await f.runtime.list({rootId:'project',relativePath:'x',limit:10,hashFiles:true});
|
|
55
|
+
const r=await f.runtime.read({rootId:'project',relativePath:'x/a.txt',offset:0,maxBytes:5,hashFile:true});
|
|
56
|
+
const m=await f.runtime.move({rootId:'project',sourcePath:'x/a.txt',destinationPath:'x/b.txt',expectedSourceIdentity:s.identity_sha256});
|
|
57
|
+
const final=await f.runtime.stat({rootId:'project',relativePath:'x/b.txt',hash:true});
|
|
58
|
+
const proof=await f.runtime.verifyReceipt(m.receipt_id);
|
|
59
|
+
assert.equal(m.status,'committed'); assert.equal(final.sha256,w.sha256); assert.equal(proof.ok,true);
|
|
60
|
+
assert.deepEqual(page.entries.map(x=>x.path),['x/a.txt']);
|
|
61
|
+
assert.equal(Buffer.from(r.data_base64,'base64').toString(),'alpha');
|
|
62
|
+
assert.ok(performance.now()-t0>=0);
|
|
63
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('real stale mutation is rejected before commit and leaves source intact',async()=>{
|
|
67
|
+
const f=await fsFixture();
|
|
68
|
+
try{
|
|
69
|
+
const w=await f.runtime.write({rootId:'project',relativePath:'race.txt',content:'one',expectedSha256:'absent'});
|
|
70
|
+
const s=await f.runtime.stat({rootId:'project',relativePath:'race.txt',hash:true});
|
|
71
|
+
await writeFile(path.join(f.project,'race.txt'),'two');
|
|
72
|
+
await assert.rejects(()=>f.runtime.move({rootId:'project',sourcePath:'race.txt',destinationPath:'new.txt',expectedSourceIdentity:s.identity_sha256}),/identity.*mismatch/i);
|
|
73
|
+
assert.equal((await readFile(path.join(f.project,'race.txt'),'utf8')),'two');
|
|
74
|
+
assert.equal((await f.runtime.state({rootId:'project',relativePath:'new.txt'})).exists,false);
|
|
75
|
+
assert.ok(w.receipt_id);
|
|
76
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('real deterministic paging and deny boundary survive adversarial paths',async()=>{
|
|
80
|
+
const f=await fsFixture();
|
|
81
|
+
try{
|
|
82
|
+
for(const n of ['c.txt','a.txt','b.txt']) await writeFile(path.join(f.project,n),n);
|
|
83
|
+
await mkdir(path.join(f.project,'blocked'));
|
|
84
|
+
const p1=await f.runtime.list({rootId:'project',relativePath:'.',limit:2});
|
|
85
|
+
const p2=await f.runtime.list({rootId:'project',relativePath:'.',limit:2,cursor:p1.next_cursor});
|
|
86
|
+
assert.deepEqual(p1.entries.map(x=>x.path),['a.txt','blocked']);
|
|
87
|
+
assert.deepEqual(p2.entries.map(x=>x.path),['b.txt','c.txt']);
|
|
88
|
+
await assert.rejects(()=>f.runtime.stat({rootId:'project',relativePath:'blocked'}),/denied/i);
|
|
89
|
+
await assert.rejects(()=>f.runtime.stat({rootId:'project',relativePath:'../escape'}),/relative|path|traversal/i);
|
|
90
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('real owned process lifecycle yields terminal evidence, not PID authority', {skip:process.platform!=='win32'}, async()=>{
|
|
94
|
+
const f=await processFixture();
|
|
95
|
+
try{
|
|
96
|
+
const s=await f.runtime.start({profileId:'echo',rootId:'core',cwd:'.',args:['echo','DEADBYTE_REAL_PARITY']});
|
|
97
|
+
let status=await f.runtime.status({sessionId:s.session_id});
|
|
98
|
+
for(let i=0;i<100&&status.state==='running';i++){await sleep(25);status=await f.runtime.status({sessionId:s.session_id});}
|
|
99
|
+
assert.equal(status.state,'exited'); assert.equal(status.exit_code,0);
|
|
100
|
+
const out=await f.runtime.read({sessionId:s.session_id,offset:0,maxBytes:65536});
|
|
101
|
+
assert.match(Buffer.from(out.data_base64,'base64').toString(),/DEADBYTE_REAL_PARITY/);
|
|
102
|
+
assert.ok(status.operation_id); assert.ok(status.operation_receipt);
|
|
103
|
+
assert.equal((await f.runtime.verifyEventJournal({sessionId:s.session_id})).ok,true);
|
|
104
|
+
assert.equal((await f.runtime.verifyOperationEvidence({sessionId:s.session_id})).ok,true);
|
|
105
|
+
assert.equal(Object.prototype.hasOwnProperty.call(status,'pid'),false);
|
|
106
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('real process cancellation reaches one terminal state with verified evidence', {skip:process.platform!=='win32'}, async()=>{
|
|
110
|
+
const f=await processFixture();
|
|
111
|
+
try{
|
|
112
|
+
const s=await f.runtime.start({profileId:'hold',rootId:'core',cwd:'.',args:[]});
|
|
113
|
+
const cancelled=await f.runtime.cancel({sessionId:s.session_id,reason:'real-parity-cancel'});
|
|
114
|
+
assert.ok(['terminated','exited'].includes(cancelled.state));
|
|
115
|
+
assert.equal(cancelled.termination.verified,true);
|
|
116
|
+
assert.ok(cancelled.operation_receipt);
|
|
117
|
+
const ev=await f.runtime.verifyEventJournal({sessionId:s.session_id});
|
|
118
|
+
const op=await f.runtime.verifyOperationEvidence({sessionId:s.session_id});
|
|
119
|
+
assert.equal(ev.ok,true); assert.equal(op.ok,true);
|
|
120
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
121
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { registerCommanderRemoteBridge } from '../src/commander-remote-bridge-r47.mjs';
|
|
4
|
+
|
|
5
|
+
function fakeServer(){
|
|
6
|
+
const tools=new Map();
|
|
7
|
+
return {tools,registerTool(name,meta,handler){tools.set(name,{meta,handler});}};
|
|
8
|
+
}
|
|
9
|
+
function fakePlane(){
|
|
10
|
+
const calls=[];
|
|
11
|
+
const result=(op,value)=>{calls.push({op,value});return {status:'ok',op,value};};
|
|
12
|
+
return {calls,pair:async value=>result('pair',value),status:async value=>result('status',value),reportOnline:async value=>result('report_online',value),probe:async value=>result('probe',value),dispatch:async value=>result('dispatch',value),revoke:async value=>result('revoke',value),rotateCredential:async value=>result('rotate_credential',value),list:async()=>result('list',{})};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test('R47 remote Commander bridge routes remote_device directly to remote-device-r44 plane',async()=>{
|
|
16
|
+
const server=fakeServer(),plane=fakePlane();
|
|
17
|
+
registerCommanderRemoteBridge(server,{remoteDevicePlane:plane});
|
|
18
|
+
assert.ok(server.tools.has('remote_device'));assert.ok(server.tools.has('remote_mcp'));
|
|
19
|
+
const tool=server.tools.get('remote_device').handler;const id='a'.repeat(64);
|
|
20
|
+
await tool({op:'pair',public_key_spki_base64:'ZmFrZQ==',display_name:'lab'});
|
|
21
|
+
await tool({op:'status',device_id:id});await tool({op:'report_online',device_id:id,epoch:1,connection_id:'c'});
|
|
22
|
+
await tool({op:'probe',device_id:id,epoch:1});await tool({op:'dispatch',device_id:id,epoch:1,capability_id:'machine.status',payload:{},request_id:'b'.repeat(64)});
|
|
23
|
+
await tool({op:'revoke',device_id:id,epoch:1});await tool({op:'rotate_credential',device_id:id,epoch:1,encrypted_bundle_base64:'dG9rZW4=',expected_previous_sha256:null});await tool({op:'list'});
|
|
24
|
+
assert.deepEqual(plane.calls.map(x=>x.op),['pair','status','report_online','probe','dispatch','revoke','rotate_credential','list']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R47 remote_mcp is a thin dispatch surface and never shells out to Commander',async()=>{
|
|
28
|
+
const server=fakeServer(),plane=fakePlane();registerCommanderRemoteBridge(server,{remoteDevicePlane:plane});
|
|
29
|
+
const id='c'.repeat(64),request='d'.repeat(64);
|
|
30
|
+
const result=await server.tools.get('remote_mcp').handler({device_id:id,epoch:7,capability_id:'coding.file_read',payload:{path:'README.md'},request_id:request});
|
|
31
|
+
assert.equal(result.status,'ok');
|
|
32
|
+
assert.deepEqual(plane.calls,[{op:'dispatch',value:{deviceId:id,epoch:7,capabilityId:'coding.file_read',payload:{path:'README.md'},requestId:request}}]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R47 remote surfaces fail closed as unavailable when no remote-device-r44 plane is wired',async()=>{
|
|
36
|
+
const server=fakeServer();registerCommanderRemoteBridge(server,{});
|
|
37
|
+
const result=await server.tools.get('remote_device').handler({op:'list'});
|
|
38
|
+
assert.equal(result.status,'unavailable');assert.match(result.error,/remote-device-r44 plane is not configured/);
|
|
39
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const sha=value=>createHash('sha256').update(value).digest('hex');
|
|
7
|
+
|
|
8
|
+
test('R47 production server wires native remote device plane',async()=>{
|
|
9
|
+
const server=await readFile(new URL('../src/mcp-server.mjs',import.meta.url),'utf8');
|
|
10
|
+
assert.match(server,/createRemoteDevicePlaneR44/);
|
|
11
|
+
assert.match(server,/createCommanderRemoteRelayR47/);
|
|
12
|
+
assert.match(server,/registerCommanderRemoteBridge/);
|
|
13
|
+
assert.match(server,/DEADBYTE_REMOTE_RELAY_ENDPOINT\|\|process\.env\.DEADBYTE_REMOTE_ENDPOINT/);
|
|
14
|
+
assert.match(server,/DEADBYTE_REMOTE_CAPABILITIES/);
|
|
15
|
+
assert.match(server,/remote-device-r44/);
|
|
16
|
+
assert.equal((server.match(/commander-remote-relay-r47\.mjs/g)??[]).length,1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('R47 relay is native MCP client and contains no Desktop Commander dependency',async()=>{
|
|
20
|
+
const relay=await readFile(new URL('../src/commander-remote-relay-r47.mjs',import.meta.url),'utf8');
|
|
21
|
+
assert.match(relay,/StreamableHTTPClientTransport/);
|
|
22
|
+
assert.match(relay,/client\.callTool/);
|
|
23
|
+
assert.doesNotMatch(relay,/@wonderwhy-er\/desktop-commander/);
|
|
24
|
+
assert.equal(sha(relay).length,64);
|
|
25
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
7
|
+
import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
8
|
+
import { createMachineFsRuntime } from '../src/machine-fs.mjs';
|
|
9
|
+
import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
|
|
10
|
+
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
|
|
13
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
14
|
+
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
|
15
|
+
|
|
16
|
+
async function fsFixture(){
|
|
17
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-torture-fs-')), project=path.join(root,'project');
|
|
18
|
+
await mkdir(project);
|
|
19
|
+
const policyPath=path.join(root,'policy.json'), grantPath=path.join(root,'grant.json');
|
|
20
|
+
await writeFile(policyPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:grantPath,evidence_dir:path.join(root,'evidence'),roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:['blocked']}},commands:{},max_read_bytes:65536,max_write_bytes:65536}));
|
|
21
|
+
const policy=await loadCodingPolicy(policyPath);
|
|
22
|
+
await writeFile(grantPath,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:policy.policy_sha256,instance_nonce:'c'.repeat(64),root_ids:['project'],granted_at_utc:new Date().toISOString()}));
|
|
23
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
|
|
24
|
+
return {root,project,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function processFixture(){
|
|
28
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-torture-proc-')), project=path.join(root,'project'),trust=path.join(root,'trust');
|
|
29
|
+
await mkdir(project); await mkdir(trust);
|
|
30
|
+
const host=path.resolve('bin/deadbyte-process-host.exe'), exe='C:\\Windows\\System32\\cmd.exe';
|
|
31
|
+
const grantFile=path.join(root,'grant.json'), stateDir=path.join(root,'state'), policyPath=path.join(root,'policy.json');
|
|
32
|
+
await writeFile(policyPath,JSON.stringify({schema:PROCESS_POLICY_SCHEMA,enabled:true,grant_file:grantFile,state_dir:stateDir,native_host:{path:host,sha256:sha(await readFile(host))},roots:{core:{path:project,deny_paths:[]}},profiles:{
|
|
33
|
+
echo:{executable:exe,executable_sha256:sha(await readFile(exe)),root_ids:['core'],prefix_args:['/d','/c'],max_args:4,max_arg_bytes:256,max_sessions:8,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}},
|
|
34
|
+
hold:{executable:exe,executable_sha256:sha(await readFile(exe)),root_ids:['core'],transport:'pipe',prefix_args:['/d','/q'],max_args:0,max_arg_bytes:256,max_sessions:8,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}}
|
|
35
|
+
}}));
|
|
36
|
+
const policy=await loadProcessPolicy(policyPath);
|
|
37
|
+
await writeFile(grantFile,JSON.stringify({schema:PROCESS_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:'d'.repeat(64),profile_ids:['echo','hold'],root_ids:['core'],granted_at_utc:new Date().toISOString()}));
|
|
38
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
39
|
+
return {root,runtime:createProcessRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test('torture: concurrent stale writers permit exactly one winner per version',async()=>{
|
|
43
|
+
const f=await fsFixture();
|
|
44
|
+
try{
|
|
45
|
+
let expected='absent', winners=0, conflicts=0;
|
|
46
|
+
for(let round=0;round<12;round++){
|
|
47
|
+
const target='race.txt';
|
|
48
|
+
const before=await f.runtime.state({rootId:'project',relativePath:target});
|
|
49
|
+
expected=before.exists?before.sha256:'absent';
|
|
50
|
+
const results=await Promise.allSettled(Array.from({length:8},(_,i)=>f.runtime.write({rootId:'project',relativePath:target,content:`round=${round};writer=${i}`,expectedSha256:expected})));
|
|
51
|
+
const ok=results.filter(x=>x.status==='fulfilled'&&x.value.status==='committed');
|
|
52
|
+
winners+=ok.length; conflicts+=results.filter(x=>x.status==='rejected').length;
|
|
53
|
+
assert.equal(ok.length,1);
|
|
54
|
+
assert.equal(conflicts>=round+7,true);
|
|
55
|
+
const current=await f.runtime.stat({rootId:'project',relativePath:target,hash:true});
|
|
56
|
+
assert.equal(current.sha256,ok[0].value.sha256);
|
|
57
|
+
}
|
|
58
|
+
assert.equal(winners,12);
|
|
59
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('torture: move/delete races cannot duplicate or silently lose an object',async()=>{
|
|
63
|
+
const f=await fsFixture();
|
|
64
|
+
try{
|
|
65
|
+
await f.runtime.write({rootId:'project',relativePath:'source.txt',content:'immutable',expectedSha256:'absent'});
|
|
66
|
+
const s=await f.runtime.stat({rootId:'project',relativePath:'source.txt',hash:true});
|
|
67
|
+
const [a,b]=await Promise.allSettled([
|
|
68
|
+
f.runtime.move({rootId:'project',sourcePath:'source.txt',destinationPath:'a.txt',expectedSourceIdentity:s.identity_sha256}),
|
|
69
|
+
f.runtime.move({rootId:'project',sourcePath:'source.txt',destinationPath:'b.txt',expectedSourceIdentity:s.identity_sha256})
|
|
70
|
+
]);
|
|
71
|
+
assert.equal([a,b].filter(x=>x.status==='fulfilled').length,1);
|
|
72
|
+
const stateA=await f.runtime.state({rootId:'project',relativePath:'a.txt'});
|
|
73
|
+
const stateB=await f.runtime.state({rootId:'project',relativePath:'b.txt'});
|
|
74
|
+
assert.equal(Number(stateA.exists)+Number(stateB.exists),1);
|
|
75
|
+
const winner=stateA.exists?stateA:stateB;
|
|
76
|
+
assert.equal(winner.sha256,sha(Buffer.from('immutable')));
|
|
77
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('torture: pagination remains deterministic under concurrent directory churn',async()=>{
|
|
81
|
+
const f=await fsFixture();
|
|
82
|
+
try{
|
|
83
|
+
for(let i=0;i<40;i++) await writeFile(path.join(f.project,`f${String(i).padStart(2,'0')}.txt`),String(i));
|
|
84
|
+
let cursor=null;
|
|
85
|
+
const first=await f.runtime.list({rootId:'project',relativePath:'.',limit:7,cursor});
|
|
86
|
+
assert.equal(first.entries.length,7);
|
|
87
|
+
cursor=first.next_cursor;
|
|
88
|
+
const churn=(async()=>{for(let i=0;i<20;i++){
|
|
89
|
+
const p=path.join(f.project,"z"+i+".tmp");
|
|
90
|
+
await writeFile(p,'z');
|
|
91
|
+
await rm(p,{force:true});
|
|
92
|
+
}
|
|
93
|
+
await writeFile(path.join(f.project,'persistent-churn.txt'),'changed');})();
|
|
94
|
+
await churn;
|
|
95
|
+
await assert.rejects(
|
|
96
|
+
()=>f.runtime.list({rootId:'project',relativePath:'.',limit:7,cursor}),
|
|
97
|
+
/cursor snapshot changed/i
|
|
98
|
+
);
|
|
99
|
+
} finally {
|
|
100
|
+
for (let i=0;i<20;i++) {
|
|
101
|
+
try { await rm(f.root,{recursive:true,force:true}); break; }
|
|
102
|
+
catch (error) { if (error?.code!=='EBUSY' && error?.code!=='ENOTEMPTY') throw error; await sleep(50); }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('torture: process cancellation race converges to one verified terminal state', {skip:process.platform!=='win32'}, async()=>{
|
|
108
|
+
const f=await processFixture();
|
|
109
|
+
try{
|
|
110
|
+
const s=await f.runtime.start({profileId:'hold',rootId:'core',cwd:'.',args:[]});
|
|
111
|
+
const results=await Promise.allSettled([
|
|
112
|
+
f.runtime.cancel({sessionId:s.session_id,reason:'race-a'}),
|
|
113
|
+
f.runtime.cancel({sessionId:s.session_id,reason:'race-b'}),
|
|
114
|
+
f.runtime.status({sessionId:s.session_id})
|
|
115
|
+
]);
|
|
116
|
+
const cancels=results.filter(x=>x.status==='fulfilled'&&x.value.termination);
|
|
117
|
+
assert.ok(cancels.length>=1);
|
|
118
|
+
const deadline=Date.now()+5000; let status=await f.runtime.status({sessionId:s.session_id});
|
|
119
|
+
while(status.state==='running'&&Date.now()<deadline){await sleep(20);status=await f.runtime.status({sessionId:s.session_id});}
|
|
120
|
+
assert.notEqual(status.state,'running');
|
|
121
|
+
assert.ok(status.operation_receipt); assert.ok(status.operation_id);
|
|
122
|
+
assert.equal((await f.runtime.verifyEventJournal({sessionId:s.session_id})).ok,true);
|
|
123
|
+
assert.equal((await f.runtime.verifyOperationEvidence({sessionId:s.session_id})).ok,true);
|
|
124
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('torture: process session identity cannot be replaced by a foreign session id', {skip:process.platform!=='win32'}, async()=>{
|
|
128
|
+
const f=await processFixture();
|
|
129
|
+
try{
|
|
130
|
+
const a=await f.runtime.start({profileId:'echo',rootId:'core',cwd:'.',args:['echo','A']});
|
|
131
|
+
const b=await f.runtime.start({profileId:'echo',rootId:'core',cwd:'.',args:['echo','B']});
|
|
132
|
+
await assert.rejects(()=>f.runtime.read({sessionId:'f'.repeat(64),offset:0,maxBytes:64}),/session|not found|unknown/i);
|
|
133
|
+
await f.runtime.cancel({sessionId:a.session_id,reason:'cleanup'});
|
|
134
|
+
await f.runtime.cancel({sessionId:b.session_id,reason:'cleanup'});
|
|
135
|
+
for (const id of [a.session_id,b.session_id]) {
|
|
136
|
+
let s=await f.runtime.status({sessionId:id});
|
|
137
|
+
for(let i=0;i<100&&s.state==='running';i++){await sleep(20);s=await f.runtime.status({sessionId:id});}
|
|
138
|
+
assert.notEqual(s.state,'running');
|
|
139
|
+
}
|
|
140
|
+
for(let i=0;i<20;i++){
|
|
141
|
+
try { await rm(f.root,{recursive:true,force:true}); return; }
|
|
142
|
+
catch(error){ if(error?.code!=='EBUSY') throw error; await sleep(50); }
|
|
143
|
+
}
|
|
144
|
+
await rm(f.root,{recursive:true,force:true});
|
|
145
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
146
|
+
});
|