deadbyte-mcp 0.15.20 → 0.15.22
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 +34 -25
- package/PROVENANCE.json +10 -10
- package/README.txt +7 -7
- package/RELEASE.json +47 -16
- package/SBOM.cdx.json +33 -7
- package/TEST-MATRIX.json +85 -15
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/docs/R48-FULL-TOOL-CATALOG.json +124 -0
- package/native/deadbyte-hardware-observer.cpp +535 -12
- package/package.json +94 -93
- package/scripts/build-hardware-r45.ps1 +19 -0
- package/scripts/full-regression-r48-batch.mjs +27 -0
- package/scripts/promote-local-c-generation.ps1 +217 -0
- package/scripts/promote-local-d-generation.ps1 +217 -0
- package/scripts/test-r48-post-bump.ps1 +8 -0
- package/scripts/test-r48-windows-observation.ps1 +8 -0
- package/src/capability-ledger.mjs +7 -0
- package/src/capability-resolver.mjs +70 -14
- package/src/compact-gateway-mcp-tools.mjs +16 -2
- package/src/machine-fs.mjs +16 -3
- package/src/mcp-server.mjs +2 -1
- package/src/observation-mcp-tools.mjs +13 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +54 -2
- package/src/version.mjs +1 -1
- package/test/capability-resolver.test.mjs +68 -1
- package/test/compact-max-catalog-r45.test.mjs +25 -1
- package/test/full-max-catalog-r47.test.mjs +1 -1
- package/test/full-max-catalog-r48.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +28 -3
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/r48-release-contract.test.mjs +20 -14
- package/test/windows-observation-r48.test.mjs +63 -0
- package/test/windows-observation-surface-r48.test.mjs +70 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
6
|
import {
|
|
4
7
|
CAPABILITY_STATES,
|
|
5
8
|
deriveCapabilityRequirements,
|
|
@@ -22,7 +25,7 @@ test('deriveCapabilityRequirements maps dependencies accurately', () => {
|
|
|
22
25
|
mutating: false,
|
|
23
26
|
runtime_flag: 'DEADBYTE_CODING_RUNTIME'
|
|
24
27
|
});
|
|
25
|
-
assert.deepEqual(reqCodingRead, ['DEADBYTE_CODING_RUNTIME', '
|
|
28
|
+
assert.deepEqual(reqCodingRead, ['DEADBYTE_CODING_RUNTIME', 'coding_session.armed']);
|
|
26
29
|
|
|
27
30
|
const reqCodingWrite = deriveCapabilityRequirements({
|
|
28
31
|
id: 'coding.file_write',
|
|
@@ -131,3 +134,67 @@ test('resolveCapabilityStates resolves all 5 states correctly', async () => {
|
|
|
131
134
|
assert.equal(skillCap.diagnostic.code, 'SKILL_ENVELOPE_MISMATCH');
|
|
132
135
|
assert.ok(degradedRes.degraded_capabilities.some(d => d.id === 'skill.catalog'));
|
|
133
136
|
});
|
|
137
|
+
|
|
138
|
+
test('capability resolver reports actual disarmed authority across coding process autonomous desktop and host', async () => {
|
|
139
|
+
const ledger={entries:[
|
|
140
|
+
{id:'coding.search',compact_tool:'coding_inspect',full_tool:'coding_search',compact_op:'search',authority:'coding',mutating:false,production:true,runtime_flag:'DEADBYTE_CODING_RUNTIME'},
|
|
141
|
+
{id:'process.start',compact_tool:'process_session',full_tool:'process_session_start',compact_op:'start',authority:'process',mutating:true,production:true,runtime_flag:'DEADBYTE_PROCESS_RUNTIME'},
|
|
142
|
+
{id:'autonomous.goal_submit',compact_tool:'autonomous_goal_submit',full_tool:'autonomous_goal_submit',compact_op:'direct',authority:'autonomous',mutating:true,production:true,runtime_flag:'DEADBYTE_AUTONOMOUS_RUNTIME'},
|
|
143
|
+
{id:'desktop.action',compact_tool:'desktop_control',full_tool:'desktop_action',compact_op:'action',authority:'desktop',mutating:true,production:true,runtime_flag:'DEADBYTE_DESKTOP_RUNTIME'},
|
|
144
|
+
{id:'host.file_read',compact_tool:'host_file_read',full_tool:'host_file_read',compact_op:'direct',authority:'host',mutating:false,production:false,runtime_flag:'DEADBYTE_PRIVILEGED_POLICY'}
|
|
145
|
+
]};
|
|
146
|
+
const result=await resolveCapabilityStates({
|
|
147
|
+
ledger,
|
|
148
|
+
toolCatalog:['coding_inspect','process_session','autonomous_goal_submit','desktop_control','host_file_read'],
|
|
149
|
+
profile:'compact',
|
|
150
|
+
env:{},
|
|
151
|
+
codingRuntime:{sessionStatus:async()=>({armed:false,reason:'coding-disarmed'})},
|
|
152
|
+
processRuntime:{sessionStatus:async()=>({armed:false,reason:'process-disarmed'})},
|
|
153
|
+
autonomousRuntime:{sessionStatus:async()=>({armed:false,reason:'autonomous-disarmed'})},
|
|
154
|
+
desktopRuntime:{status:async()=>({armed:false,reason:'desktop-disarmed'})},
|
|
155
|
+
hostPolicy:{arm_file:path.join(os.tmpdir(),'deadbyte-capability-resolver-never-armed.json'),policy_sha256:'a'.repeat(64)}
|
|
156
|
+
});
|
|
157
|
+
const byId=id=>result.capabilities.find(item=>item.id===id);
|
|
158
|
+
assert.equal(byId('coding.search').state,CAPABILITY_STATES.DENIED);
|
|
159
|
+
assert.equal(byId('coding.search').diagnostic.code,'CODING_SESSION_DISARMED');
|
|
160
|
+
assert.equal(byId('process.start').state,CAPABILITY_STATES.DENIED);
|
|
161
|
+
assert.equal(byId('process.start').diagnostic.code,'PROCESS_SESSION_DISARMED');
|
|
162
|
+
assert.equal(byId('autonomous.goal_submit').state,CAPABILITY_STATES.DENIED);
|
|
163
|
+
assert.equal(byId('autonomous.goal_submit').diagnostic.code,'AUTONOMOUS_SESSION_DISARMED');
|
|
164
|
+
assert.equal(byId('desktop.action').state,CAPABILITY_STATES.DENIED);
|
|
165
|
+
assert.equal(byId('desktop.action').diagnostic.code,'DESKTOP_SESSION_DISARMED');
|
|
166
|
+
assert.equal(byId('host.file_read').state,CAPABILITY_STATES.DENIED);
|
|
167
|
+
assert.equal(byId('host.file_read').diagnostic.code,'HOST_POLICY_DISARMED');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
test('capability resolver reports armed Host capability when the standing grant is valid', async () => {
|
|
172
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-capability-host-'));
|
|
173
|
+
try{
|
|
174
|
+
const armFile=path.join(root,'host-grant.json');
|
|
175
|
+
const policySha='b'.repeat(64);
|
|
176
|
+
await writeFile(armFile,JSON.stringify({
|
|
177
|
+
schema:'deadbyte.host-grant.v2',
|
|
178
|
+
policy_sha256:policySha,
|
|
179
|
+
instance_nonce:'c'.repeat(64),
|
|
180
|
+
armed_at_utc:new Date().toISOString()
|
|
181
|
+
}));
|
|
182
|
+
const result=await resolveCapabilityStates({
|
|
183
|
+
ledger:{entries:[{
|
|
184
|
+
id:'host.file_read',compact_tool:'host_file_read',full_tool:'host_file_read',compact_op:'direct',
|
|
185
|
+
authority:'host',mutating:false,production:false,runtime_flag:'DEADBYTE_PRIVILEGED_POLICY'
|
|
186
|
+
}]},
|
|
187
|
+
toolCatalog:['host_file_read'],
|
|
188
|
+
profile:'compact',
|
|
189
|
+
env:{},
|
|
190
|
+
hostPolicy:{arm_file:armFile,policy_sha256:policySha}
|
|
191
|
+
});
|
|
192
|
+
const hostRead=result.capabilities[0];
|
|
193
|
+
assert.equal(hostRead.state,CAPABILITY_STATES.AVAILABLE);
|
|
194
|
+
assert.equal(hostRead.ready,true);
|
|
195
|
+
assert.equal(hostRead.diagnostic,null);
|
|
196
|
+
assert.deepEqual(hostRead.requires,['DEADBYTE_PRIVILEGED_POLICY','host_policy.armed']);
|
|
197
|
+
}finally{
|
|
198
|
+
await rm(root,{recursive:true,force:true});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
@@ -90,7 +90,31 @@ test('R45 real max-runtime compact stdio catalog is exactly 43 with verified ski
|
|
|
90
90
|
assert.ok(hardware);assert.equal(hardware.authority,'none');assert.equal(hardware.compact_tool,'machine_observe');assert.equal(hardware.compact_op,'hardware_query');
|
|
91
91
|
const skill=m.capabilities.find(item=>item.id==='skill.catalog');
|
|
92
92
|
assert.ok(skill);assert.equal(skill.authority,'none');assert.equal(skill.mutating,false);
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
const byId=id=>m.capabilities.find(item=>item.id===id);
|
|
95
|
+
for(const [id,reasonCode] of [
|
|
96
|
+
['coding.search','CODING_SESSION_DISARMED'],
|
|
97
|
+
['process.start','PROCESS_SESSION_DISARMED'],
|
|
98
|
+
['autonomous.goal_submit','AUTONOMOUS_SESSION_DISARMED'],
|
|
99
|
+
['desktop.action','DESKTOP_SESSION_DISARMED'],
|
|
100
|
+
['host.file_read','HOST_POLICY_DISARMED']
|
|
101
|
+
]){
|
|
102
|
+
const item=byId(id);
|
|
103
|
+
assert.ok(item,'missing capability '+id);
|
|
104
|
+
assert.equal(item.state,'DENIED',id+' must reflect disarmed authority');
|
|
105
|
+
assert.equal(item.ready,false,id+' must not be ready while disarmed');
|
|
106
|
+
assert.equal(item.diagnostic?.code,reasonCode,id+' diagnostic mismatch');
|
|
107
|
+
assert.ok(m.unavailable_capabilities.some(entry=>entry.id===id&&entry.state==='DENIED'&&entry.reason_code===reasonCode),
|
|
108
|
+
id+' must be listed as unavailable');
|
|
109
|
+
}
|
|
110
|
+
for(const id of ['coding.task_events','coding.task_status','coding.task_verify','process.list','desktop.observe']){
|
|
111
|
+
const item=byId(id);
|
|
112
|
+
assert.ok(item,'missing read-only capability '+id);
|
|
113
|
+
assert.equal(item.state,'AVAILABLE',id+' should remain available without mutation authority');
|
|
114
|
+
assert.equal(item.ready,true,id+' should remain ready without mutation authority');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
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',authority_truth:true}));
|
|
94
118
|
} finally {
|
|
95
119
|
await client.close().catch(()=>{});
|
|
96
120
|
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
@@ -15,7 +15,7 @@ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy
|
|
|
15
15
|
const catalogPath=path.join(repoRoot,'docs','R47-FULL-TOOL-CATALOG.json');
|
|
16
16
|
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
17
|
|
|
18
|
-
test('R47 runtime full stdio catalog exactly matches canonical registration surface',async()=>{
|
|
18
|
+
test('R47 runtime full stdio catalog exactly matches canonical registration surface',{skip:'historical R47 catalog; R48 supersedes it'},async()=>{
|
|
19
19
|
const canonical=JSON.parse(await readFile(catalogPath,'utf8'));
|
|
20
20
|
assert.equal(canonical.schema,'deadbyte.r47-full-catalog.v1');
|
|
21
21
|
assert.equal(canonical.train,'R47');
|
|
@@ -0,0 +1,78 @@
|
|
|
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 catalogPath=path.join(repoRoot,'docs','R48-FULL-TOOL-CATALOG.json');
|
|
16
|
+
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
|
+
|
|
18
|
+
test('R48 runtime full stdio catalog exactly matches canonical registration surface',async()=>{
|
|
19
|
+
const canonical=JSON.parse(await readFile(catalogPath,'utf8'));
|
|
20
|
+
assert.equal(canonical.schema,'deadbyte.r48-full-catalog.v1');
|
|
21
|
+
assert.equal(canonical.train,'R48');
|
|
22
|
+
assert.ok(Array.isArray(canonical.tools));
|
|
23
|
+
assert.equal(canonical.tool_count,canonical.tools.length);
|
|
24
|
+
assert.equal(new Set(canonical.tools).size,canonical.tools.length);
|
|
25
|
+
assert.deepEqual([...canonical.tools].sort(),canonical.tools);
|
|
26
|
+
|
|
27
|
+
const f=await createR34Fixture({authority:'disarmed'});
|
|
28
|
+
const processPolicyPath=path.join(f.root,'process-policy.json');
|
|
29
|
+
const processGrant=path.join(f.root,'process-grant.json');
|
|
30
|
+
const processState=path.join(f.root,'process-state');
|
|
31
|
+
const hostPolicyPath=path.join(f.root,'host-policy.json');
|
|
32
|
+
const hostArm=path.join(f.root,'host-arm.json');
|
|
33
|
+
const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
|
|
34
|
+
const desktopGrant=desktopRaw.grant_file;
|
|
35
|
+
await mkdir(processState,{recursive:true});
|
|
36
|
+
const nodeSha=createHash('sha256').update(await readFile(process.execPath)).digest('hex');
|
|
37
|
+
await writeFile(processPolicyPath,JSON.stringify({
|
|
38
|
+
schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
|
|
39
|
+
native_host:{path:process.execPath,sha256:nodeSha},
|
|
40
|
+
roots:{core:{path:f.project,deny_paths:[]}},
|
|
41
|
+
profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],input_actions:{ping:'PING\\r\\n'}}}
|
|
42
|
+
},null,2));
|
|
43
|
+
await writeFile(hostPolicyPath,JSON.stringify({
|
|
44
|
+
schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
|
|
45
|
+
roots:{core:{path:f.project,read:true,write:true,exec:true}},
|
|
46
|
+
commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
|
|
47
|
+
},null,2));
|
|
48
|
+
const codingLease=f.codingPolicy.lease_file;
|
|
49
|
+
const autonomousLease=f.policy.lease_file;
|
|
50
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
|
|
51
|
+
|
|
52
|
+
const client=new Client({name:'deadbyte-r48-full-catalog',version:'0.15.22'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
53
|
+
const env={
|
|
54
|
+
...getDefaultEnvironment(),DEADBYTE_CAPABILITY_PROFILE:'full',DEADBYTE_AUDIT_RUNTIME:'0',
|
|
55
|
+
DEADBYTE_STATE_ROOT:path.join(f.root,'state'),DEADBYTE_WORKSPACE_ROOT:f.root,
|
|
56
|
+
DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
|
|
57
|
+
DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'1',
|
|
58
|
+
DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:processPolicyPath,DEADBYTE_CODING_RUNTIME:'1',
|
|
59
|
+
DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
60
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
|
|
61
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
62
|
+
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
|
|
63
|
+
};
|
|
64
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
|
|
65
|
+
try{
|
|
66
|
+
await client.connect(transport);
|
|
67
|
+
const listed=await client.listTools();
|
|
68
|
+
const names=listed.tools.map(t=>t.name).sort();
|
|
69
|
+
assert.deepEqual(names,canonical.tools);
|
|
70
|
+
assert.equal(new Set(names).size,names.length,'runtime duplicate tool names');
|
|
71
|
+
for(const tool of listed.tools) if(tool.outputSchema!==undefined) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
|
|
72
|
+
for(const name of ['agent_task_submit','autonomous_goal_submit','coding_task_submit','process_session_start','machine_fs_write','machine_search_start','desktop_action','host_exec','native_diff_apply','skill_catalog']) assert.ok(names.includes(name),'missing R48 surface '+name);
|
|
73
|
+
} finally {
|
|
74
|
+
await client.close().catch(()=>{});
|
|
75
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
76
|
+
await f.cleanup();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { lstat, 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';
|
|
@@ -10,7 +10,7 @@ import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machi
|
|
|
10
10
|
|
|
11
11
|
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
12
12
|
|
|
13
|
-
async function fixture({ readWorkerPath = undefined } = {}) {
|
|
13
|
+
async function fixture({ readWorkerPath = undefined, listEntryLstat = undefined } = {}) {
|
|
14
14
|
const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-'));
|
|
15
15
|
const project = path.join(root, 'project');
|
|
16
16
|
await mkdir(project);
|
|
@@ -30,7 +30,7 @@ async function fixture({ readWorkerPath = undefined } = {}) {
|
|
|
30
30
|
privateKeyPath: path.join(root, 'private.pem'), publicKeyPath: path.join(root, 'public.pem')
|
|
31
31
|
});
|
|
32
32
|
const runtime = createMachineFsRuntime({
|
|
33
|
-
policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath, readWorkerPath
|
|
33
|
+
policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath, readWorkerPath, listEntryLstat
|
|
34
34
|
});
|
|
35
35
|
return { root, project, policy, keys, runtime };
|
|
36
36
|
}
|
|
@@ -87,6 +87,31 @@ test('R31 machine fs supports exact mkdir/write/stat/list/range-read/edit/move/d
|
|
|
87
87
|
assert.equal(second.next_cursor, null);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
test('R48 machine fs listing isolates one unreadable child instead of failing the page', async () => {
|
|
91
|
+
let projectPath = null;
|
|
92
|
+
const injectedLstat = async target => {
|
|
93
|
+
if (projectPath && path.basename(target) === 'bad.txt') {
|
|
94
|
+
const error = new Error('simulated Windows lstat EINVAL');
|
|
95
|
+
error.code = 'EINVAL';
|
|
96
|
+
error.syscall = 'lstat';
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return lstat(target);
|
|
100
|
+
};
|
|
101
|
+
const f = await fixture({ listEntryLstat: injectedLstat });
|
|
102
|
+
projectPath = f.project;
|
|
103
|
+
await writeFile(path.join(f.project, 'bad.txt'), 'bad');
|
|
104
|
+
await writeFile(path.join(f.project, 'good.txt'), 'good');
|
|
105
|
+
const listed = await f.runtime.list({ rootId:'project', relativePath:'.', limit:10, hashFiles:true });
|
|
106
|
+
assert.deepEqual(listed.entries.map(item => item.path), ['bad.txt','good.txt']);
|
|
107
|
+
assert.deepEqual(listed.entries[0], {
|
|
108
|
+
path:'bad.txt', kind:'unreadable', bytes:0, mtime_utc:null, sha256:null,
|
|
109
|
+
error:{ code:'EINVAL', operation:'lstat' }
|
|
110
|
+
});
|
|
111
|
+
assert.equal(listed.entries[1].kind, 'file');
|
|
112
|
+
assert.equal(listed.entries[1].sha256, sha(Buffer.from('good')));
|
|
113
|
+
});
|
|
114
|
+
|
|
90
115
|
test('R31 machine fs fails closed on traversal, denied paths and stale source identity', async () => {
|
|
91
116
|
const f = await fixture();
|
|
92
117
|
await mkdir(path.join(f.project, 'blocked'));
|
|
@@ -16,14 +16,15 @@ function runtime(){
|
|
|
16
16
|
searchCancel:async()=>({status:'ok',search_id:hex('a'),state:'cancelled',cancel_requested:true}),
|
|
17
17
|
processList:async()=>({status:'ok',snapshot_id:hex('e'),observed_at_utc:new Date(0).toISOString(),processes:[],next_cursor:null,request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
|
|
18
18
|
systemStatus:async()=>({status:'ok',observed_at_utc:new Date(0).toISOString(),hostname:'x',platform:'win32',architecture:'x64',uptime_seconds:1,total_memory_bytes:2,free_memory_bytes:1,cpu_count:1,load_average:[0,0,0],request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
|
|
19
|
-
hardwareQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{vendor:'x'},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')})
|
|
19
|
+
hardwareQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{vendor:'x'},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
|
|
20
|
+
windowsQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{query},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')})
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
test('
|
|
24
|
+
test('R48 registers exactly eight bounded observation tools including typed native Windows observation',()=>{
|
|
24
25
|
const server=new FakeServer(); registerObservationTools(server,{runtime:runtime()});
|
|
25
26
|
assert.deepEqual([...server.tools.keys()].sort(),[
|
|
26
|
-
'machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'
|
|
27
|
+
'machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status','machine_windows_query'
|
|
27
28
|
]);
|
|
28
29
|
for(const [name,tool] of server.tools){ assert.ok(tool.spec.inputSchema,`${name} input`); assert.ok(tool.spec.outputSchema,`${name} output`); }
|
|
29
30
|
});
|
|
@@ -6,29 +6,35 @@ import { DEADBYTE_VERSION } from '../src/version.mjs';
|
|
|
6
6
|
const root=new URL('../',import.meta.url);
|
|
7
7
|
const read=rel=>readFile(new URL(rel,root),'utf8');
|
|
8
8
|
|
|
9
|
-
test('R48 successor
|
|
9
|
+
test('R48 0.15.22 successor binds active 0.15.21 parent and native Windows observation release evidence',async()=>{
|
|
10
10
|
const [pkg,lock,release,provenance]=await Promise.all([
|
|
11
11
|
read('package.json').then(JSON.parse),
|
|
12
12
|
read('package-lock.json').then(JSON.parse),
|
|
13
13
|
read('RELEASE.json').then(JSON.parse),
|
|
14
14
|
read('PROVENANCE.json').then(JSON.parse)
|
|
15
15
|
]);
|
|
16
|
-
assert.equal(pkg.version,'0.15.
|
|
17
|
-
assert.equal(lock.version,'0.15.
|
|
18
|
-
assert.equal(lock.packages[''].version,'0.15.
|
|
19
|
-
assert.equal(DEADBYTE_VERSION,'0.15.
|
|
20
|
-
assert.equal(release.version,'0.15.
|
|
21
|
-
assert.equal(provenance.version,'0.15.
|
|
16
|
+
assert.equal(pkg.version,'0.15.22');
|
|
17
|
+
assert.equal(lock.version,'0.15.22');
|
|
18
|
+
assert.equal(lock.packages[''].version,'0.15.22');
|
|
19
|
+
assert.equal(DEADBYTE_VERSION,'0.15.22');
|
|
20
|
+
assert.equal(release.version,'0.15.22');
|
|
21
|
+
assert.equal(provenance.version,'0.15.22');
|
|
22
22
|
assert.equal(release.train,'R48');
|
|
23
|
-
assert.equal(release.label,'
|
|
24
|
-
assert.equal(release.parent.release_id,'v0.15.
|
|
25
|
-
assert.equal(release.parent.manifest_sha256,'
|
|
26
|
-
assert.equal(release.parent.generation_manifest_sha256,'
|
|
27
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
28
|
-
assert.equal(release.source_predecessor.tarball_sha256,'
|
|
29
|
-
assert.equal(release.source_predecessor.manifest_file_sha256,'
|
|
23
|
+
assert.equal(release.label,'NATIVE WINDOWS OBSERVATION + MACHINE FS RESILIENCE');
|
|
24
|
+
assert.equal(release.parent.release_id,'v0.15.21-7c2735e0b1c6cc8d');
|
|
25
|
+
assert.equal(release.parent.manifest_sha256,'7c2735e0b1c6cc8dac6581c47b6b557d8d0157a324b67efd4df058e67cdb289f');
|
|
26
|
+
assert.equal(release.parent.generation_manifest_sha256,'b376e7fc450f44edddb0d0e1d596014c463f603e4917c56a68906b7f66f99dc7');
|
|
27
|
+
assert.equal(release.source_predecessor.version,'0.15.21');
|
|
28
|
+
assert.equal(release.source_predecessor.tarball_sha256,'3439f5f4435632e4cde385a6937e3e3d2ffcfb777295dd56aafbf3b036be7493');
|
|
29
|
+
assert.equal(release.source_predecessor.manifest_file_sha256,'7c2735e0b1c6cc8dac6581c47b6b557d8d0157a324b67efd4df058e67cdb289f');
|
|
30
30
|
assert.equal(release.source_predecessor.reference_server_sha256,'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305');
|
|
31
31
|
assert.equal(release.source_predecessor.reference_schemas_sha256,'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140');
|
|
32
|
+
assert.equal(release.capabilities.full_tool_count,116);
|
|
33
|
+
assert.equal(release.capabilities.full_tool_catalog.path,'docs/R48-FULL-TOOL-CATALOG.json');
|
|
34
|
+
assert.deepEqual(release.capabilities.windows_queries,['registry','services','drivers','sockets','volumes','usb','wsl']);
|
|
35
|
+
assert.equal(release.capabilities.machine_fs_list_entry_fault_isolation,true);
|
|
36
|
+
assert.equal(release.capabilities.capability_manifest_authority_truth,true);
|
|
37
|
+
assert.equal(release.capabilities.capability_manifest_host_grant_truth,true);
|
|
32
38
|
assert.equal(provenance.parent_release_id,release.parent.release_id);
|
|
33
39
|
assert.equal(provenance.parent_manifest_sha256,release.parent.manifest_sha256);
|
|
34
40
|
assert.equal(provenance.parent_generation_manifest_sha256,release.parent.generation_manifest_sha256);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, 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 { createObservationRuntime, verifyObservationReceiptFile } from '../src/observation-runtime.mjs';
|
|
9
|
+
|
|
10
|
+
async function fixture(){
|
|
11
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-windows-query-r48-'));
|
|
12
|
+
const project=path.join(root,'project');await mkdir(project);
|
|
13
|
+
const policyPath=path.join(root,'coding-policy.json');
|
|
14
|
+
await writeFile(policyPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
15
|
+
lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'evidence'),
|
|
16
|
+
roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},commands:{},
|
|
17
|
+
max_read_bytes:65536,max_write_bytes:65536}));
|
|
18
|
+
const policy=await loadCodingPolicy(policyPath);
|
|
19
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
|
|
20
|
+
const calls=[];
|
|
21
|
+
const windowsSnapshotProvider=async args=>{
|
|
22
|
+
calls.push(args);
|
|
23
|
+
return {schema:'deadbyte.windows-query.v1',query:args.query,data:{query:args.query,fixture:true},observer_sha256:'e'.repeat(64)};
|
|
24
|
+
};
|
|
25
|
+
const runtime=createObservationRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath,
|
|
26
|
+
stateRoot:root,processSnapshotProvider:async()=>[],systemSnapshotProvider:async()=>({hostname:'x',platform:'win32',architecture:'x64',
|
|
27
|
+
uptime_seconds:1,total_memory_bytes:1,free_memory_bytes:1,cpu_count:1,load_average:[0,0,0]}),
|
|
28
|
+
ownedRuntimePidProvider:async()=>new Set(),hardwareSnapshotProvider:async query=>({schema:'deadbyte.hardware-query.v1',query,data:{fixture:true}}),
|
|
29
|
+
windowsSnapshotProvider});
|
|
30
|
+
return {root,policy,keys,runtime,calls};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('R48 typed Windows queries are read-only signed native observations',async()=>{
|
|
34
|
+
const f=await fixture();
|
|
35
|
+
assert.equal(typeof f.runtime.windowsQuery,'function','windowsQuery runtime method missing');
|
|
36
|
+
const requests=[
|
|
37
|
+
{query:'registry',hive:'HKLM',key:'SYSTEM\\CurrentControlSet\\Control',value:null},
|
|
38
|
+
{query:'services'},{query:'drivers'},{query:'sockets'},{query:'volumes'},{query:'usb'},{query:'wsl'}
|
|
39
|
+
];
|
|
40
|
+
for(const request of requests){
|
|
41
|
+
const out=await f.runtime.windowsQuery(request);
|
|
42
|
+
assert.equal(out.status,'ok');
|
|
43
|
+
assert.equal(out.query,request.query);
|
|
44
|
+
assert.equal(out.data.query,request.query);
|
|
45
|
+
assert.match(out.observer_sha256,/^[0-9a-f]{64}$/);
|
|
46
|
+
assert.match(out.request_sha256,/^[0-9a-f]{64}$/);
|
|
47
|
+
assert.match(out.result_head_sha256,/^[0-9a-f]{64}$/);
|
|
48
|
+
assert.match(out.receipt_id,/^[0-9a-f]{24}$/);
|
|
49
|
+
const verified=await verifyObservationReceiptFile({policy:f.policy,verifyKeyPath:f.keys.publicKeyPath,receiptId:out.receipt_id});
|
|
50
|
+
assert.equal(verified.ok,true);
|
|
51
|
+
assert.equal(verified.kind,'machine_windows_query');
|
|
52
|
+
}
|
|
53
|
+
assert.deepEqual(f.calls,requests);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('R48 Windows query validation rejects arbitrary commands and malformed registry selectors before dispatch',async()=>{
|
|
57
|
+
const f=await fixture();
|
|
58
|
+
assert.equal(typeof f.runtime.windowsQuery,'function','windowsQuery runtime method missing');
|
|
59
|
+
await assert.rejects(()=>f.runtime.windowsQuery({query:'powershell',command:'Get-Service'}),/windows query/i);
|
|
60
|
+
await assert.rejects(()=>f.runtime.windowsQuery({query:'registry',hive:'HKCR',key:'x'}),/registry hive/i);
|
|
61
|
+
await assert.rejects(()=>f.runtime.windowsQuery({query:'registry',hive:'HKLM',key:''}),/registry key/i);
|
|
62
|
+
assert.deepEqual(f.calls,[]);
|
|
63
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { machineObserveInputSchema, registerCompactGatewayTools } from '../src/compact-gateway-mcp-tools.mjs';
|
|
4
|
+
import { registerObservationTools } from '../src/observation-mcp-tools.mjs';
|
|
5
|
+
import { R36_CAPABILITY_LEDGER } from '../src/capability-ledger.mjs';
|
|
6
|
+
|
|
7
|
+
const fakeServer=()=>{
|
|
8
|
+
const tools=new Map();
|
|
9
|
+
return {tools,registerTool(name,spec,handler){if(tools.has(name))throw new Error('duplicate '+name);tools.set(name,{spec,handler});}};
|
|
10
|
+
};
|
|
11
|
+
const ledger={schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)};
|
|
12
|
+
const evidence=query=>({status:'ok',observed_at_utc:new Date(0).toISOString(),query,data:{query},observer_sha256:'e'.repeat(64),
|
|
13
|
+
request_sha256:'b'.repeat(64),result_head_sha256:'c'.repeat(64),receipt_id:'a'.repeat(24),receipt_sha256:'d'.repeat(64)});
|
|
14
|
+
|
|
15
|
+
test('R48 compact machine_observe exposes seven strict Windows native operations without generic command dispatch',()=>{
|
|
16
|
+
const valid=[
|
|
17
|
+
{op:'registry_query',hive:'HKLM',key:'SYSTEM\\CurrentControlSet\\Control',value:null},
|
|
18
|
+
{op:'service_list'},{op:'driver_list'},{op:'socket_list'},{op:'volume_list'},{op:'usb_list'},{op:'wsl_list'}
|
|
19
|
+
];
|
|
20
|
+
for(const input of valid) assert.equal(machineObserveInputSchema.safeParse(input).success,true,JSON.stringify(input));
|
|
21
|
+
assert.equal(machineObserveInputSchema.safeParse({op:'registry_query',hive:'HKCR',key:'x',value:null}).success,false);
|
|
22
|
+
assert.equal(machineObserveInputSchema.safeParse({op:'service_list',command:'sc.exe query'}).success,false);
|
|
23
|
+
assert.equal(machineObserveInputSchema.safeParse({op:'socket_list',powershell:'Get-NetTCPConnection'}).success,false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('R48 compact machine_observe routes all Windows operations to one typed runtime method',async()=>{
|
|
27
|
+
const calls=[];
|
|
28
|
+
const observationRuntime={windowsQuery:async args=>{calls.push(args);return evidence(args.query);}};
|
|
29
|
+
const server=fakeServer();
|
|
30
|
+
registerCompactGatewayTools(server,{profile:'compact',ledger,codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,
|
|
31
|
+
observationRuntime,machineRuntime:null,desktopRuntime:null,skillRuntime:null,autonomousRuntime:null,hostPolicy:null,
|
|
32
|
+
catalogProvider:()=>[...server.tools.keys()]});
|
|
33
|
+
const tool=server.tools.get('machine_observe');
|
|
34
|
+
const inputs=[
|
|
35
|
+
{op:'registry_query',hive:'HKCU',key:'Software\\Microsoft',value:'Test'},
|
|
36
|
+
{op:'service_list'},{op:'driver_list'},{op:'socket_list'},{op:'volume_list'},{op:'usb_list'},{op:'wsl_list'}
|
|
37
|
+
];
|
|
38
|
+
for(const input of inputs){
|
|
39
|
+
const out=await tool.handler(input);
|
|
40
|
+
assert.equal(out.isError,false);
|
|
41
|
+
}
|
|
42
|
+
assert.deepEqual(calls,[
|
|
43
|
+
{query:'registry',hive:'HKCU',key:'Software\\Microsoft',value:'Test'},
|
|
44
|
+
{query:'services'},{query:'drivers'},{query:'sockets'},{query:'volumes'},{query:'usb'},{query:'wsl'}
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('R48 full observation surface exposes one typed machine_windows_query tool',()=>{
|
|
49
|
+
const server=fakeServer();
|
|
50
|
+
registerObservationTools(server,{runtime:{windowsQuery:async args=>evidence(args.query),
|
|
51
|
+
searchStart:async()=>{},searchPage:async()=>{},searchStatus:async()=>{},searchCancel:async()=>{},processList:async()=>{},systemStatus:async()=>{},hardwareQuery:async()=>{}}});
|
|
52
|
+
assert.equal(server.tools.has('machine_windows_query'),true);
|
|
53
|
+
const schema=server.tools.get('machine_windows_query').spec.inputSchema;
|
|
54
|
+
assert.equal(schema.safeParse({query:'registry',hive:'HKLM',key:'SOFTWARE',value:null}).success,true);
|
|
55
|
+
assert.equal(schema.safeParse({query:'services'}).success,true);
|
|
56
|
+
assert.equal(schema.safeParse({query:'services',command:'sc query'}).success,false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('R48 capability ledger declares every Windows observation through compact machine_observe',()=>{
|
|
60
|
+
const expected=['registry_query','service_list','driver_list','socket_list','volume_list','usb_list','wsl_list'];
|
|
61
|
+
for(const op of expected){
|
|
62
|
+
const cap=R36_CAPABILITY_LEDGER.entries.find(x=>x.id===`observation.${op}`);
|
|
63
|
+
assert.ok(cap,`missing observation.${op}`);
|
|
64
|
+
assert.equal(cap.full_tool,'machine_windows_query');
|
|
65
|
+
assert.equal(cap.compact_tool,'machine_observe');
|
|
66
|
+
assert.equal(cap.compact_op,op);
|
|
67
|
+
assert.equal(cap.authority,'none');
|
|
68
|
+
assert.equal(cap.mutating,false);
|
|
69
|
+
}
|
|
70
|
+
});
|