deadbyte-mcp 0.15.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/MANIFEST.SHA256 +76 -41
- package/PROVENANCE.json +10 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +14 -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 +2 -2
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +1 -1
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +12 -7
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -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-r45.mjs +121 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -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-r45.mjs +253 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/autonomous-runtime.mjs +12 -7
- package/src/capability-ledger.mjs +1 -0
- 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/host-plane.mjs +133 -85
- package/src/mcp-server.mjs +64 -23
- package/src/native-diff-v2.mjs +9 -4
- package/src/native-diff.mjs +29 -7
- 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 +6 -2
- package/src/output-contracts.mjs +27 -11
- 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-mcp-r45-parity-client.mjs +206 -0
- package/src/runtime-update.mjs +36 -8
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -0
- package/test/autonomous-loop.test.mjs +24 -0
- package/test/autonomous-runtime.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/coding-plane.test.mjs +1 -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/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/machine-mcp-integration.test.mjs +1 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +1 -0
- package/test/package-boundary.test.mjs +1 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +20 -19
- package/test/r33-finalization.test.mjs +3 -3
- 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 +52 -41
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { DEADBYTE_VERSION, MCP_PROTOCOL_VERSION } from './version.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
9
|
+
const stateRoot=()=>path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
10
|
+
const sha256=b=>createHash('sha256').update(b).digest('hex');
|
|
11
|
+
async function jsonOrNull(file){try{return JSON.parse((await readFile(file,'utf8')).replace(/^\uFEFF/,''));}catch(e){if(e?.code==='ENOENT')return null;throw e;}}
|
|
12
|
+
async function fileHash(file){return sha256(await readFile(file));}
|
|
13
|
+
function parseManifest(text){
|
|
14
|
+
const rows=[];
|
|
15
|
+
for(const [index,line] of text.split(/\r?\n/).entries()){
|
|
16
|
+
if(!line) continue;
|
|
17
|
+
const m=/^([0-9a-f]{64}) (.+)$/.exec(line);
|
|
18
|
+
if(!m) throw new Error(`manifest line ${index+1} malformed`);
|
|
19
|
+
if(path.isAbsolute(m[2])||m[2].includes('..')||m[2].includes('\\')) throw new Error(`manifest line ${index+1} path unsafe`);
|
|
20
|
+
rows.push({sha256:m[1],relative:m[2]});
|
|
21
|
+
}
|
|
22
|
+
return rows;
|
|
23
|
+
}
|
|
24
|
+
export async function doctor(){
|
|
25
|
+
const manifestPath=path.join(root,'MANIFEST.SHA256');
|
|
26
|
+
const manifestBytes=await readFile(manifestPath);
|
|
27
|
+
const manifestSha256=sha256(manifestBytes);
|
|
28
|
+
const rows=parseManifest(manifestBytes.toString('utf8'));
|
|
29
|
+
let verified=0;
|
|
30
|
+
for(const row of rows){
|
|
31
|
+
const file=path.join(root,...row.relative.split('/'));
|
|
32
|
+
const st=await lstat(file);
|
|
33
|
+
if(!st.isFile()||st.isSymbolicLink()) throw new Error(`manifest entry not regular file: ${row.relative}`);
|
|
34
|
+
const actual=await fileHash(file);
|
|
35
|
+
if(actual!==row.sha256) throw new Error(`manifest hash mismatch: ${row.relative}`);
|
|
36
|
+
verified+=1;
|
|
37
|
+
}
|
|
38
|
+
const metadata={};
|
|
39
|
+
for(const name of ['RELEASE.json','TEST-MATRIX.json','PROVENANCE.json','SBOM.cdx.json']){
|
|
40
|
+
const file=path.join(root,name),body=await jsonOrNull(file);
|
|
41
|
+
if(!body) throw new Error(`required release metadata missing: ${name}`);
|
|
42
|
+
metadata[name]={sha256:await fileHash(file),schema:body.schema??body.bomFormat??null};
|
|
43
|
+
}
|
|
44
|
+
const release=await jsonOrNull(path.join(root,'RELEASE.json'));
|
|
45
|
+
if(release?.version!==DEADBYTE_VERSION) throw new Error('RELEASE.json version mismatch');
|
|
46
|
+
const hardware=path.join(root,'bin','deadbyte-hardware-observer-r45.exe');
|
|
47
|
+
const hardwareStat=await lstat(hardware);
|
|
48
|
+
if(!hardwareStat.isFile()||hardwareStat.isSymbolicLink()) throw new Error('hardware observer must be a regular file');
|
|
49
|
+
const hardwareSha256=await fileHash(hardware);
|
|
50
|
+
const active=await jsonOrNull(path.join(stateRoot(),'active-release.json'));
|
|
51
|
+
const hostPolicy=await jsonOrNull(path.join(root,'controller','deadbyte-host-policy.json'));
|
|
52
|
+
if(hostPolicy&&release?.train==='R45'&&process.platform==='win32'&&hostPolicy.schema!=='deadbyte.host-policy.v2') throw new Error('R45 doctor rejects non-v2 generated host policy');
|
|
53
|
+
return {
|
|
54
|
+
schema:'deadbyte.doctor.v1',status:'ok',package_version:DEADBYTE_VERSION,protocol_revision:MCP_PROTOCOL_VERSION,
|
|
55
|
+
platform:{current:process.platform,architecture:process.arch,primary:'win32',supported:process.platform==='win32'},
|
|
56
|
+
manifest:{sha256:manifestSha256,entries:rows.length,verified_entries:verified,verified:true},
|
|
57
|
+
release_metadata:metadata,
|
|
58
|
+
native:{hardware_observer:{path:'bin/deadbyte-hardware-observer-r45.exe',sha256:hardwareSha256,bytes:hardwareStat.size}},
|
|
59
|
+
policy:{host_schema:hostPolicy?.schema??null,host_v2_ready:hostPolicy?.schema==='deadbyte.host-policy.v2'},
|
|
60
|
+
active_release:active?{release_id:active.release_id,version:active.version,manifest_sha256:active.manifest_sha256,generation_manifest_sha256:active.generation_manifest_sha256,
|
|
61
|
+
candidate_active:active.version===DEADBYTE_VERSION&&active.manifest_sha256===manifestSha256}:null,
|
|
62
|
+
known_unsupported:process.platform==='win32'?[]:['native_hardware_observation','windows_controller','desktop_native']
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function printDoctor(){const value=await doctor();process.stdout.write(JSON.stringify(value,null,2)+'\n');return value;}
|
package/src/host-plane.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { decodeUtf8Strict } from './text-policy.mjs';
|
|
|
16
16
|
import { isInside, validatePortableRelativePath } from './path-policy.mjs';
|
|
17
17
|
|
|
18
18
|
export const HOST_POLICY_SCHEMA = 'deadbyte.host-policy.v1';
|
|
19
|
+
export const HOST_POLICY_SCHEMA_V2 = 'deadbyte.host-policy.v2';
|
|
19
20
|
export const HOST_ARM_SCHEMA = 'deadbyte.host-arm.v1';
|
|
20
21
|
export const HOST_GRANT_SCHEMA = 'deadbyte.host-grant.v2';
|
|
21
22
|
export const HOST_DEFAULT_MAX_READ_BYTES = 256 * 1024;
|
|
@@ -45,7 +46,7 @@ function finiteInteger(value, name, min, max, fallback) {
|
|
|
45
46
|
|
|
46
47
|
function normalizePolicyObject(raw, policyPath) {
|
|
47
48
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('host policy must be a JSON object');
|
|
48
|
-
if (raw.schema
|
|
49
|
+
if (![HOST_POLICY_SCHEMA, HOST_POLICY_SCHEMA_V2].includes(raw.schema)) throw new Error('host policy schema mismatch');
|
|
49
50
|
if (raw.enabled !== true) throw new Error('host policy is not enabled');
|
|
50
51
|
if (!raw.arm_file || typeof raw.arm_file !== 'string' || !path.isAbsolute(raw.arm_file)) {
|
|
51
52
|
throw new Error('host policy arm_file must be absolute');
|
|
@@ -75,7 +76,10 @@ function normalizePolicyObject(raw, policyPath) {
|
|
|
75
76
|
for (const [id, value] of Object.entries(raw.commands)) {
|
|
76
77
|
if (!COMMAND_ID_RE.test(id)) throw new Error(`invalid host command id '${id}'`);
|
|
77
78
|
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`host command '${id}' malformed`);
|
|
78
|
-
|
|
79
|
+
const kind=value.kind;
|
|
80
|
+
if (kind!=='script' && !(raw.schema===HOST_POLICY_SCHEMA_V2 && kind==='inline')) {
|
|
81
|
+
throw new Error(`host command '${id}' kind must be script${raw.schema===HOST_POLICY_SCHEMA_V2?' or inline':''}`);
|
|
82
|
+
}
|
|
79
83
|
if (typeof value.executable !== 'string' || !path.isAbsolute(value.executable)) {
|
|
80
84
|
throw new Error(`host command '${id}' executable must be absolute`);
|
|
81
85
|
}
|
|
@@ -86,37 +90,60 @@ function normalizePolicyObject(raw, policyPath) {
|
|
|
86
90
|
if (rootIds.length === 0 || rootIds.some(rootId => !ROOT_ID_RE.test(rootId) || !roots[rootId])) {
|
|
87
91
|
throw new Error(`host command '${id}' root_ids invalid`);
|
|
88
92
|
}
|
|
89
|
-
if (!Array.isArray(value.prefix_args) || value.prefix_args.some(x => typeof x !== 'string')) {
|
|
93
|
+
if (!Array.isArray(value.prefix_args) || value.prefix_args.some(x => typeof x !== 'string' || x.includes('\0'))) {
|
|
90
94
|
throw new Error(`host command '${id}' prefix_args must be strings`);
|
|
91
95
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
throw new Error(`host command '${id}' extensions invalid`);
|
|
95
|
-
}
|
|
96
|
-
commands[id] = {
|
|
97
|
-
kind: 'script',
|
|
96
|
+
const base={
|
|
97
|
+
kind,
|
|
98
98
|
executable: path.resolve(value.executable),
|
|
99
99
|
executable_sha256: value.executable_sha256.toLowerCase(),
|
|
100
100
|
root_ids: [...new Set(rootIds)],
|
|
101
101
|
prefix_args: [...value.prefix_args],
|
|
102
|
-
extensions: value.extensions.map(x => x.toLowerCase()),
|
|
103
102
|
timeout_ms: finiteInteger(value.timeout_ms, `commands.${id}.timeout_ms`, 100, 300_000, HOST_DEFAULT_TIMEOUT_MS),
|
|
104
103
|
max_output_bytes: finiteInteger(value.max_output_bytes, `commands.${id}.max_output_bytes`, 1024, 4 * 1024 * 1024, HOST_DEFAULT_MAX_OUTPUT_BYTES)
|
|
105
104
|
};
|
|
105
|
+
if(kind==='script'){
|
|
106
|
+
if (!Array.isArray(value.extensions) || value.extensions.length === 0 ||
|
|
107
|
+
value.extensions.some(x => typeof x !== 'string' || !/^\.[A-Za-z0-9._-]+$/.test(x))) {
|
|
108
|
+
throw new Error(`host command '${id}' extensions invalid`);
|
|
109
|
+
}
|
|
110
|
+
base.extensions=value.extensions.map(x => x.toLowerCase());
|
|
111
|
+
} else {
|
|
112
|
+
base.max_command_chars=finiteInteger(value.max_command_chars,`commands.${id}.max_command_chars`,1,262144,32768);
|
|
113
|
+
}
|
|
114
|
+
commands[id]=base;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let nativeExec={enabled:false,root_ids:[],timeout_ms:HOST_DEFAULT_TIMEOUT_MS,max_output_bytes:HOST_DEFAULT_MAX_OUTPUT_BYTES,max_args:64,max_arg_chars:8192};
|
|
118
|
+
if(raw.schema===HOST_POLICY_SCHEMA_V2 && raw.native_exec!==undefined){
|
|
119
|
+
const value=raw.native_exec;
|
|
120
|
+
if(!value||typeof value!=='object'||Array.isArray(value)) throw new Error('host native_exec malformed');
|
|
121
|
+
if(value.enabled===true){
|
|
122
|
+
const rootIds=Array.isArray(value.root_ids)?value.root_ids:[];
|
|
123
|
+
if(rootIds.length===0||rootIds.some(rootId=>!ROOT_ID_RE.test(rootId)||!roots[rootId])) throw new Error('host native_exec root_ids invalid');
|
|
124
|
+
nativeExec={
|
|
125
|
+
enabled:true,
|
|
126
|
+
root_ids:[...new Set(rootIds)],
|
|
127
|
+
timeout_ms:finiteInteger(value.timeout_ms,'native_exec.timeout_ms',100,300000,HOST_DEFAULT_TIMEOUT_MS),
|
|
128
|
+
max_output_bytes:finiteInteger(value.max_output_bytes,'native_exec.max_output_bytes',1024,4*1024*1024,HOST_DEFAULT_MAX_OUTPUT_BYTES),
|
|
129
|
+
max_args:finiteInteger(value.max_args,'native_exec.max_args',0,64,64),
|
|
130
|
+
max_arg_chars:finiteInteger(value.max_arg_chars,'native_exec.max_arg_chars',1,32768,8192)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
return {
|
|
109
|
-
schema:
|
|
136
|
+
schema: raw.schema,
|
|
110
137
|
enabled: true,
|
|
111
138
|
policy_path: path.resolve(policyPath),
|
|
112
139
|
arm_file: path.resolve(raw.arm_file),
|
|
113
140
|
roots,
|
|
114
141
|
commands,
|
|
142
|
+
native_exec:nativeExec,
|
|
115
143
|
max_read_bytes: finiteInteger(raw.max_read_bytes, 'max_read_bytes', 1, 4 * 1024 * 1024, HOST_DEFAULT_MAX_READ_BYTES),
|
|
116
144
|
max_write_bytes: finiteInteger(raw.max_write_bytes, 'max_write_bytes', 1, 8 * 1024 * 1024, HOST_DEFAULT_MAX_WRITE_BYTES)
|
|
117
145
|
};
|
|
118
146
|
}
|
|
119
|
-
|
|
120
147
|
export async function loadHostPolicy(policyPath) {
|
|
121
148
|
if (!policyPath) throw new Error('missing DEADBYTE privileged host policy path');
|
|
122
149
|
const absolute = path.resolve(policyPath);
|
|
@@ -345,6 +372,93 @@ function collectBounded(stream, limit, state, label, onExceeded) {
|
|
|
345
372
|
});
|
|
346
373
|
}
|
|
347
374
|
|
|
375
|
+
function validateHostArgs(args,maxArgs=64,maxChars=2048,label='host exec args'){
|
|
376
|
+
if(!Array.isArray(args)||args.length>maxArgs||args.some(x=>typeof x!=='string'||x.length>maxChars||x.includes('\0'))){
|
|
377
|
+
throw new Error(`${label} must be <=${maxArgs} strings of <=${maxChars} characters`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function resolveExecCwd(policy,rootId,cwd){
|
|
381
|
+
const root=getRoot(policy,rootId,'exec');
|
|
382
|
+
let cwdResolved=root.real_path;
|
|
383
|
+
if(cwd!=='.'){
|
|
384
|
+
validatePortableRelativePath(cwd);
|
|
385
|
+
cwdResolved=path.resolve(root.real_path,cwd);
|
|
386
|
+
if(!isInside(root.real_path,cwdResolved)) throw new Error('host exec cwd escapes allowed root');
|
|
387
|
+
const realCwd=await realpath(cwdResolved);
|
|
388
|
+
if(!isInside(root.real_path,realCwd)) throw new Error('host exec cwd symlink escapes allowed root');
|
|
389
|
+
const cwdStat=await stat(realCwd);
|
|
390
|
+
if(!cwdStat.isDirectory()) throw new Error('host exec cwd must be a directory');
|
|
391
|
+
cwdResolved=realCwd;
|
|
392
|
+
}
|
|
393
|
+
return cwdResolved;
|
|
394
|
+
}
|
|
395
|
+
async function runHostChild(policy,armed,{executable,argv,cwd,timeoutMs,maxTimeoutMs,maxOutputBytes,authorityPollMs=100}){
|
|
396
|
+
const activeArm=await recheckHostAuthority(policy,armed);
|
|
397
|
+
const grantBound=Number.isFinite(activeArm.remaining_ms)?Math.floor(activeArm.remaining_ms):maxTimeoutMs;
|
|
398
|
+
if(grantBound<100) throw new Error('privileged host plane arm lease expires too soon');
|
|
399
|
+
const requested=timeoutMs===null||timeoutMs===undefined?maxTimeoutMs:finiteInteger(timeoutMs,'timeout_ms',100,maxTimeoutMs,maxTimeoutMs);
|
|
400
|
+
const effectiveTimeout=Math.min(requested,maxTimeoutMs,grantBound);
|
|
401
|
+
const child=spawn(executable,argv,{cwd,env:scrubHostEnvironment(),shell:false,windowsHide:true,detached:false,stdio:['ignore','pipe','pipe']});
|
|
402
|
+
const stdoutState={chunks:[],bytes:0,exceeded:null};
|
|
403
|
+
const stderrState={chunks:[],bytes:0,exceeded:null};
|
|
404
|
+
const kill=()=>{try{child.kill('SIGKILL');}catch{}};
|
|
405
|
+
collectBounded(child.stdout,maxOutputBytes,stdoutState,'stdout',kill);
|
|
406
|
+
collectBounded(child.stderr,maxOutputBytes,stderrState,'stderr',kill);
|
|
407
|
+
let timedOut=false,authorityError=null,timer=null,poller=null;
|
|
408
|
+
const completion=new Promise((resolve,reject)=>{
|
|
409
|
+
child.once('error',reject);
|
|
410
|
+
child.once('close',(code,signal)=>resolve({code,signal}));
|
|
411
|
+
timer=setTimeout(()=>{timedOut=true;kill();},effectiveTimeout); timer.unref?.();
|
|
412
|
+
const poll=Math.max(25,Math.min(1000,Number.isInteger(authorityPollMs)?authorityPollMs:100));
|
|
413
|
+
poller=setInterval(()=>{void recheckHostAuthority(policy,armed).catch(error=>{if(!authorityError){authorityError=error;kill();}});},poll); poller.unref?.();
|
|
414
|
+
});
|
|
415
|
+
let result;
|
|
416
|
+
try{result=await completion;}finally{if(timer)clearTimeout(timer);if(poller)clearInterval(poller);}
|
|
417
|
+
if(authorityError) throw authorityError;
|
|
418
|
+
if(stdoutState.exceeded||stderrState.exceeded) throw new Error(stdoutState.exceeded??stderrState.exceeded);
|
|
419
|
+
const stdoutBytes=Buffer.concat(stdoutState.chunks),stderrBytes=Buffer.concat(stderrState.chunks);
|
|
420
|
+
return {
|
|
421
|
+
exit_code:result.code,signal:result.signal,timed_out:timedOut,timeout_ms:effectiveTimeout,
|
|
422
|
+
stdout:decodeUtf8Strict(stdoutBytes),stderr:decodeUtf8Strict(stderrBytes),
|
|
423
|
+
stdout_sha256:sha256Bytes(stdoutBytes),stderr_sha256:sha256Bytes(stderrBytes),
|
|
424
|
+
argv_sha256:sha256Bytes(Buffer.from(JSON.stringify(argv),'utf8'))
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export async function hostExecInline(policy,{commandId,rootId,command,cwd='.',timeoutMs=null,authorityPollMs=100}){
|
|
429
|
+
const armed=await assertHostArmed(policy);
|
|
430
|
+
if(!COMMAND_ID_RE.test(commandId)) throw new Error('invalid host command id');
|
|
431
|
+
const spec=policy.commands[commandId];
|
|
432
|
+
if(!spec||spec.kind!=='inline') throw new Error(`host inline command '${commandId}' is not allowed`);
|
|
433
|
+
if(!ROOT_ID_RE.test(rootId)||!spec.root_ids.includes(rootId)) throw new Error(`host command '${commandId}' is not allowed for root '${rootId}'`);
|
|
434
|
+
getRoot(policy,rootId,'exec');
|
|
435
|
+
if(typeof command!=='string'||command.length<1||command.length>spec.max_command_chars||command.includes('\0')) throw new Error('host inline command exceeds policy bounds');
|
|
436
|
+
const actualExecutableHash=await sha256File(spec.executable);
|
|
437
|
+
if(actualExecutableHash!==spec.executable_sha256) throw new Error(`host command '${commandId}' executable hash changed`);
|
|
438
|
+
const cwdResolved=await resolveExecCwd(policy,rootId,cwd);
|
|
439
|
+
const argv=[...spec.prefix_args,command];
|
|
440
|
+
const result=await runHostChild(policy,armed,{executable:spec.executable,argv,cwd:cwdResolved,timeoutMs,maxTimeoutMs:spec.timeout_ms,maxOutputBytes:spec.max_output_bytes,authorityPollMs});
|
|
441
|
+
return {command_id:commandId,mode:'inline',executable_sha256:actualExecutableHash,command_sha256:sha256Bytes(Buffer.from(command,'utf8')),...result};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function hostExecNative(policy,{rootId,executable,executableSha256,args=[],cwd='.',timeoutMs=null,authorityPollMs=100}){
|
|
445
|
+
const armed=await assertHostArmed(policy);
|
|
446
|
+
const spec=policy.native_exec;
|
|
447
|
+
if(!spec?.enabled) throw new Error('host native exec is not enabled by policy');
|
|
448
|
+
if(!ROOT_ID_RE.test(rootId)||!spec.root_ids.includes(rootId)) throw new Error(`host native exec is not allowed for root '${rootId}'`);
|
|
449
|
+
getRoot(policy,rootId,'exec');
|
|
450
|
+
if(typeof executable!=='string'||!path.isAbsolute(executable)) throw new Error('host native executable must be absolute');
|
|
451
|
+
if(typeof executableSha256!=='string'||!SHA256_RE.test(executableSha256)) throw new Error('executable_sha256 must be a lowercase SHA-256 digest');
|
|
452
|
+
validateHostArgs(args,spec.max_args,spec.max_arg_chars,'host native args');
|
|
453
|
+
const resolved=path.resolve(executable); const info=await stat(resolved);
|
|
454
|
+
if(!info.isFile()) throw new Error('host native executable is not a file');
|
|
455
|
+
const actualExecutableHash=await sha256File(resolved);
|
|
456
|
+
if(actualExecutableHash!==executableSha256) throw new Error(`host native executable hash mismatch: expected ${executableSha256}, found ${actualExecutableHash}`);
|
|
457
|
+
const cwdResolved=await resolveExecCwd(policy,rootId,cwd);
|
|
458
|
+
const result=await runHostChild(policy,armed,{executable:resolved,argv:[...args],cwd:cwdResolved,timeoutMs,maxTimeoutMs:spec.timeout_ms,maxOutputBytes:spec.max_output_bytes,authorityPollMs});
|
|
459
|
+
return {command_id:'native',mode:'native',executable_sha256:actualExecutableHash,...result};
|
|
460
|
+
}
|
|
461
|
+
|
|
348
462
|
export async function hostExec(policy, {
|
|
349
463
|
commandId,
|
|
350
464
|
rootId,
|
|
@@ -357,14 +471,12 @@ export async function hostExec(policy, {
|
|
|
357
471
|
const armed = await assertHostArmed(policy);
|
|
358
472
|
if (!COMMAND_ID_RE.test(commandId)) throw new Error('invalid host command id');
|
|
359
473
|
const command = policy.commands[commandId];
|
|
360
|
-
if (!command) throw new Error(`host command '${commandId}' is not allowed`);
|
|
361
|
-
|
|
362
|
-
throw new Error('host exec args must be <=64 strings of <=2048 characters');
|
|
363
|
-
}
|
|
474
|
+
if (!command || command.kind!=='script') throw new Error(`host command '${commandId}' is not allowed as a script command`);
|
|
475
|
+
validateHostArgs(args,64,2048,'host exec args');
|
|
364
476
|
if (typeof scriptSha256 !== 'string' || !SHA256_RE.test(scriptSha256)) throw new Error('script_sha256 must be a lowercase SHA-256 digest');
|
|
365
477
|
|
|
366
478
|
if (!ROOT_ID_RE.test(rootId) || !command.root_ids.includes(rootId)) throw new Error(`host command '${commandId}' is not allowed for root '${rootId}'`);
|
|
367
|
-
|
|
479
|
+
getRoot(policy, rootId, 'exec');
|
|
368
480
|
const script = await resolveHostExisting(policy, rootId, scriptPath, 'exec');
|
|
369
481
|
const extension = path.extname(script.resolved).toLowerCase();
|
|
370
482
|
if (!command.extensions.includes(extension)) throw new Error(`script extension '${extension}' is not allowed for '${commandId}'`);
|
|
@@ -373,72 +485,8 @@ export async function hostExec(policy, {
|
|
|
373
485
|
|
|
374
486
|
const actualExecutableHash = await sha256File(command.executable);
|
|
375
487
|
if (actualExecutableHash !== command.executable_sha256) throw new Error(`host command '${commandId}' executable hash changed`);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
cwdResolved = path.resolve(root.real_path, cwd);
|
|
381
|
-
if (!isInside(root.real_path, cwdResolved)) throw new Error('host exec cwd escapes allowed root');
|
|
382
|
-
const realCwd = await realpath(cwdResolved);
|
|
383
|
-
if (!isInside(root.real_path, realCwd)) throw new Error('host exec cwd symlink escapes allowed root');
|
|
384
|
-
const cwdStat = await stat(realCwd);
|
|
385
|
-
if (!cwdStat.isDirectory()) throw new Error('host exec cwd must be a directory');
|
|
386
|
-
cwdResolved = realCwd;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const requestedTimeout = timeoutMs === null ? command.timeout_ms : finiteInteger(timeoutMs, 'timeout_ms', 100, command.timeout_ms, command.timeout_ms);
|
|
390
|
-
const activeArm = await recheckHostAuthority(policy, armed);
|
|
391
|
-
const grantBound = Number.isFinite(activeArm.remaining_ms) ? Math.floor(activeArm.remaining_ms) : command.timeout_ms;
|
|
392
|
-
if (grantBound < 100) throw new Error('privileged host plane arm lease expires too soon');
|
|
393
|
-
const effectiveTimeout = Math.min(requestedTimeout, command.timeout_ms, grantBound);
|
|
394
|
-
const argv = [...command.prefix_args, script.resolved, ...args];
|
|
395
|
-
|
|
396
|
-
const child = spawn(command.executable, argv, {
|
|
397
|
-
cwd: cwdResolved,
|
|
398
|
-
env: scrubHostEnvironment(),
|
|
399
|
-
shell: false,
|
|
400
|
-
windowsHide: true,
|
|
401
|
-
detached: false,
|
|
402
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
const stdoutState = { chunks: [], bytes: 0, exceeded: null };
|
|
406
|
-
const stderrState = { chunks: [], bytes: 0, exceeded: null };
|
|
407
|
-
collectBounded(child.stdout, command.max_output_bytes, stdoutState, 'stdout', () => child.kill('SIGKILL'));
|
|
408
|
-
collectBounded(child.stderr, command.max_output_bytes, stderrState, 'stderr', () => child.kill('SIGKILL'));
|
|
409
|
-
|
|
410
|
-
let timedOut = false;
|
|
411
|
-
let timer;
|
|
412
|
-
const completion = new Promise((resolve, reject) => {
|
|
413
|
-
child.once('error', reject);
|
|
414
|
-
child.once('close', (code, signal) => resolve({ code, signal }));
|
|
415
|
-
timer = setTimeout(() => {
|
|
416
|
-
timedOut = true;
|
|
417
|
-
child.kill('SIGKILL');
|
|
418
|
-
}, effectiveTimeout);
|
|
419
|
-
timer.unref?.();
|
|
420
|
-
});
|
|
421
|
-
let result;
|
|
422
|
-
try {
|
|
423
|
-
result = await completion;
|
|
424
|
-
} finally {
|
|
425
|
-
if (timer) clearTimeout(timer);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (stdoutState.exceeded || stderrState.exceeded) {
|
|
429
|
-
throw new Error(stdoutState.exceeded ?? stderrState.exceeded);
|
|
430
|
-
}
|
|
431
|
-
const stdoutBytes = Buffer.concat(stdoutState.chunks);
|
|
432
|
-
const stderrBytes = Buffer.concat(stderrState.chunks);
|
|
433
|
-
return {
|
|
434
|
-
command_id: commandId,
|
|
435
|
-
executable_sha256: actualExecutableHash,
|
|
436
|
-
script_sha256: actualScriptHash,
|
|
437
|
-
exit_code: result.code,
|
|
438
|
-
signal: result.signal,
|
|
439
|
-
timed_out: timedOut,
|
|
440
|
-
timeout_ms: effectiveTimeout,
|
|
441
|
-
stdout: decodeUtf8Strict(stdoutBytes),
|
|
442
|
-
stderr: decodeUtf8Strict(stderrBytes)
|
|
443
|
-
};
|
|
488
|
+
const cwdResolved=await resolveExecCwd(policy,rootId,cwd);
|
|
489
|
+
const argv=[...command.prefix_args,script.resolved,...args];
|
|
490
|
+
const result=await runHostChild(policy,armed,{executable:command.executable,argv,cwd:cwdResolved,timeoutMs,maxTimeoutMs:command.timeout_ms,maxOutputBytes:command.max_output_bytes});
|
|
491
|
+
return {command_id:commandId,mode:'script',executable_sha256:actualExecutableHash,script_sha256:actualScriptHash,...result};
|
|
444
492
|
}
|
package/src/mcp-server.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { DEADBYTE_VERSION } from './version.mjs';
|
|
|
15
15
|
import { currentIngressAdapterMode, TUNNEL_STDIO_PROBE_COMPAT_MODE } from './ingress-mode.mjs';
|
|
16
16
|
import { installTunnelStdioProbeCompat } from './tunnel-stdio-compat.mjs';
|
|
17
17
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
18
|
-
import { assertHostArmed, hostExec, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
|
|
18
|
+
import { assertHostArmed, hostExec, hostExecInline, hostExecNative, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
|
|
19
19
|
import { createMachineRuntime } from './machine-identity.mjs';
|
|
20
20
|
import { registerMachineTools } from './machine-mcp-tools.mjs';
|
|
21
21
|
import { createAgentRuntime } from './agent-runtime.mjs';
|
|
@@ -75,8 +75,9 @@ if (!['0','1'].includes(auditRuntimeMode)) throw new Error('DEADBYTE_AUDIT_RUNTI
|
|
|
75
75
|
const runtimeStateRoot = path.resolve(process.env.DEADBYTE_STATE_ROOT || path.join(os.homedir(),'.deadbyte-mcp'));
|
|
76
76
|
const surfaceProfile = capabilityProfileFromEnv(process.env);
|
|
77
77
|
const localPrivilegedPolicyPath = path.join(packageRoot, 'controller', 'deadbyte-host-policy.json');
|
|
78
|
+
const privilegedPolicyExplicit = Object.hasOwn(process.env, 'DEADBYTE_PRIVILEGED_POLICY');
|
|
78
79
|
let privilegedPolicyPath = process.env.DEADBYTE_PRIVILEGED_POLICY ?? '';
|
|
79
|
-
if (!
|
|
80
|
+
if (!privilegedPolicyExplicit) {
|
|
80
81
|
try {
|
|
81
82
|
await access(localPrivilegedPolicyPath);
|
|
82
83
|
privilegedPolicyPath = localPrivilegedPolicyPath;
|
|
@@ -505,31 +506,71 @@ serveStdio(() => {
|
|
|
505
506
|
toolServer.registerTool(
|
|
506
507
|
'host_exec',
|
|
507
508
|
{
|
|
508
|
-
description: 'Privileged
|
|
509
|
-
inputSchema: z.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
509
|
+
description: 'Privileged Host execution with three bounded modes: legacy hash-pinned script, policy-approved inline argv memory, or absolute hash-pinned native executable argv. shell=false; live Host revocation is enforced.',
|
|
510
|
+
inputSchema: z.union([
|
|
511
|
+
z.strictObject({
|
|
512
|
+
mode: z.literal('script').optional().default('script'),
|
|
513
|
+
command_id: z.string().min(1).max(64),
|
|
514
|
+
root_id: z.string().min(1).max(64),
|
|
515
|
+
script_path: z.string().min(1).max(512),
|
|
516
|
+
script_sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
517
|
+
args: z.array(z.string().max(2048)).max(64).default([]),
|
|
518
|
+
cwd: z.string().min(1).max(512).default('.'),
|
|
519
|
+
timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
|
|
520
|
+
}),
|
|
521
|
+
z.strictObject({
|
|
522
|
+
mode: z.literal('inline'),
|
|
523
|
+
command_id: z.string().min(1).max(64),
|
|
524
|
+
root_id: z.string().min(1).max(64),
|
|
525
|
+
command: z.string().min(1).max(262144),
|
|
526
|
+
cwd: z.string().min(1).max(512).default('.'),
|
|
527
|
+
timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
|
|
528
|
+
}),
|
|
529
|
+
z.strictObject({
|
|
530
|
+
mode: z.literal('native'),
|
|
531
|
+
root_id: z.string().min(1).max(64),
|
|
532
|
+
executable: z.string().min(1).max(2048),
|
|
533
|
+
executable_sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
534
|
+
args: z.array(z.string().max(32768)).max(64).default([]),
|
|
535
|
+
cwd: z.string().min(1).max(512).default('.'),
|
|
536
|
+
timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
|
|
537
|
+
})
|
|
538
|
+
]),
|
|
518
539
|
outputSchema: hostExecOutputSchema
|
|
519
540
|
},
|
|
520
|
-
async
|
|
541
|
+
async args => {
|
|
521
542
|
try {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
543
|
+
let result;
|
|
544
|
+
if (args.mode === 'inline') {
|
|
545
|
+
result = await hostExecInline(hostPolicy, {
|
|
546
|
+
commandId: args.command_id,
|
|
547
|
+
rootId: args.root_id,
|
|
548
|
+
command: args.command,
|
|
549
|
+
cwd: args.cwd,
|
|
550
|
+
timeoutMs: args.timeout_ms
|
|
551
|
+
});
|
|
552
|
+
} else if (args.mode === 'native') {
|
|
553
|
+
result = await hostExecNative(hostPolicy, {
|
|
554
|
+
rootId: args.root_id,
|
|
555
|
+
executable: args.executable,
|
|
556
|
+
executableSha256: args.executable_sha256,
|
|
557
|
+
args: args.args,
|
|
558
|
+
cwd: args.cwd,
|
|
559
|
+
timeoutMs: args.timeout_ms
|
|
560
|
+
});
|
|
561
|
+
} else {
|
|
562
|
+
result = await hostExec(hostPolicy, {
|
|
563
|
+
commandId: args.command_id,
|
|
564
|
+
rootId: args.root_id,
|
|
565
|
+
scriptPath: args.script_path,
|
|
566
|
+
scriptSha256: args.script_sha256,
|
|
567
|
+
args: args.args,
|
|
568
|
+
cwd: args.cwd,
|
|
569
|
+
timeoutMs: args.timeout_ms
|
|
570
|
+
});
|
|
571
|
+
}
|
|
531
572
|
const failed = result.timed_out || result.exit_code !== 0;
|
|
532
|
-
return textResult(hostExecOutputSchema, { status: failed ? 'failed' : 'passed', root_id, ...result }, failed);
|
|
573
|
+
return textResult(hostExecOutputSchema, { status: failed ? 'failed' : 'passed', root_id: args.root_id, ...result }, failed);
|
|
533
574
|
} catch (error) {
|
|
534
575
|
return textResult(hostExecOutputSchema, { status: 'denied', error: String(error) }, true);
|
|
535
576
|
}
|
package/src/native-diff-v2.mjs
CHANGED
|
@@ -321,17 +321,22 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
|
|
|
321
321
|
const receipt=await createApplyReceipt(ctx,diffId,identity,preimage,events);return {status:'committed',diff_id:diffId,receipt_sha256:receipt.receipt_sha256,operation_count:identity.operations.length,operation_receipts:receipt.operation_receipts.map(x=>({index:x.index,kind:x.kind,receipt_class:x.receipt_class,receipt_id:x.receipt_id,receipt_sha256:x.receipt_sha256}))};
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
async function
|
|
324
|
+
async function verifyEvidence({diffId}){
|
|
325
325
|
try{
|
|
326
326
|
const {ctx,identity}=await loadDiff(diffId);const verifyKey=await loadPublicVerifyKey(verifyKeyPath);const preimage=await readEnvelope(ctx.preimagePath,verifyKey,PREIMAGE_DOMAIN,'native diff V2 preimage');const events=await readEvents(ctx);const receipt=await readEnvelope(ctx.receiptPath,verifyKey,RECEIPT_DOMAIN,'native diff V2 receipt');const {attestation:_a,receipt_sha256:claimed,...body}=receipt;
|
|
327
327
|
if(body.schema!==NATIVE_DIFF_V2_RECEIPT_SCHEMA||body.diff_id!==diffId||body.policy_sha256!==policy.policy_sha256||body.coding_policy_sha256!==policy.coding_policy_sha256||!SHA256_RE.test(claimed??'')||sha256Object(body)!==claimed)return {ok:false,reason:'native diff V2 receipt identity/hash mismatch'};
|
|
328
328
|
const completion=events.at(-1);if(completion?.type!=='transaction_completed'||completion.prev_sha256!==receipt.event_head_sha256||completion.payload?.receipt_sha256!==claimed||receipt.preimage_sha256!==preimage.preimage_sha256)return {ok:false,reason:'native diff V2 receipt/event/preimage binding mismatch'};
|
|
329
329
|
if(!Array.isArray(receipt.operation_receipts)||receipt.operation_receipts.length!==identity.operations.length)return {ok:false,reason:'native diff V2 operation receipt count mismatch'};
|
|
330
330
|
let verified=0,codingVerified=0;for(let index=0;index<identity.operations.length;index+=1){const op=identity.operations[index];const bound=receipt.operation_receipts.find(x=>x.index===index);if(!bound||bound.kind!==op.kind||bound.receipt_class!==receiptClass(op))return {ok:false,reason:`native diff V2 receipt binding mismatch index ${index}`};const checked=await verifyOperationReceipt(op,bound.receipt_id);if(!checked.ok||checked.receipt_sha256!==bound.receipt_sha256||checked.request_sha256!==bound.request_sha256)return {ok:false,reason:`native diff V2 operation receipt invalid index ${index}`};verified+=1;if(bound.receipt_class==='coding')codingVerified+=1;}
|
|
331
|
-
|
|
332
|
-
return {ok:true,diff_id:diffId,receipt_sha256:claimed,signature_valid:true,root_id:identity.root_id,operation_count:identity.operations.length,coding_receipts_verified:codingVerified,operation_receipts_verified:verified,final_states:receipt.final_states,current_state_matches:currentMatches,journal_head_sha256:completion.event_sha256};
|
|
331
|
+
return {ok:true,diff_id:diffId,receipt_sha256:claimed,signature_valid:true,root_id:identity.root_id,operation_count:identity.operations.length,coding_receipts_verified:codingVerified,operation_receipts_verified:verified,final_states:receipt.final_states,journal_head_sha256:completion.event_sha256};
|
|
333
332
|
}catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
|
|
334
333
|
}
|
|
335
334
|
|
|
336
|
-
|
|
335
|
+
async function verify({diffId}){
|
|
336
|
+
const checked=await verifyEvidence({diffId});if(!checked.ok)return checked;
|
|
337
|
+
try{const {identity}=await loadDiff(diffId);let currentMatches=true;for(const op of identity.operations)if(!(await terminalStateMatches(identity.root_id,op)))currentMatches=false;return {...checked,current_state_matches:currentMatches};}
|
|
338
|
+
catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return Object.freeze({preview,apply,verify,verifyEvidence,policy});
|
|
337
342
|
}
|
package/src/native-diff.mjs
CHANGED
|
@@ -361,7 +361,7 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
361
361
|
coding_receipts:receipt.coding_receipts.map(item => ({ index:item.index, path:item.path, receipt_id:item.receipt_id, receipt_sha256:item.receipt_sha256 })) };
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
async function
|
|
364
|
+
async function verifyEvidence({ diffId }) {
|
|
365
365
|
try {
|
|
366
366
|
const { ctx, identity } = await loadDiff(diffId);
|
|
367
367
|
const verifyKey = await loadPublicVerifyKey(verifyKeyPath);
|
|
@@ -390,21 +390,32 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
390
390
|
}
|
|
391
391
|
verifiedReceipts += 1;
|
|
392
392
|
}
|
|
393
|
+
return { ok:true, diff_id:diffId, receipt_sha256:claimed, signature_valid:true,
|
|
394
|
+
root_id:identity.root_id, operation_count:identity.operations.length,
|
|
395
|
+
coding_receipts_verified:verifiedReceipts, final_states:receipt.final_states,
|
|
396
|
+
journal_head_sha256:completion.event_sha256 };
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return { ok:false, reason:String(error).slice(0,2048) };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function verify({ diffId }) {
|
|
403
|
+
const checked = await verifyEvidence({ diffId });
|
|
404
|
+
if (!checked.ok) return checked;
|
|
405
|
+
try {
|
|
406
|
+
const { identity } = await loadDiff(diffId);
|
|
393
407
|
let currentMatches = true;
|
|
394
408
|
for (const op of identity.operations) {
|
|
395
409
|
const current = await codingRuntime.fileState({ rootId:identity.root_id, relativePath:op.path });
|
|
396
410
|
if (!stateMatches(current, op.after_sha256)) currentMatches = false;
|
|
397
411
|
}
|
|
398
|
-
return {
|
|
399
|
-
root_id:identity.root_id, operation_count:identity.operations.length,
|
|
400
|
-
coding_receipts_verified:verifiedReceipts, final_states:receipt.final_states,
|
|
401
|
-
current_state_matches:currentMatches, journal_head_sha256:completion.event_sha256 };
|
|
412
|
+
return { ...checked, current_state_matches:currentMatches };
|
|
402
413
|
} catch (error) {
|
|
403
414
|
return { ok:false, reason:String(error).slice(0,2048) };
|
|
404
415
|
}
|
|
405
416
|
}
|
|
406
417
|
|
|
407
|
-
return Object.freeze({ preview, apply, verify, policy });
|
|
418
|
+
return Object.freeze({ preview, apply, verify, verifyEvidence, policy });
|
|
408
419
|
}
|
|
409
420
|
|
|
410
421
|
export { NATIVE_DIFF_V2_SCHEMA };
|
|
@@ -448,5 +459,16 @@ export function createNativeDiffRuntime(options) {
|
|
|
448
459
|
return {ok:false,reason:String(error).slice(0,2048)};
|
|
449
460
|
}
|
|
450
461
|
}
|
|
451
|
-
|
|
462
|
+
async function verifyEvidence(args){
|
|
463
|
+
try {
|
|
464
|
+
const schema=await schemaFor(args?.diffId);
|
|
465
|
+
if(schema===NATIVE_DIFF_SCHEMA) return v1.verifyEvidence(args);
|
|
466
|
+
if(schema===NATIVE_DIFF_V2_SCHEMA){
|
|
467
|
+
if(!v2) return {ok:false,reason:'native diff V2 topology requires machine fs runtime'};
|
|
468
|
+
return v2.verifyEvidence(args);
|
|
469
|
+
}
|
|
470
|
+
return {ok:false,reason:`unsupported native diff schema: ${String(schema)}`};
|
|
471
|
+
}catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
|
|
472
|
+
}
|
|
473
|
+
return Object.freeze({preview,apply,verify,verifyEvidence,policy:options.policy});
|
|
452
474
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
2
|
import {
|
|
3
|
-
observationProcessListOutputSchema,observationSearchCancelOutputSchema,observationSearchPageOutputSchema,
|
|
3
|
+
observationHardwareQueryOutputSchema,observationProcessListOutputSchema,observationSearchCancelOutputSchema,observationSearchPageOutputSchema,
|
|
4
4
|
observationSearchStartOutputSchema,observationSearchStatusOutputSchema,observationStructuredResult,observationSystemStatusOutputSchema
|
|
5
5
|
} from './observation-output-contracts.mjs';
|
|
6
6
|
|
|
@@ -41,4 +41,8 @@ export function registerObservationTools(server,{runtime}){
|
|
|
41
41
|
description:'Read bounded timestamped system status with signed observation evidence. This tool cannot mutate or arm any authority.',
|
|
42
42
|
inputSchema:z.strictObject({}),outputSchema:observationSystemStatusOutputSchema
|
|
43
43
|
},async()=>{try{return ok(observationSystemStatusOutputSchema,await runtime.systemStatus());}catch(error){return fail(observationSystemStatusOutputSchema,error);}});
|
|
44
|
+
server.registerTool('machine_hardware_query',{
|
|
45
|
+
description:'Read one typed native hardware snapshot (CPU, GPU, memory, storage, network, or PCI) with signed observation evidence. No shell or Host authority is used.',
|
|
46
|
+
inputSchema:z.strictObject({query:z.enum(['cpu','gpu','memory','storage','network','pci'])}),outputSchema:observationHardwareQueryOutputSchema
|
|
47
|
+
},async args=>{try{return ok(observationHardwareQueryOutputSchema,await runtime.hardwareQuery({query:args.query}));}catch(error){return fail(observationHardwareQueryOutputSchema,error);}});
|
|
44
48
|
}
|
|
@@ -45,6 +45,14 @@ export const observationSystemStatusOutputSchema=statusContract({
|
|
|
45
45
|
...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
|
|
46
46
|
},['observed_at_utc','hostname','platform','architecture','uptime_seconds','total_memory_bytes','free_memory_bytes','cpu_count','load_average','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
|
|
47
47
|
|
|
48
|
+
export const observationHardwareQueryOutputSchema=statusContract({
|
|
49
|
+
observed_at_utc:z.string().min(1).optional(),
|
|
50
|
+
query:z.enum(['cpu','gpu','memory','storage','network','pci']).optional(),
|
|
51
|
+
data:z.record(z.string(),z.json()).optional(),
|
|
52
|
+
observer_sha256:hex64.optional(),
|
|
53
|
+
...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
|
|
54
|
+
},['observed_at_utc','query','data','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
|
|
55
|
+
|
|
48
56
|
export function observationStructuredResult(schema,value,isError=false){
|
|
49
57
|
return structuredToolResult(schema,value,isError);
|
|
50
58
|
}
|