deadbyte-mcp 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -18
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +156 -70
- package/PROVENANCE.json +10 -0
- package/R43-PARENT.json +20 -0
- package/R44-DONORS.json +99 -0
- package/R44-PARENT.json +18 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +19 -14
- package/RELEASE.json +37 -0
- package/SBOM.cdx.json +46 -0
- package/TEST-MATRIX.json +31 -0
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/controller/README.TXT +5 -6
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/R44-ARCHITECTURE.md +37 -0
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +18 -8
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r43.mjs +126 -0
- package/scripts/deferred-slot-operation-r44.mjs +126 -0
- package/scripts/deferred-slot-operation-r45.mjs +126 -0
- package/scripts/derive-r45-parent.mjs +24 -0
- package/scripts/derive-r45-release.mjs +72 -0
- package/scripts/derive-r45-tests.mjs +20 -0
- package/scripts/final-closure-r43.mjs +115 -0
- package/scripts/final-closure-r44.mjs +121 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-verify-r43.mjs +213 -0
- package/scripts/final-closure-verify-r44.mjs +220 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r43.mjs +217 -0
- package/scripts/finalize-r44.mjs +217 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r43.mjs +20 -0
- package/scripts/full-regression-r44.mjs +20 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r43.ps1 +36 -0
- package/scripts/gate-windows-r44.ps1 +36 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r43.mjs +218 -0
- package/scripts/release-parity-r44.mjs +242 -0
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-tests-r43.ps1 +8 -0
- package/scripts/release-parity-tests-r44.ps1 +8 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r43-parent.mjs +126 -0
- package/scripts/verify-r44-parent.mjs +142 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r43.mjs +55 -0
- package/scripts/windows-gate-evidence-r44.mjs +55 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/agent-backend-r44.mjs +254 -0
- package/src/autonomous-output-contracts.mjs +1 -1
- package/src/autonomous-policy.mjs +41 -4
- package/src/autonomous-runtime.mjs +37 -19
- package/src/autonomous-supervisor-host.mjs +12 -2
- package/src/capability-ledger.mjs +3 -1
- package/src/codex-app-server-r44.mjs +154 -0
- package/src/coding-plane.mjs +66 -19
- package/src/compact-gateway-mcp-tools.mjs +4 -2
- package/src/deadbyte-cli.mjs +17 -9
- package/src/doctor-r45.mjs +65 -0
- package/src/durable-job-r44.mjs +145 -0
- package/src/external-job-provider-r44.mjs +126 -0
- package/src/host-plane.mjs +156 -87
- package/src/job-supervisor-host-r44.mjs +112 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +21 -10
- package/src/mcp-host-smoke-client.mjs +1 -1
- package/src/mcp-server.mjs +84 -26
- package/src/mcp-smoke-client.mjs +1 -1
- package/src/native-diff-v2.mjs +12 -7
- package/src/native-diff.mjs +32 -9
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +33 -2
- package/src/observation-smoke-client.mjs +7 -3
- package/src/operator-surface.mjs +2 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-policy.mjs +19 -0
- package/src/process-runtime.mjs +5 -2
- package/src/process-smoke-client.mjs +2 -2
- package/src/provider-circuit.mjs +8 -2
- package/src/r44-compact-disarmed-probe.mjs +74 -0
- package/src/r44-full-surface-probe.mjs +34 -0
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +1 -0
- package/src/remote-device-r44.mjs +205 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
- package/src/remote-mcp-r44-parity-client.mjs +203 -0
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +7 -4
- package/src/runtime-update.mjs +100 -39
- package/src/skill-mcp-tools-r44.mjs +37 -0
- package/src/skill-plane-r44.mjs +203 -0
- package/src/tunnel-stdio-compat-smoke.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-backend-r44.test.mjs +115 -0
- package/test/agent-runtime.test.mjs +4 -3
- package/test/autonomous-loop.test.mjs +121 -11
- package/test/autonomous-runtime.test.mjs +3 -2
- package/test/capability-ledger-r36.test.mjs +3 -3
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/codex-app-server-r44.test.mjs +115 -0
- package/test/codex-autonomous-r44.test.mjs +87 -0
- package/test/coding-plane.test.mjs +3 -2
- package/test/coding-runtime.test.mjs +36 -1
- package/test/compact-catalog-r36.test.mjs +3 -3
- package/test/compact-max-catalog-r40.test.mjs +4 -2
- package/test/compact-max-catalog-r44.test.mjs +97 -0
- package/test/compact-max-catalog-r45.test.mjs +99 -0
- package/test/contained.test.mjs +23 -11
- package/test/controller.test.mjs +5 -1
- package/test/core.test.mjs +1 -1
- package/test/durable-job-r44.test.mjs +75 -0
- package/test/external-job-provider-r44.test.mjs +46 -0
- package/test/full-max-catalog-r40.test.mjs +5 -3
- package/test/full-max-catalog-r44.test.mjs +91 -0
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/host-plane.test.mjs +15 -1
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/native-diff.test.mjs +28 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +3 -2
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-policy.test.mjs +22 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r33-closeout-regression.test.mjs +20 -36
- package/test/r33-finalization.test.mjs +7 -7
- package/test/r43-finalization.test.mjs +31 -0
- package/test/r43-finalizer.test.mjs +34 -0
- package/test/r43-parent.test.mjs +29 -0
- package/test/r44-finalization.test.mjs +58 -0
- package/test/r44-finalizer.test.mjs +48 -0
- package/test/r44-parent.test.mjs +25 -0
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +1 -0
- package/test/release-version.test.mjs +65 -20
- package/test/remote-device-r44.test.mjs +104 -0
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
- package/test/runtime-self-update.test.mjs +42 -0
- package/test/skill-mcp-r44.test.mjs +38 -0
- package/test/skill-plane-r44.test.mjs +93 -0
- package/test/standing-grants.test.mjs +5 -1
|
@@ -0,0 +1,203 @@
|
|
|
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 R44_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'
|
|
19
|
+
])].sort());
|
|
20
|
+
if(R44_EXPECTED_TOOLS.length !== 81) throw new Error(`R44 full catalog identity mismatch: ${R44_EXPECTED_TOOLS.length}`);
|
|
21
|
+
export const R44_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session','desktop_control'].sort());
|
|
22
|
+
export const R44_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('R44 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('R44 compact process output timeout');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runR44CompactParity(endpointText){
|
|
77
|
+
const endpoint=new URL(endpointText);
|
|
78
|
+
if(endpoint.protocol!=='https:') throw new Error('R44 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-r44-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-r44-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>R44_COMPACT_TOOL_LIMIT) throw new Error(`R44 compact catalog exceeds ${R44_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
95
|
+
if(names.length!==R44_COMPACT_TOOL_LIMIT) throw new Error(`R44 compact catalog must be exactly ${R44_COMPACT_TOOL_LIMIT}, got ${names.length}`);
|
|
96
|
+
for(const name of R44_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R44 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('R44 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('R44 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('R44 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('R44 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('R44 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('R44 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-r40.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('R44 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('R44 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('R44 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('R44 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('R44 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('R44 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(`R44 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('R44 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('R44 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 machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
|
|
162
|
+
if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R44 compact machine status release mismatch');
|
|
163
|
+
|
|
164
|
+
const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
|
|
165
|
+
processSession=started.session_id??started.session?.session_id;
|
|
166
|
+
if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R44 compact process session id missing');
|
|
167
|
+
const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
|
|
168
|
+
const processRead=await waitProcessOutput(client,processSession);
|
|
169
|
+
const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
|
|
170
|
+
const terminalState=terminated.state??terminated.session?.state;
|
|
171
|
+
if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R44 compact process lifecycle failed');
|
|
172
|
+
|
|
173
|
+
const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
|
|
174
|
+
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');
|
|
175
|
+
machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
|
|
176
|
+
|
|
177
|
+
return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
|
|
178
|
+
catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
|
|
179
|
+
capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
|
|
180
|
+
gateway_count:R44_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,
|
|
181
|
+
desktop_armed:false,desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
|
|
182
|
+
desktop_frame_id:desktopObserved.frame.frame_id,desktop_target_id:desktopTarget.target.target_id,
|
|
183
|
+
machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
|
|
184
|
+
observation_search_result_count:page.results.length,process_session_id:processSession,process_output_bytes:processRead.stream_head,
|
|
185
|
+
process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
|
|
186
|
+
build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
|
|
187
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
188
|
+
} finally {
|
|
189
|
+
if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
|
|
190
|
+
if(targetPresent){
|
|
191
|
+
try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
|
|
192
|
+
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(()=>{});
|
|
193
|
+
}catch{}
|
|
194
|
+
}
|
|
195
|
+
await client.close().catch(()=>{});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
200
|
+
const endpoint=process.argv[2];
|
|
201
|
+
if(!endpoint) throw new Error('usage: node src/remote-mcp-r44-parity-client.mjs https://host/mcp/path');
|
|
202
|
+
runR44CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
203
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -8,7 +8,7 @@ if (!['smoke', 'full'].includes(mode)) throw new Error('mode must be smoke or fu
|
|
|
8
8
|
const endpoint = new URL(endpointText);
|
|
9
9
|
if (endpoint.protocol !== 'https:') throw new Error('remote MCP gate requires HTTPS');
|
|
10
10
|
|
|
11
|
-
const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
|
|
11
|
+
const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
|
|
12
12
|
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 = [
|
|
@@ -53,6 +53,8 @@ const r36CompactTools = [
|
|
|
53
53
|
if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
|
|
54
54
|
const r40CompactTools = [...r36CompactTools, 'desktop_control'].sort();
|
|
55
55
|
if (r40CompactTools.length !== 42) throw new Error(`R40 compact catalog constant drift: ${r40CompactTools.length}`);
|
|
56
|
+
const r44CompactTools = [...r40CompactTools, 'skill_catalog'].sort();
|
|
57
|
+
if (r44CompactTools.length !== 43) throw new Error(`R44 compact catalog constant drift: ${r44CompactTools.length}`);
|
|
56
58
|
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
57
59
|
const allowedCatalogs = [];
|
|
58
60
|
for (const machine of [false, true]) for (const host of [false, true]) for (const agent of [false, true]) {
|
|
@@ -98,7 +100,8 @@ try {
|
|
|
98
100
|
const names = listed.tools.map(t => t.name).sort();
|
|
99
101
|
const exactR36Compact = JSON.stringify(names) === JSON.stringify(r36CompactTools);
|
|
100
102
|
const exactR40Compact = JSON.stringify(names) === JSON.stringify(r40CompactTools);
|
|
101
|
-
const
|
|
103
|
+
const exactR44Compact = JSON.stringify(names) === JSON.stringify(r44CompactTools);
|
|
104
|
+
const compactProfile = exactR36Compact || exactR40Compact || exactR44Compact;
|
|
102
105
|
const catalogOk = compactProfile || allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected));
|
|
103
106
|
if (!catalogOk) throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
|
|
104
107
|
for (const tool of listed.tools) {
|
|
@@ -123,7 +126,7 @@ try {
|
|
|
123
126
|
if (compactProfile) {
|
|
124
127
|
const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
|
|
125
128
|
const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
|
|
126
|
-
const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && exactR40Compact;
|
|
129
|
+
const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && (exactR40Compact || exactR44Compact);
|
|
127
130
|
if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
|
|
128
131
|
!Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
|
|
129
132
|
throw new Error('compact capability manifest malformed');
|
|
@@ -132,7 +135,7 @@ try {
|
|
|
132
135
|
const expectedCatalogSha = sha256(Buffer.from(JSON.stringify(names), 'utf8'));
|
|
133
136
|
if (manifest.tool_count !== names.length || JSON.stringify(manifest.tool_catalog) !== JSON.stringify(names) ||
|
|
134
137
|
manifest.catalog_sha256 !== expectedCatalogSha) {
|
|
135
|
-
throw new Error('
|
|
138
|
+
throw new Error('compact v2 capability manifest catalog attestation mismatch');
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
const required = manifest.capabilities.filter(item => item?.production === true && item?.host_only !== true);
|