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.
Files changed (110) hide show
  1. package/MANIFEST.SHA256 +109 -64
  2. package/PROVENANCE.json +11 -0
  3. package/R45-PARENT.json +18 -0
  4. package/README.txt +36 -20
  5. package/RELEASE.json +53 -0
  6. package/SBOM.cdx.json +46 -0
  7. package/TEST-MATRIX.json +31 -0
  8. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  9. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  10. package/controller/README.TXT +2 -2
  11. package/controller/deadbyte-controller.ps1 +51 -3
  12. package/controller/deadbyte-process-policy.json +63 -1
  13. package/docs/ARCHITECTURE.md +3 -4
  14. package/native/deadbyte-hardware-observer.cpp +308 -0
  15. package/package.json +14 -7
  16. package/scripts/autonomous-tests.ps1 +2 -2
  17. package/scripts/build-hardware-r45.ps1 +71 -0
  18. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  19. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  20. package/scripts/derive-r45-parent.mjs +24 -0
  21. package/scripts/derive-r45-release.mjs +72 -0
  22. package/scripts/derive-r45-tests.mjs +20 -0
  23. package/scripts/final-closure-r45.mjs +121 -0
  24. package/scripts/final-closure-r46.mjs +98 -0
  25. package/scripts/final-closure-verify-r45.mjs +220 -0
  26. package/scripts/final-closure-verify-r46.mjs +119 -0
  27. package/scripts/finalize-r45.mjs +219 -0
  28. package/scripts/full-regression-r45.mjs +20 -0
  29. package/scripts/full-regression-r46.mjs +20 -0
  30. package/scripts/full-regression-summary-r45.mjs +14 -0
  31. package/scripts/gate-windows-r45.ps1 +44 -0
  32. package/scripts/gate-windows-r46.ps1 +56 -0
  33. package/scripts/init-host-policy.ps1 +39 -1
  34. package/scripts/inspect-active-release-r45.mjs +20 -0
  35. package/scripts/refresh-process-policy.mjs +64 -7
  36. package/scripts/release-manifest.mjs +3 -1
  37. package/scripts/release-parity-r45.mjs +253 -0
  38. package/scripts/release-parity-r46.mjs +187 -0
  39. package/scripts/release-parity-tests-r45.ps1 +8 -0
  40. package/scripts/verify-r45-parent.mjs +142 -0
  41. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  42. package/scripts/windows-gate-evidence-r46.mjs +110 -0
  43. package/src/autonomous-runtime.mjs +12 -7
  44. package/src/capability-ledger.mjs +2 -0
  45. package/src/compact-gateway-mcp-tools.mjs +11 -7
  46. package/src/deadbyte-cli.mjs +24 -12
  47. package/src/doctor-r45.mjs +65 -0
  48. package/src/host-plane.mjs +133 -85
  49. package/src/machine-fs-mcp-tools.mjs +3 -3
  50. package/src/machine-fs-output-contracts.mjs +1 -1
  51. package/src/machine-fs-read-worker.mjs +46 -0
  52. package/src/machine-fs.mjs +63 -10
  53. package/src/mcp-server.mjs +68 -23
  54. package/src/mcp-smoke-client.mjs +4 -1
  55. package/src/native-diff-v2.mjs +9 -4
  56. package/src/native-diff.mjs +29 -7
  57. package/src/observation-mcp-tools.mjs +5 -1
  58. package/src/observation-output-contracts.mjs +8 -0
  59. package/src/observation-runtime.mjs +57 -5
  60. package/src/observation-smoke-client.mjs +6 -2
  61. package/src/output-contracts.mjs +27 -11
  62. package/src/process-mcp-tools.mjs +18 -5
  63. package/src/process-output-contracts.mjs +20 -3
  64. package/src/process-policy.mjs +3 -1
  65. package/src/process-runtime.mjs +99 -19
  66. package/src/process-smoke-client.mjs +67 -22
  67. package/src/r45-compact-disarmed-probe.mjs +74 -0
  68. package/src/r45-full-surface-probe.mjs +34 -0
  69. package/src/release-generation.mjs +23 -3
  70. package/src/remote-device-r44.mjs +53 -15
  71. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  72. package/src/remote-mcp-smoke-client.mjs +1 -1
  73. package/src/runtime-update.mjs +36 -8
  74. package/src/stdio-console-guard.mjs +24 -0
  75. package/src/version.mjs +1 -1
  76. package/test/agent-runtime.test.mjs +1 -0
  77. package/test/autonomous-loop.test.mjs +24 -0
  78. package/test/autonomous-runtime.test.mjs +1 -0
  79. package/test/cli-entrypoint.test.mjs +4 -4
  80. package/test/coding-plane.test.mjs +1 -0
  81. package/test/compact-gateway-r36.test.mjs +28 -0
  82. package/test/compact-max-catalog-r45.test.mjs +99 -0
  83. package/test/contained.test.mjs +23 -11
  84. package/test/controller.test.mjs +5 -1
  85. package/test/core.test.mjs +1 -1
  86. package/test/full-max-catalog-r45.test.mjs +91 -0
  87. package/test/full-max-catalog-r46.test.mjs +92 -0
  88. package/test/hardware-observation-r45.test.mjs +44 -0
  89. package/test/host-inline-r45.test.mjs +67 -0
  90. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  91. package/test/machine-fs-runtime.test.mjs +18 -2
  92. package/test/machine-mcp-integration.test.mjs +1 -0
  93. package/test/observation-mcp-tools.test.mjs +4 -3
  94. package/test/observation-runtime.test.mjs +29 -2
  95. package/test/output-schema.test.mjs +1 -0
  96. package/test/package-boundary.test.mjs +1 -1
  97. package/test/process-mcp-tools.test.mjs +23 -5
  98. package/test/process-policy-refresh.test.mjs +87 -10
  99. package/test/process-runtime.test.mjs +111 -1
  100. package/test/process-supervision.test.mjs +4 -2
  101. package/test/production-docs.test.mjs +3 -3
  102. package/test/r33-closeout-regression.test.mjs +20 -19
  103. package/test/r33-finalization.test.mjs +3 -3
  104. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  105. package/test/r45-finalization.test.mjs +84 -0
  106. package/test/release-generation.test.mjs +26 -11
  107. package/test/release-version.test.mjs +112 -46
  108. package/test/remote-device-r44.test.mjs +75 -0
  109. package/test/remote-r24-catalog.test.mjs +2 -2
  110. 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:d.reachable,connection_id:d.connection_id,updated_at_utc:d.updated_at_utc};
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 publicDevice(existing);
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 publicDevice(d);
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 publicDevice(d);
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 publicDevice(d);
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
- const response=await relay.probe({device_id:deviceId,epoch,connection_id:connectionId,nonce});
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 {...publicDevice(d),probe_nonce:nonce};
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.reachable,'remote device not reachable');
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{response=await relay.request({device_id:deviceId,epoch,capability_id:capabilityId,payload,request_id:requestId,request_sha256:requestSha});}
154
- catch(error){
155
- return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_error';rec.error=String(error?.message??error);rec.updated_at_utc=now();await persist();return structuredClone(rec);});
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 {...publicDevice(d),revoked:true};
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(publicDevice).sort((a,b)=>a.device_id.localeCompare(b.device_id)),requests:structuredClone(state.requests)};}
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',
@@ -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
- if(policy.schema!==schema||policy.enabled!==true||!policy.commands||typeof policy.commands!=='object'||Array.isArray(policy.commands)) {
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
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.0';
1
+ export const DEADBYTE_VERSION = '0.15.2';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -54,6 +54,7 @@ async function connectAgent(f) {
54
54
  DEADBYTE_WORKSPACE_ROOT: f.root,
55
55
  DEADBYTE_SIGNING_KEY: f.keys.privateKeyPath,
56
56
  DEADBYTE_VERIFY_KEY: f.keys.publicKeyPath,
57
+ DEADBYTE_PRIVILEGED_POLICY: '',
57
58
  DEADBYTE_AGENT_RUNTIME: '1'
58
59
  },
59
60
  stderr: 'pipe'
@@ -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.0');
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 deadbyte-mcp@latest supervisor status/);
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@latest remote/);
54
- assert.match(readme,/npx deadbyte-mcp@latest audit verify/);
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