deadbyte-mcp 0.15.21 β 0.15.23
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 +53 -28
- package/PROVENANCE.json +10 -10
- package/README.txt +18 -7
- package/RELEASE.json +51 -16
- package/SBOM.cdx.json +33 -7
- package/TEST-MATRIX.json +95 -18
- 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 +95 -93
- package/scripts/activate-skill-creator-1.1.1.mjs +46 -0
- package/scripts/build-advanced-pcb-skill.mjs +208 -0
- package/scripts/build-hardware-r45.ps1 +19 -0
- package/scripts/diagnose-skill-tree.mjs +8 -0
- package/scripts/full-regression-r48-batch.mjs +27 -0
- package/scripts/npm-release-r48.mjs +252 -0
- package/scripts/pcb-skill-src-SKILL.md.txt +67 -0
- package/scripts/pcb-skill-src-bringup-evidence.md.txt +22 -0
- package/scripts/pcb-skill-src-constraint-model.md.txt +17 -0
- package/scripts/pcb-skill-src-emc-mechanical-dfm.md.txt +17 -0
- package/scripts/pcb-skill-src-high-speed-layout.md.txt +27 -0
- package/scripts/pcb-skill-src-kicad-native.md.txt +30 -0
- package/scripts/pcb-skill-src-kicad_pcb_probe.py.txt +159 -0
- package/scripts/pcb-skill-src-pcb_constraint_gate.py.txt +76 -0
- package/scripts/pcb-skill-src-power-integrity.md.txt +15 -0
- package/scripts/prepare-skill-creator-1.1.1.mjs +113 -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/scripts/test-skill-creator-semver-matrix.mjs +30 -0
- package/scripts/test-skill-creator-semver.mjs +22 -0
- package/src/capability-ledger.mjs +7 -0
- package/src/capability-resolver.mjs +7 -0
- package/src/compact-gateway-mcp-tools.mjs +16 -2
- package/src/deadbyte-cli.mjs +70 -15
- package/src/machine-fs.mjs +16 -3
- 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/remote-live-log.mjs +189 -35
- package/src/tool-audit.mjs +1 -1
- package/src/version.mjs +1 -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 +24 -14
- package/test/remote-live-log-r37.test.mjs +106 -7
- package/test/remote-session-cli.test.mjs +40 -0
- package/test/tool-audit.test.mjs +17 -0
- package/test/windows-observation-r48.test.mjs +63 -0
- package/test/windows-observation-surface-r48.test.mjs +70 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import
|
|
3
|
+
import * as live from '../src/remote-live-log.mjs';
|
|
4
|
+
const {createLiveTimelineTracker,formatRemoteLifecycle,formatRuntimeDiagnosticLine,summarizeLiveToolArgs,truncateUtf8}=live;
|
|
4
5
|
|
|
5
6
|
test('R40 human live timeline suppresses noisy read request and emits one categorized terminal line',()=>{
|
|
6
7
|
let now=1000;
|
|
@@ -43,7 +44,7 @@ test('R40 heartbeat burst folds repeated status calls instead of printing every
|
|
|
43
44
|
else assert.match(line,/^π‘ .*#39 machine_ping Γ5 avg 3ms β$/);
|
|
44
45
|
now+=10;
|
|
45
46
|
}
|
|
46
|
-
assert.match(tracker.summary(),/5 calls \| 0 errors .*folded 4 heartbeat lines.*machine_pingΓ5/);
|
|
47
|
+
assert.match(tracker.summary(),/5 calls \| 5 ok \| 0 errors \| 0 unknown \| 0 mutations .*folded 4 heartbeat lines.*machine_pingΓ5/);
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
test('R40 transport IN/OUT echo is hidden by default and available only in verbose diagnostics',()=>{
|
|
@@ -64,14 +65,19 @@ test('R40 interrupted spans remain correlated and session summary is available',
|
|
|
64
65
|
args:{root_id:'core',path:'x.txt',content:'x'}},{sequence:50});
|
|
65
66
|
const lines=tracker.reconcileInterrupted('recovery');
|
|
66
67
|
assert.deepEqual(lines,['β [pending-] OUTCOME_UNKNOWN coding_file_write β recovery ended before terminal result; inspect state before retry']);
|
|
67
|
-
assert.match(tracker.summary(),/^π \[SUMMARY\] 0 calls \| 0 errors/);
|
|
68
|
+
assert.match(tracker.summary(),/^π \[SUMMARY\] 0 calls \| 0 ok \| 0 errors \| 0 unknown \| 0 mutations/);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
test('R40 CLI exposes explicit verbose mode while keeping default live mode compact',async()=>{
|
|
71
72
|
const {readFile}=await import('node:fs/promises');
|
|
72
73
|
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
73
|
-
assert.match(src,/
|
|
74
|
-
assert.match(src,/
|
|
74
|
+
assert.match(src,/readLiveRootPaths\(ctx\.root\)/);
|
|
75
|
+
assert.match(src,/createLiveTimelineTracker\(\{verbose,displayMode,rootPaths,trace\}\)/);
|
|
76
|
+
assert.match(src,/createLiveTimelineFilter\(filterExpression\)/);
|
|
77
|
+
assert.match(src,/createLiveTimelineWriter\(\{stream:process\.stdout,displayMode,rewrite:!verbose\}\)/);
|
|
78
|
+
assert.match(src,/arg==='--verbose'\|\|arg==='-v'/);
|
|
79
|
+
assert.match(src,/arg==='--trace'/);
|
|
80
|
+
assert.match(src,/arg==='--filter'/);
|
|
75
81
|
assert.match(src,/Transport IN\/OUT hidden/);
|
|
76
82
|
assert.doesNotMatch(src,/\[cursor \$\{recorded\.cursor\}\]/);
|
|
77
83
|
});
|
|
@@ -88,13 +94,13 @@ test('R40 ASCII display fallback contains no non-ASCII bytes and preserves termi
|
|
|
88
94
|
assert.equal(formatRemoteLifecycle('device_start',{displayMode:'ascii'}),'[DEVICE] Starting DEADBYTE MCP...');
|
|
89
95
|
});
|
|
90
96
|
|
|
91
|
-
test('R40 interrupted side effect is explicit
|
|
97
|
+
test('R40 interrupted side effect is explicit failed-after-unknown-effect instead of implying replay safety',()=>{
|
|
92
98
|
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
93
99
|
tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'unknown-12345678',
|
|
94
100
|
args:{root_id:'core',path:'x.txt',content:'x'}},{sequence:70});
|
|
95
101
|
const line=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'unknown-12345678',
|
|
96
102
|
outcome:{status:'interrupted',is_error:true,duration_ms:0,side_effect_state:'unknown',recovery:{reason:'runtime_restart'}}},{sequence:71});
|
|
97
|
-
assert.match(line,/
|
|
103
|
+
assert.match(line,/FAILED_AFTER_UNKNOWN_EFFECT β runtime_restart$/);
|
|
98
104
|
assert.doesNotMatch(line,/\s[ββ]\s/);
|
|
99
105
|
});
|
|
100
106
|
|
|
@@ -105,3 +111,96 @@ test('R40 UTF-8 truncation never splits Thai or emoji into replacement character
|
|
|
105
111
|
assert.equal(clipped.includes('\uFFFD'),false);
|
|
106
112
|
assert.ok(clipped.endsWith('β¦'));
|
|
107
113
|
});
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
test('visual opcode projection resolves multiplexed machine_fs operation and exposes terminal evidence',()=>{
|
|
117
|
+
let now=1000;
|
|
118
|
+
const receipt='1'.repeat(64);
|
|
119
|
+
const tracker=createLiveTimelineTracker({clock:()=>now,rootPaths:{projects:'D:\\Projects'}});
|
|
120
|
+
const start=tracker.observe({event_type:'tool.request',tool:'machine_fs',request_id:'machinefs-12345678',
|
|
121
|
+
ts:'1970-01-01T00:00:01.000Z',args:{op:'write',root_id:'projects',path:'S3CORE01-R2/STATUS.txt',content:'x'}},{sequence:80});
|
|
122
|
+
assert.match(start,/^π .*machine_fs\.write /);
|
|
123
|
+
assert.ok(start.includes('D:\\Projects\\S3CORE01-R2\\STATUS.txt [root=projects]'));
|
|
124
|
+
now=1007;
|
|
125
|
+
const done=tracker.observe({event_type:'tool.result',tool:'machine_fs',request_id:'machinefs-12345678',
|
|
126
|
+
ts:'1970-01-01T00:00:01.007Z',outcome:{status:'committed',is_error:false,duration_ms:7,receipt_sha256s:[receipt]}},{sequence:81});
|
|
127
|
+
assert.match(done,/^π .*machine_fs\.write .*7ms β
SUCCEEDED π§Ύ receipt=11111111β¦$/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('visual opcode projection reserves warning semantics for explicitly unknown side effects',()=>{
|
|
131
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
132
|
+
tracker.observe({event_type:'tool.request',tool:'host_exec',request_id:'unknown-host-1234',
|
|
133
|
+
args:{root_id:'core',command_id:'powershell_inline',mode:'inline'}},{sequence:90});
|
|
134
|
+
const done=tracker.observe({event_type:'tool.result',tool:'host_exec',request_id:'unknown-host-1234',
|
|
135
|
+
outcome:{status:'interrupted',is_error:true,duration_ms:4,side_effect_state:'unknown',recovery:{reason:'runtime_restart'}}},{sequence:91});
|
|
136
|
+
assert.match(done,/^β οΈ .*host_exec .*4ms FAILED_AFTER_UNKNOWN_EFFECT β runtime_restart$/);
|
|
137
|
+
assert.doesNotMatch(done,/β|π«/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
test('absolute Windows root rendering shows the operator the real host path and root identity',()=>{
|
|
142
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000,rootPaths:{local_c:'C:\\'}});
|
|
143
|
+
tracker.observe({event_type:'tool.request',tool:'machine_fs',request_id:'localc-123456789',
|
|
144
|
+
args:{op:'read',root_id:'local_c',path:'Users/DEADBYTE/.deadbyte-mcp/active-release.json'}},{sequence:100});
|
|
145
|
+
const line=tracker.observe({event_type:'tool.result',tool:'machine_fs',request_id:'localc-123456789',
|
|
146
|
+
outcome:{is_error:false,duration_ms:1}},{sequence:101});
|
|
147
|
+
assert.ok(line.includes('C:\\Users\\DEADBYTE\\.deadbyte-mcp\\active-release.json [root=local_c]'));
|
|
148
|
+
assert.match(line,/^π .*machine_fs\.read /);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
test('trace mode exposes deterministic trace/span hierarchy without payload bytes',()=>{
|
|
153
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000,trace:true});
|
|
154
|
+
const start=tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'span-1234567890abcdef',
|
|
155
|
+
context:{parent_span_id:'parent-abcdef123456',job_id:'job-112233445566'},args:{root_id:'core',path:'x.txt',content:'SECRET'}},{sequence:110});
|
|
156
|
+
assert.match(start,/trace=job-1122β¦ span=span-123β¦ parent=parent-aβ¦/);
|
|
157
|
+
assert.doesNotMatch(start,/SECRET/);
|
|
158
|
+
const done=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'span-1234567890abcdef',
|
|
159
|
+
outcome:{is_error:false,duration_ms:2}},{sequence:111});
|
|
160
|
+
assert.match(done,/trace=job-1122β¦ span=span-123β¦ parent=parent-aβ¦/);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('timeline filter keeps audit observation complete while selecting presentation by category status and trace',()=>{
|
|
164
|
+
const filter=live.createLiveTimelineFilter('category=write,status=error,trace=goal-1234');
|
|
165
|
+
const request={event_type:'tool.request',tool:'machine_fs',request_id:'filter-span-1',
|
|
166
|
+
args:{op:'write',root_id:'projects',path:'x.txt',goal_id:'goal-1234'}};
|
|
167
|
+
assert.equal(filter.accept(request),false,'status-constrained request start is deferred');
|
|
168
|
+
assert.equal(filter.accept({event_type:'tool.result',tool:'machine_fs',request_id:'filter-span-1',
|
|
169
|
+
outcome:{is_error:true,duration_ms:1}}),true);
|
|
170
|
+
const other={event_type:'tool.request',tool:'coding_file_read',request_id:'filter-span-2',
|
|
171
|
+
args:{root_id:'projects',path:'x.txt',goal_id:'goal-1234'}};
|
|
172
|
+
assert.equal(filter.accept(other),false);
|
|
173
|
+
assert.equal(filter.accept({event_type:'tool.result',tool:'coding_file_read',request_id:'filter-span-2',
|
|
174
|
+
outcome:{is_error:true,duration_ms:1}}),false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('TTY writer rewrites matching visible start in place and never rewrites unrelated output',()=>{
|
|
178
|
+
const writes=[];const stream={isTTY:true,write(value){writes.push(value);return true;}};
|
|
179
|
+
const writer=live.createLiveTimelineWriter({stream,displayMode:'unicode'});
|
|
180
|
+
writer.write('RUNNING',{event_type:'tool.call',request_id:'a'});
|
|
181
|
+
writer.write('DONE',{event_type:'tool.result',request_id:'a'});
|
|
182
|
+
assert.deepEqual(writes,['RUNNING','\r\x1b[2KDONE\n']);
|
|
183
|
+
writes.length=0;
|
|
184
|
+
writer.write('RUN-A',{event_type:'tool.call',request_id:'a'});
|
|
185
|
+
writer.write('OTHER',{event_type:'tool.result',request_id:'b'});
|
|
186
|
+
assert.deepEqual(writes,['RUN-A','\n','OTHER\n']);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('expanded session summary reports outcome classes latency and slowest tool',()=>{
|
|
190
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
191
|
+
const events=[
|
|
192
|
+
['a','coding_file_read',10,false,false],
|
|
193
|
+
['b','coding_file_write',50,false,false],
|
|
194
|
+
['c','coding_command_run',100,true,false],
|
|
195
|
+
['d','host_exec',200,true,true]
|
|
196
|
+
];
|
|
197
|
+
for(const [id,tool,duration,isError,unknown] of events){
|
|
198
|
+
tracker.observe({event_type:'tool.request',tool,request_id:id,args:{root_id:'core',path:'x'}});
|
|
199
|
+
tracker.observe({event_type:'tool.result',tool,request_id:id,outcome:{is_error:isError,duration_ms:duration,
|
|
200
|
+
...(unknown?{side_effect_state:'unknown'}:{})}});
|
|
201
|
+
}
|
|
202
|
+
const summary=tracker.summary();
|
|
203
|
+
assert.match(summary,/4 calls \| 2 ok \| 1 error \| 1 unknown \| 3 mutations/);
|
|
204
|
+
assert.match(summary,/p50 75ms \| p95 200ms/);
|
|
205
|
+
assert.match(summary,/slowest host_exec 200ms/);
|
|
206
|
+
});
|
|
@@ -466,3 +466,43 @@ winTest('bare remote surfaces bounded controller-start-failure evidence instead
|
|
|
466
466
|
assert.match(output.stderr,/controller-start-failure/i);
|
|
467
467
|
}finally{await rm(fx.dir,{recursive:true,force:true});}
|
|
468
468
|
});
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
winTest('remote --trace --filter selects presentation without dropping journaled events',async()=>{
|
|
472
|
+
const fx=await fixture();
|
|
473
|
+
let child=null;
|
|
474
|
+
try{
|
|
475
|
+
child=spawn(process.execPath,[cliPath,'remote','--trace','--filter','category=write,status=error,trace=goal-1234'],{
|
|
476
|
+
env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true
|
|
477
|
+
});
|
|
478
|
+
const output=await collect(child);
|
|
479
|
+
await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
480
|
+
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
481
|
+
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
482
|
+
const audit=[
|
|
483
|
+
{event_type:'tool.request',tool:'machine_fs',request_id:'filter-write-1234',created_at_utc:'2026-09-19T01:00:00.000Z',
|
|
484
|
+
args:{op:'write',root_id:'projects',path:'x.txt',goal_id:'goal-1234',content:'SECRET'}},
|
|
485
|
+
{event_type:'tool.result',tool:'machine_fs',request_id:'filter-write-1234',created_at_utc:'2026-09-19T01:00:00.010Z',
|
|
486
|
+
outcome:{is_error:true,duration_ms:10,reason:'denied'}},
|
|
487
|
+
{event_type:'tool.request',tool:'coding_file_read',request_id:'filter-read-12345',created_at_utc:'2026-09-19T01:00:00.020Z',
|
|
488
|
+
args:{root_id:'projects',path:'x.txt',goal_id:'goal-1234'}},
|
|
489
|
+
{event_type:'tool.result',tool:'coding_file_read',request_id:'filter-read-12345',created_at_utc:'2026-09-19T01:00:00.030Z',
|
|
490
|
+
outcome:{is_error:true,duration_ms:10,reason:'missing'}}
|
|
491
|
+
];
|
|
492
|
+
await writeFile(path.join(auditDir,'segment-00000001.jsonl'),audit.map(JSON.stringify).join('\n')+'\n','utf8');
|
|
493
|
+
const deadline=Date.now()+4000;
|
|
494
|
+
while(Date.now()<deadline&&!/machine_fs\.write.*trace=goal-1234/.test(output.stdout)) await sleep(50);
|
|
495
|
+
assert.match(output.stdout,/machine_fs\.write.*trace=goal-123\.\.\. span=filter-w\.\.\..*10ms.*ERR - denied/);
|
|
496
|
+
assert.doesNotMatch(output.stdout,/coding_file_read.*missing/);
|
|
497
|
+
assert.doesNotMatch(output.stdout,/SECRET/);
|
|
498
|
+
const journal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',marker.result_session_id+'.json'),'utf8'));
|
|
499
|
+
assert.equal(journal.events.length,4,'presentation filter must not remove journal evidence');
|
|
500
|
+
child.stdin.end();
|
|
501
|
+
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
502
|
+
assert.equal(ended.timeout,undefined);
|
|
503
|
+
assert.equal(ended.code,0,output.stderr);
|
|
504
|
+
}finally{
|
|
505
|
+
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
506
|
+
await rm(fx.dir,{recursive:true,force:true});
|
|
507
|
+
}
|
|
508
|
+
});
|
package/test/tool-audit.test.mjs
CHANGED
|
@@ -132,3 +132,20 @@ test('controller enables audit only for Bridge child and restores parent audit e
|
|
|
132
132
|
assert.match(source,/if \(\$hadAuditRuntimeEnv\) \{ \$env:DEADBYTE_AUDIT_RUNTIME = \$previousAuditRuntimeEnv \}/);
|
|
133
133
|
assert.match(source,/else \{ Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue \}/);
|
|
134
134
|
});
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
test('audit outcome reason preserves ordinary letters and Windows paths while folding actual whitespace',async()=>{
|
|
138
|
+
const root=await temp();
|
|
139
|
+
try{
|
|
140
|
+
const k=await keys(root);
|
|
141
|
+
const audit=await createToolAudit({stateRoot:root,signingKeyPath:k.privateKeyPath,contextProvider:async()=>context()});
|
|
142
|
+
const server=fakeServer();installToolAudit(server,audit);
|
|
143
|
+
server.registerTool('broken_read',{},async()=>({
|
|
144
|
+
isError:true,
|
|
145
|
+
structuredContent:{status:'error',error:"Error:\tENOENT: no such file or directory,\nrealpath 'D:\\\\Projects\\\\runtime'"}
|
|
146
|
+
}));
|
|
147
|
+
await server.handlers.get('broken_read')({path:'x'});
|
|
148
|
+
const all=await events(root);
|
|
149
|
+
assert.equal(all.at(-1).outcome.reason,"Error: ENOENT: no such file or directory, realpath 'D:\\\\Projects\\\\runtime'");
|
|
150
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
151
|
+
});
|
|
@@ -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
|
+
});
|