deadbyte-mcp 0.15.0 → 0.15.2
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 +109 -64
- package/PROVENANCE.json +11 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +36 -20
- package/RELEASE.json +53 -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 +51 -3
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +14 -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/deferred-slot-operation-r46.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-r46.mjs +98 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-r46.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/gate-windows-r46.ps1 +56 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-r46.mjs +187 -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/scripts/windows-gate-evidence-r46.mjs +110 -0
- package/src/autonomous-runtime.mjs +12 -7
- package/src/capability-ledger.mjs +2 -0
- package/src/compact-gateway-mcp-tools.mjs +11 -7
- package/src/deadbyte-cli.mjs +24 -12
- package/src/doctor-r45.mjs +65 -0
- package/src/host-plane.mjs +133 -85
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs.mjs +63 -10
- package/src/mcp-server.mjs +68 -23
- package/src/mcp-smoke-client.mjs +4 -1
- 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 +57 -5
- package/src/observation-smoke-client.mjs +6 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-mcp-tools.mjs +18 -5
- package/src/process-output-contracts.mjs +20 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +99 -19
- package/src/process-smoke-client.mjs +67 -22
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +23 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/runtime-update.mjs +36 -8
- package/src/stdio-console-guard.mjs +24 -0
- 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-gateway-r36.test.mjs +28 -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/full-max-catalog-r46.test.mjs +92 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/observation-runtime.test.mjs +29 -2
- package/test/output-schema.test.mjs +1 -0
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-mcp-tools.test.mjs +23 -5
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +111 -1
- package/test/process-supervision.test.mjs +4 -2
- 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 +26 -11
- package/test/release-version.test.mjs +112 -46
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
|
@@ -57,19 +57,34 @@ function parseDeviceKey(spki){
|
|
|
57
57
|
insist(key.asymmetricKeyType==='ed25519','remote device key must be Ed25519');
|
|
58
58
|
return {deviceId:sha(der),spki};
|
|
59
59
|
}
|
|
60
|
-
function publicDevice(d){
|
|
60
|
+
function publicDevice(d,reachableTtlMs=Infinity){
|
|
61
|
+
const lastOk=typeof d.last_probe_ok_at_utc==='string'?Date.parse(d.last_probe_ok_at_utc):NaN;
|
|
62
|
+
const fresh=d.reachable===true&&(reachableTtlMs===Infinity||(Number.isFinite(lastOk)&&Date.now()-lastOk<=reachableTtlMs));
|
|
61
63
|
return {device_id:d.device_id,display_name:d.display_name,epoch:d.epoch,revoked:d.revoked,
|
|
62
|
-
reported_online:d.reported_online,reachable:
|
|
64
|
+
reported_online:d.reported_online,reachable:fresh,connection_id:d.connection_id,
|
|
65
|
+
last_probe_ok_at_utc:d.last_probe_ok_at_utc??null,updated_at_utc:d.updated_at_utc};
|
|
66
|
+
}
|
|
67
|
+
async function withTimeout(operation,timeoutMs,label){
|
|
68
|
+
let timer;
|
|
69
|
+
try{
|
|
70
|
+
return await Promise.race([
|
|
71
|
+
Promise.resolve().then(operation),
|
|
72
|
+
new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error(`${label} timed out after ${timeoutMs}ms`)),timeoutMs);timer.unref?.();})
|
|
73
|
+
]);
|
|
74
|
+
}finally{if(timer)clearTimeout(timer);}
|
|
63
75
|
}
|
|
64
76
|
function payloadHash({deviceId,epoch,capabilityId,payload}){
|
|
65
77
|
return sha(Buffer.from(canonicalJson({device_id:deviceId,epoch,capability_id:capabilityId,payload}),'utf8'));
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapability,onCredentialSuperseded=null}={}){
|
|
80
|
+
export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapability,onCredentialSuperseded=null,relayTimeoutMs=15000,probeRetries=1,reachableTtlMs=75000}={}){
|
|
69
81
|
insist(typeof stateDir==='string'&&path.isAbsolute(stateDir),'remote stateDir must be absolute');
|
|
70
82
|
insist(relay&&typeof relay.probe==='function'&&typeof relay.request==='function','remote relay contract invalid');
|
|
71
83
|
insist(typeof authorizeCapability==='function','remote capability authorizer required');
|
|
72
84
|
insist(onCredentialSuperseded===null||typeof onCredentialSuperseded==='function','remote credential supersede callback invalid');
|
|
85
|
+
insist(Number.isInteger(relayTimeoutMs)&&relayTimeoutMs>=100&&relayTimeoutMs<=120000,'remote relayTimeoutMs invalid');
|
|
86
|
+
insist(Number.isInteger(probeRetries)&&probeRetries>=0&&probeRetries<=3,'remote probeRetries invalid');
|
|
87
|
+
insist(Number.isInteger(reachableTtlMs)&&reachableTtlMs>=1000&&reachableTtlMs<=10*60*1000,'remote reachableTtlMs invalid');
|
|
73
88
|
await mkdir(stateDir,{recursive:true});const root=await realpath(stateDir);
|
|
74
89
|
const stateFile=path.join(root,'remote-devices.json'),credentialsDir=path.join(root,'credentials');
|
|
75
90
|
await mkdir(credentialsDir,{recursive:true});
|
|
@@ -79,6 +94,8 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
79
94
|
const run=lock.then(fn,fn);lock=run.catch(()=>{});return run;
|
|
80
95
|
};
|
|
81
96
|
const persist=()=>{state.updated_at_utc=now();return atomicJson(stateFile,state);};
|
|
97
|
+
const pub=d=>publicDevice(d,reachableTtlMs);
|
|
98
|
+
const fresh=d=>pub(d).reachable;
|
|
82
99
|
const getDevice=(deviceId,epoch=null)=>{
|
|
83
100
|
insist(HEX64.test(deviceId??''),'device id invalid');
|
|
84
101
|
const d=state.devices[deviceId];insist(d,'unknown remote device');
|
|
@@ -95,21 +112,21 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
95
112
|
if(existing){
|
|
96
113
|
insist(!existing.revoked,'remote device revoked');
|
|
97
114
|
insist(existing.public_key_spki_base64===spki,'remote device key drift');
|
|
98
|
-
return
|
|
115
|
+
return pub(existing);
|
|
99
116
|
}
|
|
100
117
|
const d={device_id:deviceId,public_key_spki_base64:spki,display_name:displayName,epoch:1,revoked:false,
|
|
101
118
|
reported_online:false,reachable:false,connection_id:null,last_probe_nonce:null,updated_at_utc:now()};
|
|
102
|
-
state.devices[deviceId]=d;await persist();return
|
|
119
|
+
state.devices[deviceId]=d;await persist();return pub(d);
|
|
103
120
|
});
|
|
104
121
|
}
|
|
105
122
|
async function status({deviceId}={}){
|
|
106
|
-
const d=getDevice(deviceId);return
|
|
123
|
+
const d=getDevice(deviceId);return pub(d);
|
|
107
124
|
}
|
|
108
125
|
async function reportOnline({deviceId,epoch,connectionId}={}){
|
|
109
126
|
insist(typeof connectionId==='string'&&connectionId.length>=1&&connectionId.length<=256,'connection id invalid');
|
|
110
127
|
return withLock(async()=>{
|
|
111
128
|
const d=getDevice(deviceId,epoch);d.reported_online=true;d.reachable=false;d.connection_id=connectionId;
|
|
112
|
-
d.last_probe_nonce=null;d.updated_at_utc=now();await persist();return
|
|
129
|
+
d.last_probe_nonce=null;d.updated_at_utc=now();await persist();return pub(d);
|
|
113
130
|
});
|
|
114
131
|
}
|
|
115
132
|
async function probe({deviceId,epoch}={}){
|
|
@@ -119,12 +136,25 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
119
136
|
const d=getDevice(deviceId,epoch);insist(d.reported_online&&d.connection_id,'remote device is not reported online');
|
|
120
137
|
connectionId=d.connection_id;d.reachable=false;d.last_probe_nonce=nonce;d.updated_at_utc=now();await persist();
|
|
121
138
|
});
|
|
122
|
-
|
|
139
|
+
let response=null,lastError=null;
|
|
140
|
+
for(let attempt=0;attempt<=probeRetries;attempt+=1){
|
|
141
|
+
try{
|
|
142
|
+
response=await withTimeout(
|
|
143
|
+
()=>relay.probe({device_id:deviceId,epoch,connection_id:connectionId,nonce}),
|
|
144
|
+
relayTimeoutMs,'remote readiness probe'
|
|
145
|
+
);
|
|
146
|
+
lastError=null;break;
|
|
147
|
+
}catch(error){lastError=error;}
|
|
148
|
+
}
|
|
149
|
+
if(lastError){
|
|
150
|
+
await withLock(async()=>{const d=getDevice(deviceId,epoch);if(d.connection_id===connectionId){d.reachable=false;d.updated_at_utc=now();await persist();}});
|
|
151
|
+
throw lastError;
|
|
152
|
+
}
|
|
123
153
|
return withLock(async()=>{
|
|
124
154
|
const d=getDevice(deviceId,epoch);
|
|
125
155
|
insist(response&&response.device_id===deviceId&&response.epoch===epoch&&response.nonce===nonce&&response.ready===true,'remote readiness probe acknowledgment invalid');
|
|
126
156
|
insist(d.connection_id===connectionId&&d.last_probe_nonce===nonce,'remote connection changed during readiness probe');
|
|
127
|
-
d.reachable=true;d.updated_at_utc=now();await persist();return {...
|
|
157
|
+
d.reachable=true;d.last_probe_ok_at_utc=now();d.updated_at_utc=now();await persist();return {...pub(d),probe_nonce:nonce};
|
|
128
158
|
});
|
|
129
159
|
}
|
|
130
160
|
async function dispatch({deviceId,epoch,capabilityId,payload={},requestId}={}){
|
|
@@ -141,7 +171,7 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
141
171
|
const allowed=await authorizeCapability({deviceId,epoch,capabilityId});
|
|
142
172
|
insist(allowed===true,'remote capability denied by local policy');
|
|
143
173
|
await withLock(async()=>{
|
|
144
|
-
const d=getDevice(deviceId,epoch);insist(d.reported_online&&d
|
|
174
|
+
const d=getDevice(deviceId,epoch);insist(d.reported_online&&fresh(d),'remote device not reachable or readiness probe is stale');
|
|
145
175
|
const again=state.requests[requestId];
|
|
146
176
|
if(again){insist(again.request_sha256===requestSha,'remote request id conflict');return;}
|
|
147
177
|
state.requests[requestId]={request_id:requestId,request_sha256:requestSha,device_id:deviceId,epoch,capability_id:capabilityId,
|
|
@@ -150,9 +180,17 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
150
180
|
});
|
|
151
181
|
if(state.requests[requestId].state!=='accepted')return {...structuredClone(state.requests[requestId]),replay:true};
|
|
152
182
|
let response=null;
|
|
153
|
-
try{
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
try{
|
|
184
|
+
response=await withTimeout(
|
|
185
|
+
()=>relay.request({device_id:deviceId,epoch,capability_id:capabilityId,payload,request_id:requestId,request_sha256:requestSha}),
|
|
186
|
+
relayTimeoutMs,'remote dispatch'
|
|
187
|
+
);
|
|
188
|
+
}catch(error){
|
|
189
|
+
return withLock(async()=>{
|
|
190
|
+
const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_error';rec.error=String(error?.message??error);rec.updated_at_utc=now();
|
|
191
|
+
const d=state.devices[deviceId];if(d&&d.epoch===epoch){d.reachable=false;d.updated_at_utc=now();}
|
|
192
|
+
await persist();return structuredClone(rec);
|
|
193
|
+
});
|
|
156
194
|
}
|
|
157
195
|
if(!response||response.ack!=='accepted'||response.request_id!==requestId){
|
|
158
196
|
return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_ack';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
|
|
@@ -172,7 +210,7 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
172
210
|
async function revoke({deviceId,epoch}={}){
|
|
173
211
|
return withLock(async()=>{
|
|
174
212
|
const d=getDevice(deviceId,epoch);d.revoked=true;d.reported_online=false;d.reachable=false;d.connection_id=null;d.updated_at_utc=now();await persist();
|
|
175
|
-
return {...
|
|
213
|
+
return {...pub(d),revoked:true};
|
|
176
214
|
});
|
|
177
215
|
}
|
|
178
216
|
async function rotateCredential({deviceId,epoch,encryptedBundleBase64,expectedPreviousSha256}={}){
|
|
@@ -200,6 +238,6 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
200
238
|
}
|
|
201
239
|
return {status:'committed',device_id:deviceId,epoch,credential_sha256:credentialSha,previous_credential_sha256:previous?.credential_sha256??null};
|
|
202
240
|
}
|
|
203
|
-
async function list(){return {status:'ok',devices:Object.values(state.devices).map(
|
|
241
|
+
async function list(){return {status:'ok',devices:Object.values(state.devices).map(pub).sort((a,b)=>a.device_id.localeCompare(b.device_id)),requests:structuredClone(state.requests)};}
|
|
204
242
|
return Object.freeze({pair,status,reportOnline,probe,dispatch,revoke,rotateCredential,list,state_file:stateFile});
|
|
205
243
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { 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 { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
7
|
+
import { R34_EXPECTED_TOOLS } from './remote-mcp-r34-parity-client.mjs';
|
|
8
|
+
import { loadCodingPolicy } from './coding-plane.mjs';
|
|
9
|
+
import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
|
|
10
|
+
import { verifyObservationReceiptFile } from './observation-runtime.mjs';
|
|
11
|
+
import { loadDesktopPolicy } from './desktop-policy-r40.mjs';
|
|
12
|
+
import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
|
|
13
|
+
import { loadPublicVerifyKey } from './trust.mjs';
|
|
14
|
+
|
|
15
|
+
export const R45_EXPECTED_TOOLS=Object.freeze([...new Set([
|
|
16
|
+
...R34_EXPECTED_TOOLS,
|
|
17
|
+
'coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_rename_preview',
|
|
18
|
+
'desktop_status','desktop_observe','desktop_target','desktop_action','skill_catalog','machine_hardware_query'
|
|
19
|
+
])].sort());
|
|
20
|
+
if(R45_EXPECTED_TOOLS.length !== 82) throw new Error(`R45 full catalog identity mismatch: ${R45_EXPECTED_TOOLS.length}`);
|
|
21
|
+
export const R45_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session','desktop_control'].sort());
|
|
22
|
+
export const R45_COMPACT_TOOL_LIMIT=43;
|
|
23
|
+
|
|
24
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const repoRoot=path.resolve(here,'..');
|
|
26
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
27
|
+
const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
|
|
28
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
29
|
+
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
30
|
+
|
|
31
|
+
function structured(result,label){
|
|
32
|
+
if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
|
|
33
|
+
if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
|
|
34
|
+
return result.structuredContent;
|
|
35
|
+
}
|
|
36
|
+
function gateway(result,label){
|
|
37
|
+
const value=structured(result,label);
|
|
38
|
+
if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)} error=${String(value.error??'')}`);
|
|
39
|
+
return value.result;
|
|
40
|
+
}
|
|
41
|
+
async function fileSha(relative){return sha256(await readFile(path.join(repoRoot,...relative.split('/'))));}
|
|
42
|
+
async function verifyMachineReceipt(policy,receiptId,operation){
|
|
43
|
+
const checked=await verifyMachineFsReceiptFile({policy,verifyKeyPath,receiptId});
|
|
44
|
+
if(!checked.ok||checked.signature_valid!==true||checked.operation!==operation) throw new Error(`machine-fs receipt verification failed for ${operation}`);
|
|
45
|
+
return checked;
|
|
46
|
+
}
|
|
47
|
+
async function verifyObservationReceipt(policy,receiptId){
|
|
48
|
+
const checked=await verifyObservationReceiptFile({policy,verifyKeyPath,receiptId});
|
|
49
|
+
if(!checked.ok||checked.signature_valid!==true) throw new Error('observation receipt verification failed');
|
|
50
|
+
return checked;
|
|
51
|
+
}
|
|
52
|
+
async function verifyDesktopReceipt(policy,verifyKey,receiptId,receiptSha256,kind){
|
|
53
|
+
const file=path.join(policy.evidence_dir,`${receiptId}.json`);
|
|
54
|
+
const receipt=JSON.parse(await readFile(file,'utf8'));
|
|
55
|
+
const checked=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:verifyKey.publicKey,keyId:verifyKey.keyId});
|
|
56
|
+
if(!checked.ok||checked.receipt_sha256!==receiptSha256||checked.kind!==kind) throw new Error(`desktop receipt verification failed for ${kind}`);
|
|
57
|
+
return checked;
|
|
58
|
+
}
|
|
59
|
+
async function waitSearch(client,searchId){
|
|
60
|
+
for(let attempt=0;attempt<200;attempt+=1){
|
|
61
|
+
const status=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_status',search_id:searchId}}),'search_status');
|
|
62
|
+
if(['completed','cancelled','failed'].includes(status.state)) return status;
|
|
63
|
+
await sleep(25);
|
|
64
|
+
}
|
|
65
|
+
throw new Error('R45 compact search timeout');
|
|
66
|
+
}
|
|
67
|
+
async function waitProcessOutput(client,sessionId){
|
|
68
|
+
for(let attempt=0;attempt<100;attempt+=1){
|
|
69
|
+
const page=gateway(await client.callTool({name:'process_session',arguments:{op:'read',session_id:sessionId,offset:0,max_bytes:65536}}),'process read');
|
|
70
|
+
if(Buffer.from(page.data_base64??'','base64').toString('utf8').includes('DEADBYTE_ACTION_PING')) return page;
|
|
71
|
+
await sleep(50);
|
|
72
|
+
}
|
|
73
|
+
throw new Error('R45 compact process output timeout');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runR45CompactParity(endpointText){
|
|
77
|
+
const endpoint=new URL(endpointText);
|
|
78
|
+
if(endpoint.protocol!=='https:') throw new Error('R45 compact parity requires HTTPS');
|
|
79
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
80
|
+
const policy=await loadCodingPolicy(path.join(pointer.generation_root,'coding-policy.json'));
|
|
81
|
+
const desktopPolicy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
|
|
82
|
+
const desktopVerifyKey=await loadPublicVerifyKey(verifyKeyPath);
|
|
83
|
+
const client=new Client({name:'deadbyte-r45-compact-parity',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
84
|
+
const transport=new StreamableHTTPClientTransport(endpoint);
|
|
85
|
+
const tag=randomBytes(8).toString('hex');
|
|
86
|
+
const scratchRoot='parity_scratch';
|
|
87
|
+
const target=`.deadbyte-r45-parity-${tag}.txt`;
|
|
88
|
+
let processSession=null,targetPresent=false;
|
|
89
|
+
const machineReceipts=[],observationReceipts=[];
|
|
90
|
+
try{
|
|
91
|
+
await client.connect(transport);
|
|
92
|
+
const listed=await client.listTools();
|
|
93
|
+
const names=listed.tools.map(tool=>tool.name).sort();
|
|
94
|
+
if(names.length>R45_COMPACT_TOOL_LIMIT) throw new Error(`R45 compact catalog exceeds ${R45_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
95
|
+
if(names.length!==R45_COMPACT_TOOL_LIMIT) throw new Error(`R45 compact catalog must be exactly ${R45_COMPACT_TOOL_LIMIT}, got ${names.length}`);
|
|
96
|
+
for(const name of R45_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R45 compact gateway missing ${name}`);
|
|
97
|
+
for(const tool of listed.tools){
|
|
98
|
+
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
99
|
+
}
|
|
100
|
+
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
101
|
+
if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R45 compact capability manifest malformed');
|
|
102
|
+
if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
|
|
103
|
+
manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R45 compact manifest catalog binding mismatch');
|
|
104
|
+
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
105
|
+
for(const item of required){
|
|
106
|
+
if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact capability route missing: ${String(item.id)}`);
|
|
107
|
+
}
|
|
108
|
+
const skillCatalog=structured(await client.callTool({name:'skill_catalog',arguments:{op:'list'}}),'skill_catalog');
|
|
109
|
+
if(skillCatalog.status!=='ok'||skillCatalog.op!=='list'||!skillCatalog.result||!Array.isArray(skillCatalog.result.skills)||
|
|
110
|
+
!/^[0-9a-f]{64}$/.test(String(skillCatalog.result.registry_sha256??''))) throw new Error('R45 skill catalog verification failed');
|
|
111
|
+
const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
112
|
+
const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
|
|
113
|
+
if(coding.armed!==true||autonomous.coding_armed!==true||autonomous.autonomous_armed!==true) throw new Error('R45 compact parity requires coding and autonomous standing grants');
|
|
114
|
+
const desktopStatus=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
|
|
115
|
+
if(desktopStatus.configured!==true||desktopStatus.armed!==false) throw new Error('R45 compact parity requires Desktop configured and DISARMED');
|
|
116
|
+
const desktopObserved=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
|
|
117
|
+
await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopObserved.receipt_id,desktopObserved.receipt_sha256,'desktop_observation');
|
|
118
|
+
const desktopTarget=gateway(await client.callTool({name:'desktop_control',arguments:{op:'target',frame_id:desktopObserved.frame.frame_id,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}}),'desktop target');
|
|
119
|
+
await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopTarget.grounding.receipt_id,desktopTarget.grounding.receipt_sha256,'desktop_grounding');
|
|
120
|
+
if(desktopTarget.target?.source!=='uia') throw new Error('R45 compact UIA root grounding missing');
|
|
121
|
+
const tree=structured(await client.callTool({name:'coding_tree',arguments:{root_id:'core',path:'workspaces/demo',max_depth:2,max_entries:128}}),'coding_tree');
|
|
122
|
+
if(tree.status!=='ok'||!Array.isArray(tree.entries)) throw new Error('coding_tree malformed');
|
|
123
|
+
const stat=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'stat',root_id:'core',path:'package.json',hash:true}}),'coding stat');
|
|
124
|
+
if(stat.type!=='file'||!/^[0-9a-f]{64}$/.test(String(stat.sha256??''))||!/^[0-9a-f]{64}$/.test(String(stat.observation_sha256??''))) throw new Error('coding stat identity missing');
|
|
125
|
+
const search=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'search',root_id:'core',path:'src',pattern:'R36_CAPABILITY_LEDGER',target:'content',mode:'literal',case_sensitive:true,max_results:20,max_file_bytes:262144,max_scan_bytes:4194304,page_size:20,cursor:null}}),'coding search');
|
|
126
|
+
if(!Array.isArray(search.matches)||search.matches.length<1) throw new Error('coding search returned no canonical ledger match');
|
|
127
|
+
|
|
128
|
+
const buildScript='scripts/autonomous-build.ps1';
|
|
129
|
+
const testScript='scripts/release-parity-tests-r45.ps1';
|
|
130
|
+
const build=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:buildScript,script_sha256:await fileSha(buildScript),args:[],cwd:'.',timeout_ms:120000}},{timeout:180000,maxTotalTimeout:180000}),'coding build');
|
|
131
|
+
if(build.status!=='passed'||build.exit_code!==0) throw new Error('R45 coding build failed');
|
|
132
|
+
const buildVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:build.receipt_id}}),'coding build receipt');
|
|
133
|
+
if(buildVerify.ok!==true||buildVerify.signature_valid!==true) throw new Error('R45 coding build receipt invalid');
|
|
134
|
+
const tests=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:testScript,script_sha256:await fileSha(testScript),args:[],cwd:'.',timeout_ms:300000}},{timeout:360000,maxTotalTimeout:360000}),'coding tests');
|
|
135
|
+
if(tests.status!=='passed'||tests.exit_code!==0) throw new Error('R45 coding tests failed');
|
|
136
|
+
const testVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:tests.receipt_id}}),'coding test receipt');
|
|
137
|
+
if(testVerify.ok!==true||testVerify.signature_valid!==true) throw new Error('R45 coding test receipt invalid');
|
|
138
|
+
|
|
139
|
+
const initial=`needle-${tag}\n`;
|
|
140
|
+
const write=gateway(await client.callTool({name:'machine_fs',arguments:{op:'write',root_id:scratchRoot,path:target,content:initial,encoding:'utf8',expected_sha256:'absent'}}),'machine_fs write');
|
|
141
|
+
targetPresent=true;machineReceipts.push(write.receipt_id);await verifyMachineReceipt(policy,write.receipt_id,'machine_fs_write');
|
|
142
|
+
const bounded=gateway(await client.callTool({name:'machine_fs',arguments:{op:'read',root_id:scratchRoot,path:target,offset:0,max_bytes:128,hash_file:true}}),'machine_fs read');
|
|
143
|
+
if(Buffer.from(bounded.data_base64,'base64').toString('utf8')!==initial||bounded.file_sha256!==write.sha256) throw new Error('R45 bounded machine read mismatch');
|
|
144
|
+
const edit=gateway(await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'needle',new_string:'beta',expected_replacements:1}}),'machine_fs edit');
|
|
145
|
+
machineReceipts.push(edit.receipt_id);await verifyMachineReceipt(policy,edit.receipt_id,'machine_fs_edit_exact');
|
|
146
|
+
const stale=await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'beta',new_string:'bad',expected_replacements:1}});
|
|
147
|
+
const staleValue=stale.structuredContent;
|
|
148
|
+
if(!staleValue||typeof staleValue!=='object') throw new Error('R45 stale machine-fs denial missing structured output');
|
|
149
|
+
if(stale.isError!==true||staleValue.status!=='denied'||!/precondition mismatch/i.test(String(staleValue.error??''))) {
|
|
150
|
+
throw new Error(`R45 stale machine-fs precondition was not denied: ${JSON.stringify(staleValue)}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const searchStart=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_start',root_id:scratchRoot,path:'.',pattern:tag,case_sensitive:true,max_results:20,max_files:200,max_bytes:262144,max_file_bytes:16384}}),'observe search start');
|
|
154
|
+
const searchStatus=await waitSearch(client,searchStart.search_id);
|
|
155
|
+
if(searchStatus.state!=='completed'||!searchStatus.receipt_id) throw new Error('R45 compact observation search failed');
|
|
156
|
+
observationReceipts.push(searchStatus.receipt_id);await verifyObservationReceipt(policy,searchStatus.receipt_id);
|
|
157
|
+
const page=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_page',search_id:searchStart.search_id,limit:20,cursor:null}}),'observe search page');
|
|
158
|
+
if(!page.results.some(item=>item.path===target)) throw new Error('R45 compact search did not return parity file');
|
|
159
|
+
const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');
|
|
160
|
+
observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);
|
|
161
|
+
const hardware=gateway(await client.callTool({name:'machine_observe',arguments:{op:'hardware_query',query:'cpu'}}),'hardware query');
|
|
162
|
+
if(hardware.query!=='cpu'||!hardware.data||typeof hardware.data!=='object'||!/^[0-9a-f]{64}$/.test(String(hardware.observer_sha256??''))) throw new Error('R45 native hardware observation malformed');
|
|
163
|
+
observationReceipts.push(hardware.receipt_id);await verifyObservationReceipt(policy,hardware.receipt_id);
|
|
164
|
+
const machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
|
|
165
|
+
if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R45 compact machine status release mismatch');
|
|
166
|
+
|
|
167
|
+
const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
|
|
168
|
+
processSession=started.session_id??started.session?.session_id;
|
|
169
|
+
if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R45 compact process session id missing');
|
|
170
|
+
const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
|
|
171
|
+
const processRead=await waitProcessOutput(client,processSession);
|
|
172
|
+
const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
|
|
173
|
+
const terminalState=terminated.state??terminated.session?.state;
|
|
174
|
+
if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R45 compact process lifecycle failed');
|
|
175
|
+
|
|
176
|
+
const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
|
|
177
|
+
const deleted=gateway(await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}),'machine_fs delete');
|
|
178
|
+
machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
|
|
179
|
+
|
|
180
|
+
return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
|
|
181
|
+
catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
|
|
182
|
+
capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
|
|
183
|
+
gateway_count:R45_COMPACT_GATEWAYS.length,skill_catalog_verified:true,skill_count:skillCatalog.result.skills.length,tree_entries:tree.entries.length,coding_stat_verified:true,coding_search_result_count:search.matches.length,
|
|
184
|
+
desktop_armed:false,desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
|
|
185
|
+
desktop_frame_id:desktopObserved.frame.frame_id,desktop_target_id:desktopTarget.target.target_id,
|
|
186
|
+
machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
|
|
187
|
+
observation_search_result_count:page.results.length,native_hardware_query_verified:true,hardware_observer_sha256:hardware.observer_sha256,process_session_id:processSession,process_output_bytes:processRead.stream_head,
|
|
188
|
+
process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
|
|
189
|
+
build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
|
|
190
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
191
|
+
} finally {
|
|
192
|
+
if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
|
|
193
|
+
if(targetPresent){
|
|
194
|
+
try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
|
|
195
|
+
if(fsStat.identity_sha256) await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}).catch(()=>{});
|
|
196
|
+
}catch{}
|
|
197
|
+
}
|
|
198
|
+
await client.close().catch(()=>{});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
203
|
+
const endpoint=process.argv[2];
|
|
204
|
+
if(!endpoint) throw new Error('usage: node src/remote-mcp-r45-parity-client.mjs https://host/mcp/path');
|
|
205
|
+
runR45CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
206
|
+
}
|
|
@@ -13,7 +13,7 @@ const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
|
|
|
13
13
|
const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
|
|
14
14
|
const processTools = [
|
|
15
15
|
'process_session_action', 'process_session_list', 'process_session_read', 'process_session_start',
|
|
16
|
-
'process_session_status', 'process_session_terminate'
|
|
16
|
+
'process_session_status', 'process_session_terminate', 'process_session_write'
|
|
17
17
|
];
|
|
18
18
|
const machineFsTools = [
|
|
19
19
|
'machine_fs_delete', 'machine_fs_edit_exact', 'machine_fs_list', 'machine_fs_mkdir',
|
package/src/runtime-update.mjs
CHANGED
|
@@ -45,14 +45,17 @@ function normalizePersistentPublicOrigin(value){
|
|
|
45
45
|
return publicOrigin.origin;
|
|
46
46
|
}
|
|
47
47
|
export function parseUpdateArgs(argv){
|
|
48
|
-
const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
-
const cloud={},bridge={},processPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
48
|
+
const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,hostPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
+
const cloud={},bridge={},processPolicy={},hostPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
50
50
|
const bridgeFlags=new Map([
|
|
51
51
|
['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
|
|
52
52
|
]);
|
|
53
53
|
const processPolicyFlags=new Map([
|
|
54
54
|
['--process-policy','path'],['--process-policy-sha256','sha256']
|
|
55
55
|
]);
|
|
56
|
+
const hostPolicyFlags=new Map([
|
|
57
|
+
['--host-policy','path'],['--host-policy-sha256','sha256']
|
|
58
|
+
]);
|
|
56
59
|
const desktopPolicyFlags=new Map([
|
|
57
60
|
['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
|
|
58
61
|
]);
|
|
@@ -64,7 +67,7 @@ export function parseUpdateArgs(argv){
|
|
|
64
67
|
['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
|
|
65
68
|
['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
|
|
66
69
|
]);
|
|
67
|
-
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
70
|
+
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...hostPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
68
71
|
for(let index=0;index<argv.length;index++){
|
|
69
72
|
const arg=argv[index];
|
|
70
73
|
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
|
|
@@ -99,6 +102,7 @@ export function parseUpdateArgs(argv){
|
|
|
99
102
|
else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
|
|
100
103
|
else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
|
|
101
104
|
else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
|
|
105
|
+
else if(hostPolicyFlags.has(arg)) hostPolicy[hostPolicyFlags.get(arg)]=value;
|
|
102
106
|
else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
|
|
103
107
|
else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
|
|
104
108
|
else cloud[cloudFlags.get(arg)]=value;
|
|
@@ -121,6 +125,13 @@ export function parseUpdateArgs(argv){
|
|
|
121
125
|
if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
|
|
122
126
|
out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
|
|
123
127
|
}
|
|
128
|
+
const hostPolicyFields=[...hostPolicyFlags.values()],hostPolicyPresent=hostPolicyFields.filter(key=>hostPolicy[key]!==undefined);
|
|
129
|
+
if(hostPolicyPresent.length!==0&&hostPolicyPresent.length!==hostPolicyFields.length) throw new Error('host policy bootstrap requires path plus SHA-256 as one pair');
|
|
130
|
+
if(hostPolicyPresent.length===hostPolicyFields.length){
|
|
131
|
+
const sha256=String(hostPolicy.sha256).toLowerCase();
|
|
132
|
+
if(!HEX64.test(sha256)) throw new Error('host policy SHA-256 must be lowercase hex');
|
|
133
|
+
out.hostPolicyBootstrap={path:path.resolve(hostPolicy.path),sha256};
|
|
134
|
+
}
|
|
124
135
|
const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
|
|
125
136
|
if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
|
|
126
137
|
if(desktopPolicyPresent.length===desktopPolicyFields.length){
|
|
@@ -333,6 +344,20 @@ export async function prepareProcessPolicyBootstrap(bootstrap){
|
|
|
333
344
|
return {...pinned,policy};
|
|
334
345
|
}
|
|
335
346
|
|
|
347
|
+
export async function prepareHostPolicyBootstrap(bootstrap){
|
|
348
|
+
if(!bootstrap) return null;
|
|
349
|
+
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'host policy');
|
|
350
|
+
const policy=await readJson(pinned.path);
|
|
351
|
+
if(!['deadbyte.host-policy.v1','deadbyte.host-policy.v2'].includes(policy?.schema)||policy?.enabled!==true||
|
|
352
|
+
!policy?.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
|
|
353
|
+
throw new Error('host policy schema/enabled/commands mismatch');
|
|
354
|
+
}
|
|
355
|
+
if(policy.schema==='deadbyte.host-policy.v2'&&(policy.native_exec?.enabled!==true||!Array.isArray(policy.native_exec?.root_ids))) {
|
|
356
|
+
throw new Error('host policy v2 native_exec contract invalid');
|
|
357
|
+
}
|
|
358
|
+
return {...pinned,policy};
|
|
359
|
+
}
|
|
360
|
+
|
|
336
361
|
export async function prepareDesktopPolicyBootstrap(bootstrap){
|
|
337
362
|
if(!bootstrap) return null;
|
|
338
363
|
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
|
|
@@ -358,7 +383,8 @@ export async function prepareSemanticLspConfigBootstrap(bootstrap){
|
|
|
358
383
|
|
|
359
384
|
async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={}){
|
|
360
385
|
if(!policy) return {policy:null,repinned:[],matchedCommandIds:[]};
|
|
361
|
-
|
|
386
|
+
const acceptedSchemas=Array.isArray(schema)?schema:[schema];
|
|
387
|
+
if(!acceptedSchemas.includes(policy.schema)||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
|
|
362
388
|
throw new Error(`${label} policy schema/enabled/commands mismatch`);
|
|
363
389
|
}
|
|
364
390
|
const requested=new Map();
|
|
@@ -390,7 +416,7 @@ async function preparePinnedCommandPolicy(policy,{commandPins=[],schema,label}={
|
|
|
390
416
|
|
|
391
417
|
export async function prepareHostPolicy(policy,{commandPins=[]}={}){
|
|
392
418
|
if(!policy&&commandPins.length) throw new Error('host command pins supplied but host policy is not configured');
|
|
393
|
-
const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:'deadbyte.host-policy.v1',label:'host'});
|
|
419
|
+
const result=await preparePinnedCommandPolicy(policy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
|
|
394
420
|
const matched=new Set(result.matchedCommandIds);
|
|
395
421
|
const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
|
|
396
422
|
if(unknown) throw new Error(`unknown host command pin '${unknown.commandId}'`);
|
|
@@ -398,7 +424,7 @@ export async function prepareHostPolicy(policy,{commandPins=[]}={}){
|
|
|
398
424
|
}
|
|
399
425
|
|
|
400
426
|
export async function prepareAuthorityPolicies({hostPolicy,codingPolicy},{commandPins=[]}={}){
|
|
401
|
-
const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:'deadbyte.host-policy.v1',label:'host'});
|
|
427
|
+
const host=await preparePinnedCommandPolicy(hostPolicy,{commandPins,schema:['deadbyte.host-policy.v1','deadbyte.host-policy.v2'],label:'host'});
|
|
402
428
|
const coding=await preparePinnedCommandPolicy(codingPolicy,{commandPins,schema:'deadbyte.coding-policy.v1',label:'coding'});
|
|
403
429
|
const matched=new Set([...host.matchedCommandIds,...coding.matchedCommandIds]);
|
|
404
430
|
const unknown=commandPins.find(pin=>!matched.has(String(pin?.commandId??'')));
|
|
@@ -862,7 +888,7 @@ function publicPointer(pointer){
|
|
|
862
888
|
release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
863
889
|
launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
|
|
864
890
|
}
|
|
865
|
-
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
891
|
+
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
866
892
|
if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
|
|
867
893
|
const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
|
|
868
894
|
const key=await loadUpdateKey(state); const updateId=`r27-${expectedManifest.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
@@ -882,6 +908,8 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
882
908
|
const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
|
|
883
909
|
const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
|
|
884
910
|
control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo});
|
|
911
|
+
const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);
|
|
912
|
+
if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
|
|
885
913
|
const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
|
|
886
914
|
if(processBootstrap){
|
|
887
915
|
control.processPolicy=processBootstrap.policy;
|
|
@@ -1113,7 +1141,7 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
|
|
|
1113
1141
|
const args=parseUpdateArgs(argv);
|
|
1114
1142
|
const result=await performSlotUpdate({stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,
|
|
1115
1143
|
expectedManifest:args.expectedManifest,sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
|
|
1116
|
-
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1144
|
+
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,hostPolicyBootstrap:args.hostPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1117
1145
|
enableObservation:args.enableObservation,enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
|
|
1118
1146
|
console.log(`R27_SLOT_UPDATE_PASS version=${args.expectedVersion}`);
|
|
1119
1147
|
console.log(`UPDATE_ID ${result.update_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { format } from 'node:util';
|
|
2
|
+
|
|
3
|
+
export function installStdioConsoleGuard({ stderr = process.stderr } = {}) {
|
|
4
|
+
if (!stderr || typeof stderr.write !== 'function') throw new Error('stdio console guard requires writable stderr');
|
|
5
|
+
const original = {
|
|
6
|
+
log: console.log,
|
|
7
|
+
info: console.info,
|
|
8
|
+
debug: console.debug
|
|
9
|
+
};
|
|
10
|
+
const write = (level, args) => {
|
|
11
|
+
const text = format(...args);
|
|
12
|
+
stderr.write(`DEADBYTE MCP ${level.toUpperCase()} ${text}${text.endsWith('\n') ? '' : '\n'}`);
|
|
13
|
+
};
|
|
14
|
+
console.log = (...args) => write('log', args);
|
|
15
|
+
console.info = (...args) => write('info', args);
|
|
16
|
+
console.debug = (...args) => write('debug', args);
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
restore() {
|
|
19
|
+
console.log = original.log;
|
|
20
|
+
console.info = original.info;
|
|
21
|
+
console.debug = original.debug;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
package/src/version.mjs
CHANGED
|
@@ -147,6 +147,30 @@ test('R24 autonomous goal diagnoses a failing test, replans with native diff, be
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
test('historical autonomous release verification survives source drift and disarmed mutation authority', async () => {
|
|
151
|
+
const f = await fixture();
|
|
152
|
+
try {
|
|
153
|
+
const submitted = await f.runtime.submit({ submissionId:'historical-release-verify', rootId:'project',
|
|
154
|
+
title:'repair program', goal:'make exported value equal fixed', acceptance:'release evidence must remain verifiable',
|
|
155
|
+
maxIterations:10 });
|
|
156
|
+
const result = await f.runtime.run({ goalId:submitted.goal_id, maxCycles:16 });
|
|
157
|
+
assert.equal(result.status,'completed');
|
|
158
|
+
assert.equal((await f.runtime.verify({ goalId:submitted.goal_id })).ok,true);
|
|
159
|
+
|
|
160
|
+
await writeFile(f.program,"export const value = 'rolled-back';\n");
|
|
161
|
+
await rm(f.codingRuntime.policy.lease_file);
|
|
162
|
+
await rm(f.policy.lease_file);
|
|
163
|
+
|
|
164
|
+
const verified = await f.runtime.verify({ goalId:submitted.goal_id });
|
|
165
|
+
assert.equal(verified.ok,true);
|
|
166
|
+
assert.equal(verified.native_diffs_verified,2);
|
|
167
|
+
assert.equal(verified.required_profiles_verified,3);
|
|
168
|
+
} finally {
|
|
169
|
+
await new Promise(resolve => f.planner.server.close(resolve));
|
|
170
|
+
await rm(f.root,{recursive:true,force:true});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
150
174
|
test('autonomous profile execution is capped by the absolute autonomous lease without changing coding request identity', async () => {
|
|
151
175
|
const calls=[];
|
|
152
176
|
const f=await fixture([],{
|
|
@@ -68,6 +68,7 @@ async function fixture() {
|
|
|
68
68
|
command:process.execPath, args:[serverPath], cwd:repoRoot,
|
|
69
69
|
env:{ ...getDefaultEnvironment(), DEADBYTE_WORKSPACE_ROOT:workspaceRoot,
|
|
70
70
|
DEADBYTE_SIGNING_KEY:keys.privateKeyPath, DEADBYTE_VERIFY_KEY:keys.publicKeyPath,
|
|
71
|
+
DEADBYTE_PRIVILEGED_POLICY:'',
|
|
71
72
|
DEADBYTE_AGENT_RUNTIME:'1', DEADBYTE_CODING_RUNTIME:'1',
|
|
72
73
|
DEADBYTE_CODING_POLICY:codingPolicyPath, DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
73
74
|
DEADBYTE_AUTONOMOUS_POLICY:autonomousPolicyPath }, stderr:'pipe'
|
|
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
|
|
|
21
21
|
for(const arg of ['--version','-v','version']){
|
|
22
22
|
const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
|
|
23
23
|
assert.equal(result.status,0,result.stderr);
|
|
24
|
-
assert.equal(result.stdout.trim(),'0.15.
|
|
24
|
+
assert.equal(result.stdout.trim(),'0.15.2');
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -45,11 +45,11 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
|
|
|
45
45
|
assert.match(source,/supervisorrecover/);
|
|
46
46
|
const mapping=source.match(/const SUPERVISOR_COMMANDS=.*?;\n/s)?.[0]??'';
|
|
47
47
|
assert.doesNotMatch(mapping,/arm(?:coding|autonomous|process|host)/i);
|
|
48
|
-
assert.match(source,/npx
|
|
48
|
+
assert.match(source,/npx -y \$\{exact\} supervisor status/);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
test('README makes npx the primary operator UX',async()=>{
|
|
52
52
|
const readme=await readFile(path.join(root,'README.txt'),'utf8');
|
|
53
|
-
assert.match(readme,/npx deadbyte-mcp@
|
|
54
|
-
assert.match(readme,/npx deadbyte-mcp@
|
|
53
|
+
assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 remote/);
|
|
54
|
+
assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 audit verify/);
|
|
55
55
|
});
|
|
@@ -60,6 +60,7 @@ async function fixture() {
|
|
|
60
60
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
61
61
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
62
62
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
|
63
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
63
64
|
DEADBYTE_AGENT_RUNTIME: '1',
|
|
64
65
|
DEADBYTE_CODING_RUNTIME: '1',
|
|
65
66
|
DEADBYTE_CODING_POLICY: policyPath
|