deadbyte-mcp 0.14.0 → 0.15.1
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/AGENTS.md +11 -18
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +156 -70
- package/PROVENANCE.json +10 -0
- package/R43-PARENT.json +20 -0
- package/R44-DONORS.json +99 -0
- package/R44-PARENT.json +18 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +19 -14
- package/RELEASE.json +37 -0
- package/SBOM.cdx.json +46 -0
- package/TEST-MATRIX.json +31 -0
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/controller/README.TXT +5 -6
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/R44-ARCHITECTURE.md +37 -0
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +18 -8
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r43.mjs +126 -0
- package/scripts/deferred-slot-operation-r44.mjs +126 -0
- package/scripts/deferred-slot-operation-r45.mjs +126 -0
- package/scripts/derive-r45-parent.mjs +24 -0
- package/scripts/derive-r45-release.mjs +72 -0
- package/scripts/derive-r45-tests.mjs +20 -0
- package/scripts/final-closure-r43.mjs +115 -0
- package/scripts/final-closure-r44.mjs +121 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-verify-r43.mjs +213 -0
- package/scripts/final-closure-verify-r44.mjs +220 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r43.mjs +217 -0
- package/scripts/finalize-r44.mjs +217 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r43.mjs +20 -0
- package/scripts/full-regression-r44.mjs +20 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r43.ps1 +36 -0
- package/scripts/gate-windows-r44.ps1 +36 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r43.mjs +218 -0
- package/scripts/release-parity-r44.mjs +242 -0
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-tests-r43.ps1 +8 -0
- package/scripts/release-parity-tests-r44.ps1 +8 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r43-parent.mjs +126 -0
- package/scripts/verify-r44-parent.mjs +142 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r43.mjs +55 -0
- package/scripts/windows-gate-evidence-r44.mjs +55 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/agent-backend-r44.mjs +254 -0
- package/src/autonomous-output-contracts.mjs +1 -1
- package/src/autonomous-policy.mjs +41 -4
- package/src/autonomous-runtime.mjs +37 -19
- package/src/autonomous-supervisor-host.mjs +12 -2
- package/src/capability-ledger.mjs +3 -1
- package/src/codex-app-server-r44.mjs +154 -0
- package/src/coding-plane.mjs +66 -19
- package/src/compact-gateway-mcp-tools.mjs +4 -2
- package/src/deadbyte-cli.mjs +17 -9
- package/src/doctor-r45.mjs +65 -0
- package/src/durable-job-r44.mjs +145 -0
- package/src/external-job-provider-r44.mjs +126 -0
- package/src/host-plane.mjs +156 -87
- package/src/job-supervisor-host-r44.mjs +112 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +21 -10
- package/src/mcp-host-smoke-client.mjs +1 -1
- package/src/mcp-server.mjs +84 -26
- package/src/mcp-smoke-client.mjs +1 -1
- package/src/native-diff-v2.mjs +12 -7
- package/src/native-diff.mjs +32 -9
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +33 -2
- package/src/observation-smoke-client.mjs +7 -3
- package/src/operator-surface.mjs +2 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-policy.mjs +19 -0
- package/src/process-runtime.mjs +5 -2
- package/src/process-smoke-client.mjs +2 -2
- package/src/provider-circuit.mjs +8 -2
- package/src/r44-compact-disarmed-probe.mjs +74 -0
- package/src/r44-full-surface-probe.mjs +34 -0
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +1 -0
- package/src/remote-device-r44.mjs +205 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
- package/src/remote-mcp-r44-parity-client.mjs +203 -0
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +7 -4
- package/src/runtime-update.mjs +100 -39
- package/src/skill-mcp-tools-r44.mjs +37 -0
- package/src/skill-plane-r44.mjs +203 -0
- package/src/tunnel-stdio-compat-smoke.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-backend-r44.test.mjs +115 -0
- package/test/agent-runtime.test.mjs +4 -3
- package/test/autonomous-loop.test.mjs +121 -11
- package/test/autonomous-runtime.test.mjs +3 -2
- package/test/capability-ledger-r36.test.mjs +3 -3
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/codex-app-server-r44.test.mjs +115 -0
- package/test/codex-autonomous-r44.test.mjs +87 -0
- package/test/coding-plane.test.mjs +3 -2
- package/test/coding-runtime.test.mjs +36 -1
- package/test/compact-catalog-r36.test.mjs +3 -3
- package/test/compact-max-catalog-r40.test.mjs +4 -2
- package/test/compact-max-catalog-r44.test.mjs +97 -0
- package/test/compact-max-catalog-r45.test.mjs +99 -0
- package/test/contained.test.mjs +23 -11
- package/test/controller.test.mjs +5 -1
- package/test/core.test.mjs +1 -1
- package/test/durable-job-r44.test.mjs +75 -0
- package/test/external-job-provider-r44.test.mjs +46 -0
- package/test/full-max-catalog-r40.test.mjs +5 -3
- package/test/full-max-catalog-r44.test.mjs +91 -0
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/host-plane.test.mjs +15 -1
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/native-diff.test.mjs +28 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +3 -2
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-policy.test.mjs +22 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r33-closeout-regression.test.mjs +20 -36
- package/test/r33-finalization.test.mjs +7 -7
- package/test/r43-finalization.test.mjs +31 -0
- package/test/r43-finalizer.test.mjs +34 -0
- package/test/r43-parent.test.mjs +29 -0
- package/test/r44-finalization.test.mjs +58 -0
- package/test/r44-finalizer.test.mjs +48 -0
- package/test/r44-parent.test.mjs +25 -0
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +1 -0
- package/test/release-version.test.mjs +65 -20
- package/test/remote-device-r44.test.mjs +104 -0
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
- package/test/runtime-self-update.test.mjs +42 -0
- package/test/skill-mcp-r44.test.mjs +38 -0
- package/test/skill-plane-r44.test.mjs +93 -0
- package/test/standing-grants.test.mjs +5 -1
|
@@ -5,7 +5,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
|
-
import { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
8
|
+
import { assertCodingArmed, createCodingRuntime, loadCodingPolicy, recheckCodingAuthority } from '../src/coding-plane.mjs';
|
|
9
9
|
import { createCodingTaskRuntime } from '../src/coding-task-runtime.mjs';
|
|
10
10
|
|
|
11
11
|
function sha256Bytes(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
|
|
@@ -78,6 +78,41 @@ test('coding plane rejects mutation when lease is absent or precondition is stal
|
|
|
78
78
|
await assert.rejects(() => f.runtime.fileWrite({ rootId: 'project', relativePath: 'x.txt', content: 'x', expectedSha256: 'absent' }), /coding plane is disarmed/);
|
|
79
79
|
} finally { await rm(f.root, { recursive: true, force: true }); }
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
test('coding authority recheck rejects lease generation or expiry drift before commit', async () => {
|
|
83
|
+
const f = await fixture();
|
|
84
|
+
try {
|
|
85
|
+
const initial = await assertCodingArmed(f.policy, 'project');
|
|
86
|
+
const lease = JSON.parse(await readFile(f.policy.lease_file, 'utf8'));
|
|
87
|
+
await writeFile(f.policy.lease_file, JSON.stringify({ ...lease, instance_nonce: 'b'.repeat(64) }));
|
|
88
|
+
await assert.rejects(() => recheckCodingAuthority(f.policy, initial, 'project'), /coding authority changed during operation/);
|
|
89
|
+
await writeFile(f.policy.lease_file, JSON.stringify({
|
|
90
|
+
...lease,
|
|
91
|
+
expires_at_utc: new Date(Date.parse(lease.expires_at_utc) + 60000).toISOString()
|
|
92
|
+
}));
|
|
93
|
+
await assert.rejects(() => recheckCodingAuthority(f.policy, initial, 'project'), /coding authority changed during operation/);
|
|
94
|
+
} finally { await rm(f.root, { recursive: true, force: true }); }
|
|
95
|
+
});
|
|
96
|
+
test('coding execution deadline bounds runtime without changing the signed request identity', async () => {
|
|
97
|
+
const f = await fixture();
|
|
98
|
+
try {
|
|
99
|
+
const script = path.join(f.project, 'deadline.mjs');
|
|
100
|
+
await writeFile(script, "await new Promise(resolve => setTimeout(resolve, 10000));\n");
|
|
101
|
+
const scriptHash = await sha256File(script);
|
|
102
|
+
const args = { rootId:'project', commandId:'node_script', scriptPath:'deadline.mjs', scriptSha256:scriptHash,
|
|
103
|
+
timeoutMs:20000, executionDeadlineUtc:new Date(Date.now()+3000).toISOString() };
|
|
104
|
+
const expectedRequest = f.runtime.requestSha('coding_command_run', {
|
|
105
|
+
root_id:'project', command_id:'node_script', script_path:'deadline.mjs', script_sha256:scriptHash,
|
|
106
|
+
args:[], cwd:'.', timeout_ms:20000
|
|
107
|
+
});
|
|
108
|
+
const result = await f.runtime.commandRun(args);
|
|
109
|
+
assert.equal(result.status, 'observed_failure');
|
|
110
|
+
assert.equal(result.timed_out, true);
|
|
111
|
+
assert.ok(result.timeout_ms >= 500 && result.timeout_ms <= 3000);
|
|
112
|
+
assert.equal(result.request_sha256, expectedRequest);
|
|
113
|
+
} finally { await rm(f.root, { recursive:true, force:true }); }
|
|
114
|
+
});
|
|
115
|
+
|
|
81
116
|
test('coding command and benchmark produce signed observations', async () => {
|
|
82
117
|
const f = await fixture();
|
|
83
118
|
try {
|
|
@@ -10,7 +10,7 @@ const repoRoot=path.resolve(here,'..');
|
|
|
10
10
|
const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
|
|
11
11
|
const gatewayNames=['capability_manifest','coding_inspect','desktop_control','machine_fs','machine_observe','process_session'];
|
|
12
12
|
|
|
13
|
-
test('
|
|
13
|
+
test('R44 real compact stdio catalog stays <=43 and exposes all gateways plus skill_catalog',async()=>{
|
|
14
14
|
const client=new Client({name:'deadbyte-r36-compact-test',version:'0.10.0-dev'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
15
15
|
const env={
|
|
16
16
|
...getDefaultEnvironment(),
|
|
@@ -24,8 +24,8 @@ test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async(
|
|
|
24
24
|
await client.connect(transport);
|
|
25
25
|
const listed=await client.listTools();
|
|
26
26
|
const names=listed.tools.map(tool=>tool.name).sort();
|
|
27
|
-
assert.ok(names.length<=
|
|
28
|
-
for(const name of gatewayNames) assert.ok(names.includes(name),`missing ${name}`);
|
|
27
|
+
assert.ok(names.length<=43,`compact tool count ${names.length} exceeds 43`);
|
|
28
|
+
for(const name of [...gatewayNames,'skill_catalog']) assert.ok(names.includes(name),`missing ${name}`);
|
|
29
29
|
for(const legacy of ['workspace_open','file_read','proof_execute','contained_transform']) assert.equal(names.includes(legacy),false,`legacy full-only tool leaked: ${legacy}`);
|
|
30
30
|
const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
|
|
31
31
|
assert.equal(manifest.isError,false);
|
|
@@ -10,7 +10,9 @@ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
|
10
10
|
|
|
11
11
|
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const repoRoot=path.resolve(here,'..');
|
|
13
|
-
const
|
|
13
|
+
const parent=JSON.parse(await readFile(path.join(repoRoot,'R44-PARENT.json'),'utf8'));
|
|
14
|
+
const parentRoot=parent.parent_release_root;
|
|
15
|
+
const serverPath=path.join(parentRoot,'src','mcp-server.mjs');
|
|
14
16
|
const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
|
|
15
17
|
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
16
18
|
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
@@ -70,7 +72,7 @@ test('R40 real max-runtime compact stdio catalog is exactly 42 while all authori
|
|
|
70
72
|
DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
71
73
|
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
|
|
72
74
|
};
|
|
73
|
-
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:
|
|
75
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:parentRoot,env,stderr:'pipe'});
|
|
74
76
|
try{
|
|
75
77
|
await client.connect(transport);
|
|
76
78
|
const listed=await client.listTools();
|
|
@@ -0,0 +1,97 @@
|
|
|
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('R44 real max-runtime compact stdio catalog is exactly 43 with verified skill_catalog while all authority remains disarmed',async()=>{
|
|
19
|
+
const f=await createR34Fixture({authority:'disarmed'});
|
|
20
|
+
const processPolicyPath=path.join(f.root,'process-policy.json');
|
|
21
|
+
const processGrant=path.join(f.root,'process-grant.json');
|
|
22
|
+
const processState=path.join(f.root,'process-state');
|
|
23
|
+
const hostPolicyPath=path.join(f.root,'host-policy.json');
|
|
24
|
+
const hostArm=path.join(f.root,'host-arm.json');
|
|
25
|
+
const nodeSha=sha(await readFile(process.execPath));
|
|
26
|
+
await mkdir(processState,{recursive:true});
|
|
27
|
+
await writeFile(processPolicyPath,JSON.stringify({
|
|
28
|
+
schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
|
|
29
|
+
native_host:{path:process.execPath,sha256:nodeSha},
|
|
30
|
+
roots:{core:{path:f.project,deny_paths:[]}},
|
|
31
|
+
profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
|
|
32
|
+
prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
|
|
33
|
+
max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
|
|
34
|
+
input_actions:{ping:'PING\r\n'}}}
|
|
35
|
+
},null,2));
|
|
36
|
+
await writeFile(hostPolicyPath,JSON.stringify({
|
|
37
|
+
schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
|
|
38
|
+
roots:{core:{path:f.project,read:true,write:true,exec:true}},
|
|
39
|
+
commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
|
|
40
|
+
root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
|
|
41
|
+
},null,2));
|
|
42
|
+
|
|
43
|
+
const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
|
|
44
|
+
const desktopGrant=desktopRaw.grant_file;
|
|
45
|
+
const codingLease=f.codingPolicy.lease_file;
|
|
46
|
+
const autonomousLease=f.policy.lease_file;
|
|
47
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
|
|
48
|
+
|
|
49
|
+
const client=new Client({name:'deadbyte-r40-max-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
50
|
+
const env={
|
|
51
|
+
...getDefaultEnvironment(),
|
|
52
|
+
DEADBYTE_CAPABILITY_PROFILE:'compact',
|
|
53
|
+
DEADBYTE_AUDIT_RUNTIME:'0',
|
|
54
|
+
DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
|
|
55
|
+
DEADBYTE_WORKSPACE_ROOT:f.root,
|
|
56
|
+
DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
|
|
57
|
+
DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
|
|
58
|
+
DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
|
|
59
|
+
DEADBYTE_MACHINE_RUNTIME:'1',
|
|
60
|
+
DEADBYTE_AGENT_RUNTIME:'0',
|
|
61
|
+
DEADBYTE_PROCESS_RUNTIME:'1',
|
|
62
|
+
DEADBYTE_PROCESS_POLICY:processPolicyPath,
|
|
63
|
+
DEADBYTE_CODING_RUNTIME:'1',
|
|
64
|
+
DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
|
|
65
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
66
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
67
|
+
DEADBYTE_DESKTOP_RUNTIME:'1',
|
|
68
|
+
DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
|
|
69
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
70
|
+
DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
71
|
+
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
|
|
72
|
+
};
|
|
73
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
|
|
74
|
+
try{
|
|
75
|
+
await client.connect(transport);
|
|
76
|
+
const listed=await client.listTools();
|
|
77
|
+
const names=listed.tools.map(t=>t.name).sort();
|
|
78
|
+
assert.equal(names.length,43,'expected exact compact max catalog 43, got '+names.length+': '+names.join(','));
|
|
79
|
+
for(const name of ['capability_manifest','desktop_control','coding_inspect','machine_fs','machine_observe','process_session',
|
|
80
|
+
'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
|
|
81
|
+
const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
|
|
82
|
+
assert.equal(manifest.isError,false);
|
|
83
|
+
const m=manifest.structuredContent.result;
|
|
84
|
+
assert.equal(m.schema,'deadbyte.capability-manifest.v2');
|
|
85
|
+
assert.equal(m.profile,'compact');
|
|
86
|
+
assert.equal(m.tool_count,43);
|
|
87
|
+
assert.deepEqual(m.tool_catalog,names);
|
|
88
|
+
assert.match(m.catalog_sha256,/^[0-9a-f]{64}$/);
|
|
89
|
+
const skill=m.capabilities.find(item=>item.id==='skill.catalog');
|
|
90
|
+
assert.ok(skill);assert.equal(skill.authority,'none');assert.equal(skill.mutating,false);
|
|
91
|
+
console.log(JSON.stringify({tool_count:m.tool_count,catalog_sha256:m.catalog_sha256,ledger_sha256:m.ledger_sha256,desktop:true,skill:true,authority:'DISARMED'}));
|
|
92
|
+
} finally {
|
|
93
|
+
await client.close().catch(()=>{});
|
|
94
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
95
|
+
await f.cleanup();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
8
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
9
|
+
import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
10
|
+
|
|
11
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const repoRoot=path.resolve(here,'..');
|
|
13
|
+
const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
|
|
14
|
+
const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
|
|
15
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
16
|
+
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
|
+
|
|
18
|
+
test('R45 real max-runtime compact stdio catalog is exactly 43 with verified skill_catalog while all authority remains disarmed',async()=>{
|
|
19
|
+
const f=await createR34Fixture({authority:'disarmed'});
|
|
20
|
+
const processPolicyPath=path.join(f.root,'process-policy.json');
|
|
21
|
+
const processGrant=path.join(f.root,'process-grant.json');
|
|
22
|
+
const processState=path.join(f.root,'process-state');
|
|
23
|
+
const hostPolicyPath=path.join(f.root,'host-policy.json');
|
|
24
|
+
const hostArm=path.join(f.root,'host-arm.json');
|
|
25
|
+
const nodeSha=sha(await readFile(process.execPath));
|
|
26
|
+
await mkdir(processState,{recursive:true});
|
|
27
|
+
await writeFile(processPolicyPath,JSON.stringify({
|
|
28
|
+
schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
|
|
29
|
+
native_host:{path:process.execPath,sha256:nodeSha},
|
|
30
|
+
roots:{core:{path:f.project,deny_paths:[]}},
|
|
31
|
+
profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
|
|
32
|
+
prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
|
|
33
|
+
max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
|
|
34
|
+
input_actions:{ping:'PING\r\n'}}}
|
|
35
|
+
},null,2));
|
|
36
|
+
await writeFile(hostPolicyPath,JSON.stringify({
|
|
37
|
+
schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
|
|
38
|
+
roots:{core:{path:f.project,read:true,write:true,exec:true}},
|
|
39
|
+
commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
|
|
40
|
+
root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
|
|
41
|
+
},null,2));
|
|
42
|
+
|
|
43
|
+
const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
|
|
44
|
+
const desktopGrant=desktopRaw.grant_file;
|
|
45
|
+
const codingLease=f.codingPolicy.lease_file;
|
|
46
|
+
const autonomousLease=f.policy.lease_file;
|
|
47
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
|
|
48
|
+
|
|
49
|
+
const client=new Client({name:'deadbyte-r40-max-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
50
|
+
const env={
|
|
51
|
+
...getDefaultEnvironment(),
|
|
52
|
+
DEADBYTE_CAPABILITY_PROFILE:'compact',
|
|
53
|
+
DEADBYTE_AUDIT_RUNTIME:'0',
|
|
54
|
+
DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
|
|
55
|
+
DEADBYTE_WORKSPACE_ROOT:f.root,
|
|
56
|
+
DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
|
|
57
|
+
DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
|
|
58
|
+
DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
|
|
59
|
+
DEADBYTE_MACHINE_RUNTIME:'1',
|
|
60
|
+
DEADBYTE_AGENT_RUNTIME:'0',
|
|
61
|
+
DEADBYTE_PROCESS_RUNTIME:'1',
|
|
62
|
+
DEADBYTE_PROCESS_POLICY:processPolicyPath,
|
|
63
|
+
DEADBYTE_CODING_RUNTIME:'1',
|
|
64
|
+
DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
|
|
65
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
66
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
67
|
+
DEADBYTE_DESKTOP_RUNTIME:'1',
|
|
68
|
+
DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
|
|
69
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
70
|
+
DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
71
|
+
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
|
|
72
|
+
};
|
|
73
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
|
|
74
|
+
try{
|
|
75
|
+
await client.connect(transport);
|
|
76
|
+
const listed=await client.listTools();
|
|
77
|
+
const names=listed.tools.map(t=>t.name).sort();
|
|
78
|
+
assert.equal(names.length,43,'expected exact compact max catalog 43, got '+names.length+': '+names.join(','));
|
|
79
|
+
for(const name of ['capability_manifest','desktop_control','coding_inspect','machine_fs','machine_observe','process_session',
|
|
80
|
+
'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
|
|
81
|
+
const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
|
|
82
|
+
assert.equal(manifest.isError,false);
|
|
83
|
+
const m=manifest.structuredContent.result;
|
|
84
|
+
assert.equal(m.schema,'deadbyte.capability-manifest.v2');
|
|
85
|
+
assert.equal(m.profile,'compact');
|
|
86
|
+
assert.equal(m.tool_count,43);
|
|
87
|
+
assert.deepEqual(m.tool_catalog,names);
|
|
88
|
+
assert.match(m.catalog_sha256,/^[0-9a-f]{64}$/);
|
|
89
|
+
const hardware=m.capabilities.find(item=>item.id==='observation.hardware_query');
|
|
90
|
+
assert.ok(hardware);assert.equal(hardware.authority,'none');assert.equal(hardware.compact_tool,'machine_observe');assert.equal(hardware.compact_op,'hardware_query');
|
|
91
|
+
const skill=m.capabilities.find(item=>item.id==='skill.catalog');
|
|
92
|
+
assert.ok(skill);assert.equal(skill.authority,'none');assert.equal(skill.mutating,false);
|
|
93
|
+
console.log(JSON.stringify({tool_count:m.tool_count,catalog_sha256:m.catalog_sha256,ledger_sha256:m.ledger_sha256,desktop:true,hardware_query:true,skill:true,authority:'DISARMED'}));
|
|
94
|
+
} finally {
|
|
95
|
+
await client.close().catch(()=>{});
|
|
96
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
97
|
+
await f.cleanup();
|
|
98
|
+
}
|
|
99
|
+
});
|
package/test/contained.test.mjs
CHANGED
|
@@ -352,6 +352,10 @@ test('contained execution source has a compile-time worker registry and no user
|
|
|
352
352
|
const executor = await readFile(fileURLToPath(new URL('../src/contained-executor.mjs', import.meta.url)), 'utf8');
|
|
353
353
|
const registry = await readFile(fileURLToPath(new URL('../src/worker-registry.mjs', import.meta.url)), 'utf8');
|
|
354
354
|
const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
|
|
355
|
+
const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
|
|
356
|
+
const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
|
|
357
|
+
const contained = server.slice(containedStart, containedEnd);
|
|
358
|
+
assert.ok(containedStart >= 0 && containedEnd > containedStart);
|
|
355
359
|
assert.match(registry, /contained-transform-worker\.exe/);
|
|
356
360
|
assert.match(registry, /contained-reverse-worker\.exe/);
|
|
357
361
|
assert.match(registry, /CONTAINED_EXECUTION_REGISTRY/);
|
|
@@ -360,9 +364,9 @@ test('contained execution source has a compile-time worker registry and no user
|
|
|
360
364
|
assert.match(registry, /lowercase_ascii/);
|
|
361
365
|
assert.match(registry, /reverse_bytes/);
|
|
362
366
|
assert.match(executor, /loadVerifiedWorkerRegistry/);
|
|
363
|
-
assert.match(
|
|
364
|
-
assert.match(
|
|
365
|
-
assert.doesNotMatch(
|
|
367
|
+
assert.match(contained, /'contained_transform'/);
|
|
368
|
+
assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
|
|
369
|
+
assert.doesNotMatch(contained, /executable:\s*z\./);
|
|
366
370
|
assert.doesNotMatch(executor, /--worker/);
|
|
367
371
|
assert.doesNotMatch(server, /registerTool\(\s*['"](?:command_run|shell_execute|powershell_run)['"]/);
|
|
368
372
|
});
|
|
@@ -706,12 +710,16 @@ test('contained execution validator rejects missing or altered worker manifest e
|
|
|
706
710
|
|
|
707
711
|
test('MCP contained_transform schema is a compile-time operation enum with no executable or invocation-control surface', async () => {
|
|
708
712
|
const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
|
|
709
|
-
|
|
710
|
-
|
|
713
|
+
const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
|
|
714
|
+
const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
|
|
715
|
+
const contained = server.slice(containedStart, containedEnd);
|
|
716
|
+
assert.ok(containedStart >= 0 && containedEnd > containedStart);
|
|
717
|
+
assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
|
|
718
|
+
assert.match(contained, /inputSchema: z\.strictObject\(\{/);
|
|
711
719
|
assert.match(server, /if \(hostPolicy\)/);
|
|
712
720
|
for (const name of ['host_file_read', 'host_file_write', 'host_exec']) assert.match(server, new RegExp(`'${name}'`));
|
|
713
|
-
assert.doesNotMatch(
|
|
714
|
-
assert.doesNotMatch(
|
|
721
|
+
assert.doesNotMatch(contained, /inputSchema: z\.object\(\{/);
|
|
722
|
+
assert.doesNotMatch(contained, /executable:\s*z\.|argv:\s*z\.|environment:\s*z\.|challenge:\s*z\.|request_digest:\s*z\.|worker_id:\s*z\./);
|
|
715
723
|
});
|
|
716
724
|
|
|
717
725
|
test('R27 Windows batch gate builds native payload before freezing and verifying the candidate', async () => {
|
|
@@ -914,13 +922,17 @@ test('contained execution validator fails closed on active network evidence', ()
|
|
|
914
922
|
|
|
915
923
|
test('execution authority is strict at MCP and native argv boundaries', async () => {
|
|
916
924
|
const server = await readFile(fileURLToPath(new URL('../src/mcp-server.mjs', import.meta.url)), 'utf8');
|
|
925
|
+
const containedStart = server.indexOf("toolServer.registerTool(\n 'contained_transform'");
|
|
926
|
+
const containedEnd = server.indexOf('toolServer.registerTool(', containedStart + 1);
|
|
927
|
+
const contained = server.slice(containedStart, containedEnd);
|
|
917
928
|
const launcher = await readFile(fileURLToPath(new URL('../native/deadbyte-exec.cpp', import.meta.url)), 'utf8');
|
|
918
929
|
const helper = await readFile(fileURLToPath(new URL('../native/contained-network-proof.h', import.meta.url)), 'utf8');
|
|
919
930
|
const authority = await readFile(fileURLToPath(new URL('../src/authority-smoke.mjs', import.meta.url)), 'utf8');
|
|
920
|
-
assert.
|
|
921
|
-
assert.
|
|
922
|
-
assert.doesNotMatch(
|
|
923
|
-
assert.doesNotMatch(
|
|
931
|
+
assert.ok(containedStart >= 0 && containedEnd > containedStart);
|
|
932
|
+
assert.match(contained, /inputSchema: z\.strictObject\(/);
|
|
933
|
+
assert.doesNotMatch(contained, /worker_id:\s*z\./);
|
|
934
|
+
assert.doesNotMatch(contained, /executable:\s*z\./);
|
|
935
|
+
assert.doesNotMatch(contained, /argv:\s*z\./);
|
|
924
936
|
assert.match(launcher, /argc != 7/);
|
|
925
937
|
assert.match(launcher, /invalid fixed argument contract/);
|
|
926
938
|
assert.match(helper, /HasExactContainedWorkerArguments/);
|
package/test/controller.test.mjs
CHANGED
|
@@ -82,11 +82,15 @@ test('controller keeps privileged policy optional and propagates it only to the
|
|
|
82
82
|
|
|
83
83
|
test('Windows host policy bootstrap pins executables and creates only explicit local roots', async () => {
|
|
84
84
|
const init = await readFile(fileURLToPath(new URL('../scripts/init-host-policy.ps1', import.meta.url)), 'utf8');
|
|
85
|
-
assert.match(init, /deadbyte\.host-policy\.
|
|
85
|
+
assert.match(init, /deadbyte\.host-policy\.v2/);
|
|
86
86
|
assert.match(init, /Get-FileHash/);
|
|
87
87
|
assert.match(init, /powershell_script/);
|
|
88
88
|
assert.match(init, /cmd_script/);
|
|
89
89
|
assert.match(init, /node_script/);
|
|
90
|
+
assert.match(init, /powershell_inline/);
|
|
91
|
+
assert.match(init, /cmd_inline/);
|
|
92
|
+
assert.match(init, /node_inline/);
|
|
93
|
+
assert.match(init, /native_exec/);
|
|
90
94
|
assert.match(init, /D:\\Projects/);
|
|
91
95
|
assert.match(init, /State\s+: DISARMED/);
|
|
92
96
|
});
|
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.
|
|
108
|
+
assert.equal(result.receipt.runtime.package_version, '0.15.1');
|
|
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,75 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createDurableJobSupervisorR44 } from '../src/durable-job-r44.mjs';
|
|
7
|
+
|
|
8
|
+
class FakeProvider{
|
|
9
|
+
constructor({external=true}={}){this.external=external;this.handles=new Map();this.starts=0;}
|
|
10
|
+
descriptor(){return {kind:this.external?'external-supervisor':'controller-local',instance_id:'sup-1'};}
|
|
11
|
+
async start(spec){
|
|
12
|
+
const handle={handle_id:`h-${++this.starts}`,pid:1000+this.starts,creation_id:`c-${this.starts}`,profile_id:spec.profile_id};
|
|
13
|
+
const running={...handle,state:'running'};this.handles.set(handle.handle_id,running);return {...running};
|
|
14
|
+
}
|
|
15
|
+
async reattach(handle){
|
|
16
|
+
const current=this.handles.get(handle.handle_id);return current?{...current}:null;
|
|
17
|
+
}
|
|
18
|
+
async status(handle){return this.handles.get(handle.handle_id)??null;}
|
|
19
|
+
async terminate(handle){const current=this.handles.get(handle.handle_id);if(current){current.state='terminated';return {...current};}return null;}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('R44 controller/session jobs never masquerade as persistent after owner restart',async()=>{
|
|
23
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-'));const provider=new FakeProvider();
|
|
24
|
+
try{
|
|
25
|
+
const one=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-1'});
|
|
26
|
+
const c=await one.start({lifetime:'controller',profileId:'build',rootId:'core',cwd:'.',args:[]});
|
|
27
|
+
const s=await one.start({lifetime:'session',profileId:'test',rootId:'core',cwd:'.',args:[]});
|
|
28
|
+
assert.equal(c.state,'running');assert.equal(s.state,'running');
|
|
29
|
+
const two=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
|
|
30
|
+
assert.equal((await two.status({jobId:c.job_id})).state,'interrupted');
|
|
31
|
+
assert.equal((await two.status({jobId:s.job_id})).state,'interrupted');
|
|
32
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R44 persistent job reattaches only through external supervisor with exact process identity',async()=>{
|
|
36
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-persist-'));const provider=new FakeProvider();
|
|
37
|
+
try{
|
|
38
|
+
const one=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-1'});
|
|
39
|
+
const job=await one.start({lifetime:'persistent',profileId:'watch',rootId:'core',cwd:'.',args:[]});
|
|
40
|
+
const two=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
|
|
41
|
+
const recovered=await two.status({jobId:job.job_id});
|
|
42
|
+
assert.equal(recovered.state,'running');assert.equal(recovered.recovered,true);
|
|
43
|
+
assert.equal(recovered.pid,job.pid);assert.equal(recovered.creation_id,job.creation_id);
|
|
44
|
+
|
|
45
|
+
const live=provider.handles.get(job.handle_id);live.creation_id='spoofed';
|
|
46
|
+
const three=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-3'});
|
|
47
|
+
assert.equal((await three.status({jobId:job.job_id})).state,'orphaned');
|
|
48
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('R44 persistent lifetime rejects controller-local provider and state corruption',async()=>{
|
|
52
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-local-'));
|
|
53
|
+
try{
|
|
54
|
+
const local=await createDurableJobSupervisorR44({stateDir:root,provider:new FakeProvider({external:false}),ownerInstanceId:'owner'});
|
|
55
|
+
await assert.rejects(local.start({lifetime:'persistent',profileId:'x',rootId:'core',cwd:'.',args:[]}),/external supervisor/);
|
|
56
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
57
|
+
|
|
58
|
+
const corrupt=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-corrupt-'));
|
|
59
|
+
try{
|
|
60
|
+
await writeFile(path.join(corrupt,'jobs.json'),'{"schema":','utf8');
|
|
61
|
+
await assert.rejects(createDurableJobSupervisorR44({stateDir:corrupt,provider:new FakeProvider(),ownerInstanceId:'owner'}),/state JSON invalid/);
|
|
62
|
+
}finally{await rm(corrupt,{recursive:true,force:true});}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('R44 terminate binds exact supervisor handle and becomes durable terminal state',async()=>{
|
|
66
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-job-term-'));const provider=new FakeProvider();
|
|
67
|
+
try{
|
|
68
|
+
const sup=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner'});
|
|
69
|
+
const job=await sup.start({lifetime:'persistent',profileId:'watch',rootId:'core',cwd:'.',args:[]});
|
|
70
|
+
const ended=await sup.terminate({jobId:job.job_id});
|
|
71
|
+
assert.equal(ended.state,'terminated');
|
|
72
|
+
const restarted=await createDurableJobSupervisorR44({stateDir:root,provider,ownerInstanceId:'owner-2'});
|
|
73
|
+
assert.equal((await restarted.status({jobId:job.job_id})).state,'terminated');
|
|
74
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
75
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdir, mkdtemp, rm, writeFile, readFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { createDurableJobSupervisorR44 } from '../src/durable-job-r44.mjs';
|
|
9
|
+
import { createExternalJobProviderR44 } from '../src/external-job-provider-r44.mjs';
|
|
10
|
+
|
|
11
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
12
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const repoRoot=path.resolve(here,'..');
|
|
14
|
+
const workerPath=path.join(repoRoot,'src','job-supervisor-host-r44.mjs');
|
|
15
|
+
|
|
16
|
+
test('R44 real external supervisor preserves persistent job identity across controller recreation',async()=>{
|
|
17
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-real-supervisor-'));
|
|
18
|
+
const supervisorState=path.join(root,'host'),jobsState=path.join(root,'jobs'),project=path.join(root,'project');
|
|
19
|
+
await mkdir(project,{recursive:true});
|
|
20
|
+
const script=path.join(project,'hold.mjs');
|
|
21
|
+
await writeFile(script,`process.stdout.write('READY\\n');setInterval(()=>{},1000);\n`,'utf8');
|
|
22
|
+
const nodeSha=sha(await readFile(process.execPath)), workerSha=sha(await readFile(workerPath));
|
|
23
|
+
const config={stateDir:supervisorState,nodeExecutable:process.execPath,nodeExecutableSha256:nodeSha,
|
|
24
|
+
workerPath,workerSha256:workerSha,roots:{core:project},profiles:{
|
|
25
|
+
hold:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[script],max_args:4,max_arg_bytes:1024}
|
|
26
|
+
}};
|
|
27
|
+
let providerA=null,providerB=null;
|
|
28
|
+
try{
|
|
29
|
+
providerA=await createExternalJobProviderR44(config);
|
|
30
|
+
const one=await createDurableJobSupervisorR44({stateDir:jobsState,provider:providerA,ownerInstanceId:'controller-a'});
|
|
31
|
+
const started=await one.start({lifetime:'persistent',profileId:'hold',rootId:'core',cwd:'.',args:[]});
|
|
32
|
+
assert.equal(started.state,'running');assert.ok(started.pid>0);assert.match(started.creation_id,/^[0-9a-f]{64}$/);
|
|
33
|
+
|
|
34
|
+
providerB=await createExternalJobProviderR44(config);
|
|
35
|
+
assert.equal(providerB.descriptor().instance_id,providerA.descriptor().instance_id,'must reconnect same external supervisor');
|
|
36
|
+
const two=await createDurableJobSupervisorR44({stateDir:jobsState,provider:providerB,ownerInstanceId:'controller-b'});
|
|
37
|
+
const recovered=await two.status({jobId:started.job_id});
|
|
38
|
+
assert.equal(recovered.state,'running');assert.equal(recovered.recovered,true);
|
|
39
|
+
assert.equal(recovered.handle_id,started.handle_id);assert.equal(recovered.pid,started.pid);assert.equal(recovered.creation_id,started.creation_id);
|
|
40
|
+
const ended=await two.terminate({jobId:started.job_id});assert.equal(ended.state,'terminated');
|
|
41
|
+
}finally{
|
|
42
|
+
if(providerB)await providerB.shutdown().catch(()=>{});
|
|
43
|
+
else if(providerA)await providerA.shutdown().catch(()=>{});
|
|
44
|
+
await rm(root,{recursive:true,force:true});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -10,8 +10,10 @@ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
|
10
10
|
|
|
11
11
|
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const repoRoot=path.resolve(here,'..');
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
const parent=JSON.parse(await readFile(path.join(repoRoot,'R44-PARENT.json'),'utf8'));
|
|
14
|
+
const parentRoot=parent.parent_release_root;
|
|
15
|
+
const serverPath=path.join(parentRoot,'src','mcp-server.mjs');
|
|
16
|
+
const desktopPolicyPath=path.join(parentRoot,'controller','deadbyte-desktop-policy.json');
|
|
15
17
|
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
16
18
|
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
19
|
|
|
@@ -70,7 +72,7 @@ test('R40 real max-runtime full stdio catalog is exactly 80 with four Desktop pr
|
|
|
70
72
|
DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
71
73
|
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
|
|
72
74
|
};
|
|
73
|
-
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:
|
|
75
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:parentRoot,env,stderr:'pipe'});
|
|
74
76
|
try{
|
|
75
77
|
await client.connect(transport);
|
|
76
78
|
const listed=await client.listTools();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
8
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
9
|
+
import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
10
|
+
|
|
11
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const repoRoot=path.resolve(here,'..');
|
|
13
|
+
const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
|
|
14
|
+
const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
|
|
15
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
16
|
+
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
|
+
|
|
18
|
+
test('R44 real max-runtime full stdio catalog is exactly 81 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-r44-full-catalog',version:'0.15.0'},{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 {R44_EXPECTED_TOOLS}=await import('../src/remote-mcp-r44-parity-client.mjs');
|
|
79
|
+
assert.equal(names.length,81,'expected exact full R44 catalog 81, got '+names.length+': '+names.join(','));
|
|
80
|
+
assert.deepEqual(names,R44_EXPECTED_TOOLS);
|
|
81
|
+
for(const name of ['desktop_status','desktop_observe','desktop_target','desktop_action',
|
|
82
|
+
'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
|
|
83
|
+
for(const tool of listed.tools) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
|
|
84
|
+
const catalog_sha256=sha(Buffer.from(JSON.stringify(names),'utf8'));
|
|
85
|
+
console.log(JSON.stringify({tool_count:names.length,catalog_sha256,desktop_tools:4,skill:true,authority:'DISARMED'}));
|
|
86
|
+
} finally {
|
|
87
|
+
await client.close().catch(()=>{});
|
|
88
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
89
|
+
await f.cleanup();
|
|
90
|
+
}
|
|
91
|
+
});
|