deadbyte-mcp 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +126 -54
- package/R41-PARENT.json +15 -0
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +12 -8
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +112 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/init-desktop-policy-r40.ps1 +85 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/src/autonomous-context-engine.mjs +3 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +24 -0
- package/src/autonomous-runtime.mjs +164 -25
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +87 -19
- package/src/memory-r41.mjs +692 -0
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +86 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +15 -15
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/r41-finalization.test.mjs +38 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +20 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { 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 { loadDesktopPolicy } from './desktop-policy-r40.mjs';
|
|
8
|
+
import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
|
|
9
|
+
import { loadPublicVerifyKey } from './trust.mjs';
|
|
10
|
+
|
|
11
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
12
|
+
function gateway(result,label){
|
|
13
|
+
if(result.isError)throw new Error(label+' returned error: '+JSON.stringify(result.structuredContent??result.content));
|
|
14
|
+
const value=result.structuredContent;
|
|
15
|
+
if(!value||value.status!=='ok'||!value.result)throw new Error(label+' malformed');
|
|
16
|
+
return value.result;
|
|
17
|
+
}
|
|
18
|
+
function predispatchForegroundChanged(result){
|
|
19
|
+
if(result?.isError!==true)return false;
|
|
20
|
+
const value=result.structuredContent;
|
|
21
|
+
return value?.status==='denied'&&value?.op==='action'&&value?.error==='desktop foreground window changed before action';
|
|
22
|
+
}
|
|
23
|
+
async function verify(policy,key,id,sha,kind){
|
|
24
|
+
const receipt=JSON.parse(await readFile(path.join(policy.evidence_dir,id+'.json'),'utf8'));
|
|
25
|
+
const proof=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:key.publicKey,keyId:key.keyId});
|
|
26
|
+
if(!proof.ok||proof.receipt_sha256!==sha||proof.kind!==kind)throw new Error('desktop smoke receipt mismatch '+kind);
|
|
27
|
+
}
|
|
28
|
+
export async function runR40DesktopGuardedSmoke(endpointText){
|
|
29
|
+
const endpoint=new URL(endpointText);if(endpoint.protocol!=='https:')throw new Error('R40 Desktop smoke requires HTTPS');
|
|
30
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
31
|
+
const policy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
|
|
32
|
+
const key=await loadPublicVerifyKey(path.join(stateRoot,'trust','ed25519-public.pem'));
|
|
33
|
+
const client=new Client({name:'deadbyte-r40-desktop-guarded-smoke',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
34
|
+
const transport=new StreamableHTTPClientTransport(endpoint);
|
|
35
|
+
try{
|
|
36
|
+
await client.connect(transport);
|
|
37
|
+
const status=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
|
|
38
|
+
if(status.armed!==true||!status.action_kinds?.includes('key_chord'))throw new Error('Desktop smoke requires separately armed key_chord authority');
|
|
39
|
+
const maxPredispatchAttempts=3;
|
|
40
|
+
let action=null;
|
|
41
|
+
let observationReceiptsVerified=0;
|
|
42
|
+
let predispatchRetryCount=0;
|
|
43
|
+
for(let attempt=1;attempt<=maxPredispatchAttempts;attempt+=1){
|
|
44
|
+
const observed=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
|
|
45
|
+
await verify(policy,key,observed.receipt_id,observed.receipt_sha256,'desktop_observation');
|
|
46
|
+
observationReceiptsVerified+=1;
|
|
47
|
+
const actionResult=await client.callTool({name:'desktop_control',arguments:{
|
|
48
|
+
op:'action',intent_id:randomBytes(32).toString('hex'),kind:'key_chord',expected_frame_id:observed.frame.frame_id,
|
|
49
|
+
target_id:null,point:null,scroll_delta:null,key_chord:['SHIFT'],text:null
|
|
50
|
+
}});
|
|
51
|
+
if(predispatchForegroundChanged(actionResult)){
|
|
52
|
+
if(attempt===maxPredispatchAttempts)throw new Error('Desktop guarded smoke foreground remained unstable before dispatch');
|
|
53
|
+
predispatchRetryCount+=1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
action=gateway(actionResult,'desktop key_chord');
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if(!action||action.injected!==true||action.side_effect_state!=='confirmed_injected'||action.action_summary?.kind!=='key_chord')throw new Error('Desktop guarded smoke input not confirmed');
|
|
60
|
+
await verify(policy,key,action.started_receipt_id,action.started_receipt_sha256,'desktop_action_started');
|
|
61
|
+
await verify(policy,key,action.post_receipt_id,action.post_receipt_sha256,'desktop_post_observation');
|
|
62
|
+
await verify(policy,key,action.receipt_id,action.receipt_sha256,'desktop_action_completed');
|
|
63
|
+
return {status:'passed',kind:'key_chord',keys:['SHIFT'],native_injected:true,replay_performed:false,
|
|
64
|
+
predispatch_retry_count:predispatchRetryCount,observation_receipts_verified:observationReceiptsVerified,
|
|
65
|
+
observation_receipt_verified:true,action_receipts_verified:3,action_chain_sha256:action.action_chain?.chain_sha256??null,
|
|
66
|
+
effect_id:action.effect_id,receipt_id:action.receipt_id,receipt_sha256:action.receipt_sha256};
|
|
67
|
+
}finally{await client.close().catch(()=>{});}
|
|
68
|
+
}
|
|
69
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){const endpoint=process.argv[2];if(!endpoint)throw new Error('usage: node src/remote-mcp-desktop-r40-smoke-client.mjs https://host/mcp/path');
|
|
70
|
+
runR40DesktopGuardedSmoke(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
|
|
@@ -0,0 +1,200 @@
|
|
|
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 R40_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'
|
|
19
|
+
])].sort());
|
|
20
|
+
if(R40_EXPECTED_TOOLS.length !== 80) throw new Error(`R40 full catalog identity mismatch: ${R40_EXPECTED_TOOLS.length}`);
|
|
21
|
+
export const R40_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session','desktop_control'].sort());
|
|
22
|
+
export const R40_COMPACT_TOOL_LIMIT=42;
|
|
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('R40 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('R40 compact process output timeout');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runR40CompactParity(endpointText){
|
|
77
|
+
const endpoint=new URL(endpointText);
|
|
78
|
+
if(endpoint.protocol!=='https:') throw new Error('R40 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-r40-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-r40-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>R40_COMPACT_TOOL_LIMIT) throw new Error(`R40 compact catalog exceeds ${R40_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
95
|
+
if(names.length!==R40_COMPACT_TOOL_LIMIT) throw new Error(`R40 compact catalog must be exactly ${R40_COMPACT_TOOL_LIMIT}, got ${names.length}`);
|
|
96
|
+
for(const name of R40_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R40 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('R40 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('R40 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 coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
109
|
+
const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
|
|
110
|
+
if(coding.armed!==true||autonomous.coding_armed!==true||autonomous.autonomous_armed!==true) throw new Error('R40 compact parity requires coding and autonomous standing grants');
|
|
111
|
+
const desktopStatus=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
|
|
112
|
+
if(desktopStatus.configured!==true||desktopStatus.armed!==false) throw new Error('R40 compact parity requires Desktop configured and DISARMED');
|
|
113
|
+
const desktopObserved=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
|
|
114
|
+
await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopObserved.receipt_id,desktopObserved.receipt_sha256,'desktop_observation');
|
|
115
|
+
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');
|
|
116
|
+
await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopTarget.grounding.receipt_id,desktopTarget.grounding.receipt_sha256,'desktop_grounding');
|
|
117
|
+
if(desktopTarget.target?.source!=='uia') throw new Error('R40 compact UIA root grounding missing');
|
|
118
|
+
const tree=structured(await client.callTool({name:'coding_tree',arguments:{root_id:'core',path:'workspaces/demo',max_depth:2,max_entries:128}}),'coding_tree');
|
|
119
|
+
if(tree.status!=='ok'||!Array.isArray(tree.entries)) throw new Error('coding_tree malformed');
|
|
120
|
+
const stat=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'stat',root_id:'core',path:'package.json',hash:true}}),'coding stat');
|
|
121
|
+
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');
|
|
122
|
+
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');
|
|
123
|
+
if(!Array.isArray(search.matches)||search.matches.length<1) throw new Error('coding search returned no canonical ledger match');
|
|
124
|
+
|
|
125
|
+
const buildScript='scripts/autonomous-build.ps1';
|
|
126
|
+
const testScript='scripts/release-parity-tests-r40.ps1';
|
|
127
|
+
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');
|
|
128
|
+
if(build.status!=='passed'||build.exit_code!==0) throw new Error('R40 coding build failed');
|
|
129
|
+
const buildVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:build.receipt_id}}),'coding build receipt');
|
|
130
|
+
if(buildVerify.ok!==true||buildVerify.signature_valid!==true) throw new Error('R40 coding build receipt invalid');
|
|
131
|
+
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');
|
|
132
|
+
if(tests.status!=='passed'||tests.exit_code!==0) throw new Error('R40 coding tests failed');
|
|
133
|
+
const testVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:tests.receipt_id}}),'coding test receipt');
|
|
134
|
+
if(testVerify.ok!==true||testVerify.signature_valid!==true) throw new Error('R40 coding test receipt invalid');
|
|
135
|
+
|
|
136
|
+
const initial=`needle-${tag}\n`;
|
|
137
|
+
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');
|
|
138
|
+
targetPresent=true;machineReceipts.push(write.receipt_id);await verifyMachineReceipt(policy,write.receipt_id,'machine_fs_write');
|
|
139
|
+
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');
|
|
140
|
+
if(Buffer.from(bounded.data_base64,'base64').toString('utf8')!==initial||bounded.file_sha256!==write.sha256) throw new Error('R40 bounded machine read mismatch');
|
|
141
|
+
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');
|
|
142
|
+
machineReceipts.push(edit.receipt_id);await verifyMachineReceipt(policy,edit.receipt_id,'machine_fs_edit_exact');
|
|
143
|
+
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}});
|
|
144
|
+
const staleValue=stale.structuredContent;
|
|
145
|
+
if(!staleValue||typeof staleValue!=='object') throw new Error('R40 stale machine-fs denial missing structured output');
|
|
146
|
+
if(stale.isError!==true||staleValue.status!=='denied'||!/precondition mismatch/i.test(String(staleValue.error??''))) {
|
|
147
|
+
throw new Error(`R40 stale machine-fs precondition was not denied: ${JSON.stringify(staleValue)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
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');
|
|
151
|
+
const searchStatus=await waitSearch(client,searchStart.search_id);
|
|
152
|
+
if(searchStatus.state!=='completed'||!searchStatus.receipt_id) throw new Error('R40 compact observation search failed');
|
|
153
|
+
observationReceipts.push(searchStatus.receipt_id);await verifyObservationReceipt(policy,searchStatus.receipt_id);
|
|
154
|
+
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');
|
|
155
|
+
if(!page.results.some(item=>item.path===target)) throw new Error('R40 compact search did not return parity file');
|
|
156
|
+
const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');
|
|
157
|
+
observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);
|
|
158
|
+
const machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
|
|
159
|
+
if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R40 compact machine status release mismatch');
|
|
160
|
+
|
|
161
|
+
const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
|
|
162
|
+
processSession=started.session_id??started.session?.session_id;
|
|
163
|
+
if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R40 compact process session id missing');
|
|
164
|
+
const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
|
|
165
|
+
const processRead=await waitProcessOutput(client,processSession);
|
|
166
|
+
const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
|
|
167
|
+
const terminalState=terminated.state??terminated.session?.state;
|
|
168
|
+
if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R40 compact process lifecycle failed');
|
|
169
|
+
|
|
170
|
+
const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
|
|
171
|
+
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');
|
|
172
|
+
machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
|
|
173
|
+
|
|
174
|
+
return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
|
|
175
|
+
catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
|
|
176
|
+
capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
|
|
177
|
+
gateway_count:R40_COMPACT_GATEWAYS.length,tree_entries:tree.entries.length,coding_stat_verified:true,coding_search_result_count:search.matches.length,
|
|
178
|
+
desktop_armed:false,desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
|
|
179
|
+
desktop_frame_id:desktopObserved.frame.frame_id,desktop_target_id:desktopTarget.target.target_id,
|
|
180
|
+
machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
|
|
181
|
+
observation_search_result_count:page.results.length,process_session_id:processSession,process_output_bytes:processRead.stream_head,
|
|
182
|
+
process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
|
|
183
|
+
build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
|
|
184
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
185
|
+
} finally {
|
|
186
|
+
if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
|
|
187
|
+
if(targetPresent){
|
|
188
|
+
try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
|
|
189
|
+
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(()=>{});
|
|
190
|
+
}catch{}
|
|
191
|
+
}
|
|
192
|
+
await client.close().catch(()=>{});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
197
|
+
const endpoint=process.argv[2];
|
|
198
|
+
if(!endpoint) throw new Error('usage: node src/remote-mcp-r40-parity-client.mjs https://host/mcp/path');
|
|
199
|
+
runR40CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
200
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
3
3
|
|
|
4
4
|
const endpointText = process.argv[2];
|
|
@@ -51,6 +51,9 @@ const r36CompactTools = [
|
|
|
51
51
|
'process_session'
|
|
52
52
|
].sort();
|
|
53
53
|
if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
|
|
54
|
+
const r40CompactTools = [...r36CompactTools, 'desktop_control'].sort();
|
|
55
|
+
if (r40CompactTools.length !== 42) throw new Error(`R40 compact catalog constant drift: ${r40CompactTools.length}`);
|
|
56
|
+
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
54
57
|
const allowedCatalogs = [];
|
|
55
58
|
for (const machine of [false, true]) for (const host of [false, true]) for (const agent of [false, true]) {
|
|
56
59
|
for (const processPlane of [false, true]) for (const machineFs of [false, true]) for (const observation of [false, true]) for (const codingMode of ['none', 'coding', 'autonomous']) {
|
|
@@ -93,7 +96,9 @@ try {
|
|
|
93
96
|
if (client.getProtocolEra() !== 'modern') throw new Error(`expected modern MCP era, got ${String(client.getProtocolEra())}`);
|
|
94
97
|
const listed = await client.listTools();
|
|
95
98
|
const names = listed.tools.map(t => t.name).sort();
|
|
96
|
-
const
|
|
99
|
+
const exactR36Compact = JSON.stringify(names) === JSON.stringify(r36CompactTools);
|
|
100
|
+
const exactR40Compact = JSON.stringify(names) === JSON.stringify(r40CompactTools);
|
|
101
|
+
const compactProfile = exactR36Compact || exactR40Compact;
|
|
97
102
|
const catalogOk = compactProfile || allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected));
|
|
98
103
|
if (!catalogOk) throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
|
|
99
104
|
for (const tool of listed.tools) {
|
|
@@ -117,14 +122,23 @@ try {
|
|
|
117
122
|
let agent = null;
|
|
118
123
|
if (compactProfile) {
|
|
119
124
|
const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
|
|
120
|
-
|
|
125
|
+
const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
|
|
126
|
+
const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && exactR40Compact;
|
|
127
|
+
if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
|
|
121
128
|
!Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
|
|
122
|
-
throw new Error('
|
|
129
|
+
throw new Error('compact capability manifest malformed');
|
|
130
|
+
}
|
|
131
|
+
if (manifestV2) {
|
|
132
|
+
const expectedCatalogSha = sha256(Buffer.from(JSON.stringify(names), 'utf8'));
|
|
133
|
+
if (manifest.tool_count !== names.length || JSON.stringify(manifest.tool_catalog) !== JSON.stringify(names) ||
|
|
134
|
+
manifest.catalog_sha256 !== expectedCatalogSha) {
|
|
135
|
+
throw new Error('R40 compact capability manifest catalog attestation mismatch');
|
|
136
|
+
}
|
|
123
137
|
}
|
|
124
138
|
const required = manifest.capabilities.filter(item => item?.production === true && item?.host_only !== true);
|
|
125
139
|
for (const item of required) {
|
|
126
140
|
if (typeof item.compact_tool !== 'string' || !names.includes(item.compact_tool)) {
|
|
127
|
-
throw new Error(`
|
|
141
|
+
throw new Error(`compact capability route missing: ${String(item.id)}`);
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
144
|
const machine = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_status' } }), 'machine_status');
|
|
@@ -132,7 +146,7 @@ try {
|
|
|
132
146
|
const ping = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_ping', nonce } }), 'machine_ping');
|
|
133
147
|
if (ping.nonce !== nonce || ping.machine_id !== machine.machine_id ||
|
|
134
148
|
ping.release_id !== machine.release_id || ping.release_manifest_sha256 !== machine.release_manifest_sha256) {
|
|
135
|
-
throw new Error('
|
|
149
|
+
throw new Error('compact machine ping binding mismatch');
|
|
136
150
|
}
|
|
137
151
|
compact = {
|
|
138
152
|
profile: 'compact',
|
|
@@ -6,6 +6,7 @@ export const REMOTE_RESULT_JOURNAL_SCHEMA='deadbyte.remote-result-journal.v1';
|
|
|
6
6
|
const SESSION=/^[0-9a-f]{24}$/;
|
|
7
7
|
const HASH=/^[0-9a-f]{64}$/;
|
|
8
8
|
const STATUS=new Set(['ok','error']);
|
|
9
|
+
const SIDE_EFFECT_STATES=new Set(['unknown']);
|
|
9
10
|
const EVENT_TYPES=new Set(['tool.call','tool.result']);
|
|
10
11
|
|
|
11
12
|
function text(value,label,max){
|
|
@@ -40,10 +41,17 @@ function validateEvent(raw,index=null){
|
|
|
40
41
|
observed_at_utc:observed,payload_sha256:raw.payload_sha256,arguments:sanitizeAuditArgs(rawArgs)});
|
|
41
42
|
}
|
|
42
43
|
if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
|
|
44
|
+
const durationMs=raw.duration_ms===undefined?null:raw.duration_ms;
|
|
45
|
+
if(durationMs!==null&&(!Number.isFinite(durationMs)||durationMs<0||durationMs>86_400_000)) throw new Error('remote result duration_ms invalid');
|
|
46
|
+
const reason=raw.reason===undefined||raw.reason===null?null:text(raw.reason,'remote result reason',512);
|
|
47
|
+
const sideEffectState=raw.side_effect_state===undefined||raw.side_effect_state===null?null:raw.side_effect_state;
|
|
48
|
+
if(sideEffectState!==null&&!SIDE_EFFECT_STATES.has(sideEffectState)) throw new Error('remote result side_effect_state invalid');
|
|
43
49
|
return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,status:raw.status,
|
|
44
|
-
observed_at_utc:observed,payload_sha256:raw.payload_sha256
|
|
50
|
+
observed_at_utc:observed,payload_sha256:raw.payload_sha256,
|
|
51
|
+
...(durationMs===null?{}:{duration_ms:durationMs}),...(reason===null?{}:{reason}),
|
|
52
|
+
...(sideEffectState===null?{}:{side_effect_state:sideEffectState})});
|
|
45
53
|
}
|
|
46
|
-
function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.payload_sha256}`;}
|
|
54
|
+
function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.side_effect_state??''}\0${item.payload_sha256}`;}
|
|
47
55
|
export function parseRemoteResultCursor(cursor){
|
|
48
56
|
if(typeof cursor!=='string') throw new Error('remote result cursor invalid');
|
|
49
57
|
const match=/^r1:([0-9a-f]{24}):(0|[1-9][0-9]{0,15})$/.exec(cursor);
|
package/src/runtime-update.mjs
CHANGED
|
@@ -45,14 +45,17 @@ function normalizePersistentPublicOrigin(value){
|
|
|
45
45
|
return publicOrigin.origin;
|
|
46
46
|
}
|
|
47
47
|
export function parseUpdateArgs(argv){
|
|
48
|
-
const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
-
const cloud={},bridge={},processPolicy={},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,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
+
const cloud={},bridge={},processPolicy={},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 desktopPolicyFlags=new Map([
|
|
57
|
+
['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
|
|
58
|
+
]);
|
|
56
59
|
const semanticLspFlags=new Map([
|
|
57
60
|
['--semantic-lsp-config','path'],['--semantic-lsp-config-sha256','sha256']
|
|
58
61
|
]);
|
|
@@ -61,16 +64,17 @@ export function parseUpdateArgs(argv){
|
|
|
61
64
|
['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
|
|
62
65
|
['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
|
|
63
66
|
]);
|
|
64
|
-
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
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()]);
|
|
65
68
|
for(let index=0;index<argv.length;index++){
|
|
66
69
|
const arg=argv[index];
|
|
67
|
-
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-autonomous-supervisor'){
|
|
70
|
+
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
|
|
68
71
|
if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
|
|
69
72
|
seen.add(arg);
|
|
70
73
|
if(arg==='--restore-standing-grants') out.restoreStandingGrants=true;
|
|
71
74
|
else if(arg==='--recover-offline-predecessor') out.recoverOfflinePredecessor=true;
|
|
72
75
|
else if(arg==='--enable-machine-fs') out.enableMachineFs=true;
|
|
73
76
|
else if(arg==='--enable-observation') out.enableObservation=true;
|
|
77
|
+
else if(arg==='--enable-desktop') out.enableDesktop=true;
|
|
74
78
|
else out.enableAutonomousSupervisor=true;
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
@@ -95,6 +99,7 @@ export function parseUpdateArgs(argv){
|
|
|
95
99
|
else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
|
|
96
100
|
else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
|
|
97
101
|
else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
|
|
102
|
+
else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
|
|
98
103
|
else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
|
|
99
104
|
else cloud[cloudFlags.get(arg)]=value;
|
|
100
105
|
}
|
|
@@ -116,6 +121,13 @@ export function parseUpdateArgs(argv){
|
|
|
116
121
|
if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
|
|
117
122
|
out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
|
|
118
123
|
}
|
|
124
|
+
const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
|
|
125
|
+
if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
|
|
126
|
+
if(desktopPolicyPresent.length===desktopPolicyFields.length){
|
|
127
|
+
const sha256=String(desktopPolicy.sha256).toLowerCase();
|
|
128
|
+
if(!HEX64.test(sha256)) throw new Error('desktop policy SHA-256 must be lowercase hex');
|
|
129
|
+
out.desktopPolicyBootstrap={path:path.resolve(desktopPolicy.path),sha256};
|
|
130
|
+
}
|
|
119
131
|
const semanticLspFields=[...semanticLspFlags.values()],semanticLspPresent=semanticLspFields.filter(key=>semanticLsp[key]!==undefined);
|
|
120
132
|
if(semanticLspPresent.length!==0&&semanticLspPresent.length!==semanticLspFields.length) throw new Error('semantic LSP config bootstrap requires path plus SHA-256 as one pair');
|
|
121
133
|
if(semanticLspPresent.length===semanticLspFields.length){
|
|
@@ -321,6 +333,21 @@ export async function prepareProcessPolicyBootstrap(bootstrap){
|
|
|
321
333
|
return {...pinned,policy};
|
|
322
334
|
}
|
|
323
335
|
|
|
336
|
+
export async function prepareDesktopPolicyBootstrap(bootstrap){
|
|
337
|
+
if(!bootstrap) return null;
|
|
338
|
+
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
|
|
339
|
+
const policy=await readJson(pinned.path);
|
|
340
|
+
if(policy?.schema!=='deadbyte.desktop-policy.v1'||policy?.enabled!==true) throw new Error('desktop policy schema/enabled mismatch');
|
|
341
|
+
for(const name of ['observer','input','grounder']){
|
|
342
|
+
const provider=policy[name];
|
|
343
|
+
if(!provider||typeof provider.executable!=='string'||!path.isAbsolute(provider.executable)||!HEX64.test(String(provider.sha256??''))) {
|
|
344
|
+
throw new Error(`desktop policy ${name} pin invalid`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if(!Array.isArray(policy.allowed_actions)||policy.allowed_actions.length<1) throw new Error('desktop policy allowed_actions invalid');
|
|
348
|
+
return {...pinned,policy};
|
|
349
|
+
}
|
|
350
|
+
|
|
324
351
|
export async function prepareSemanticLspConfigBootstrap(bootstrap){
|
|
325
352
|
if(!bootstrap) return null;
|
|
326
353
|
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'semantic LSP config');
|
|
@@ -460,6 +487,7 @@ export function parseControllerStatus(text){
|
|
|
460
487
|
local_ready:/Local ready\s*:\s*True/.test(text),
|
|
461
488
|
coding_armed:/Coding plane\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
462
489
|
autonomous_armed:/Autonomous\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
490
|
+
desktop_armed:/Desktop plane\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
463
491
|
host_armed:/Host plane\s*:\s*ARMED \[standing-grant\]/.test(text)
|
|
464
492
|
};
|
|
465
493
|
}
|
|
@@ -664,13 +692,14 @@ async function loadControlInputs({stateRoot,sourceRoot,pointerInfo}){
|
|
|
664
692
|
ctx=legacyContext(sourceRoot); configPath=path.join(ctx.source_root,'controller','deadbyte-controller.config.psd1');
|
|
665
693
|
}
|
|
666
694
|
let config=await loadPowerShellDataFile(configPath); config=await normalizeComponentIdentities(config);
|
|
667
|
-
const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??'');
|
|
695
|
+
const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??''); const desktopPath=String(config.DesktopPolicyFile??'');
|
|
668
696
|
if(!path.isAbsolute(codingPath)||!path.isAbsolute(autonomousPath)) throw new Error('coding/autonomous policy paths must be absolute');
|
|
669
697
|
if(processPath&&!path.isAbsolute(processPath)) throw new Error('process policy path must be absolute');
|
|
670
|
-
|
|
698
|
+
if(desktopPath&&!path.isAbsolute(desktopPath)) throw new Error('desktop policy path must be absolute');
|
|
699
|
+
const codingPolicy=await readJson(codingPath); const autonomousPolicy=await readJson(autonomousPath); const hostPolicy=hostPath?await readJson(hostPath):null; const processPolicy=processPath?await readJson(processPath):null; const desktopPolicy=desktopPath?await readJson(desktopPath):null;
|
|
671
700
|
const legacyCore=typeof config.CoreRoot==='string'&&config.CoreRoot?path.resolve(config.CoreRoot):ctx.core_root;
|
|
672
701
|
const workspaceRoot=typeof config.WorkspaceRoot==='string'&&config.WorkspaceRoot?path.resolve(config.WorkspaceRoot):path.join(legacyCore,'workspaces');
|
|
673
|
-
return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,workspaceRoot};
|
|
702
|
+
return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,desktopPolicy,workspaceRoot};
|
|
674
703
|
}
|
|
675
704
|
export function assertDevelopmentRootSafe(developmentRoot,stateRoot){
|
|
676
705
|
const dev=path.resolve(developmentRoot),state=path.resolve(stateRoot);
|
|
@@ -724,17 +753,18 @@ async function rollbackLaunchers(plan){
|
|
|
724
753
|
}
|
|
725
754
|
async function revokeAuthority(ctx,prior){
|
|
726
755
|
let revoked=false;
|
|
756
|
+
if(prior.desktop_armed){ controllerCaptured(ctx,'disarmdesktop',15000); revoked=true; }
|
|
727
757
|
if(prior.autonomous_armed){ controllerCaptured(ctx,'disarmautonomous',15000); revoked=true; }
|
|
728
758
|
if(prior.coding_armed){ controllerCaptured(ctx,'disarmcoding',15000); revoked=true; }
|
|
729
759
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
730
|
-
if(status.coding_armed||status.autonomous_armed) throw new Error('standing authority revoke gate failed');
|
|
760
|
+
if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
|
|
731
761
|
if(status.host_armed) throw new Error('host plane became armed during revoke');
|
|
732
762
|
return {revoked,status};
|
|
733
763
|
}
|
|
734
764
|
async function restoreStandingAuthority(ctx,prior,enabled){
|
|
735
765
|
if(!enabled){
|
|
736
766
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
737
|
-
if(status.coding_armed||status.autonomous_armed) throw new Error('standing authority unexpectedly survived slot switch');
|
|
767
|
+
if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority unexpectedly survived slot switch');
|
|
738
768
|
if(status.host_armed) throw new Error('host plane became armed during slot switch');
|
|
739
769
|
return status;
|
|
740
770
|
}
|
|
@@ -744,6 +774,7 @@ async function restoreStandingAuthority(ctx,prior,enabled){
|
|
|
744
774
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
745
775
|
if(status.coding_armed!==prior.coding_armed) throw new Error('coding standing-grant restore gate failed');
|
|
746
776
|
if(status.autonomous_armed!==prior.autonomous_armed) throw new Error('autonomous standing-grant restore gate failed');
|
|
777
|
+
if(status.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
|
|
747
778
|
if(status.host_armed) throw new Error('host plane became armed during slot switch');
|
|
748
779
|
return status;
|
|
749
780
|
}
|
|
@@ -795,7 +826,7 @@ function publicPointer(pointer){
|
|
|
795
826
|
release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
796
827
|
launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
|
|
797
828
|
}
|
|
798
|
-
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
829
|
+
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=[]}){
|
|
799
830
|
if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
|
|
800
831
|
const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
|
|
801
832
|
const key=await loadUpdateKey(state); const updateId=`r27-${expectedManifest.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
@@ -820,6 +851,8 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
820
851
|
control.processPolicy=processBootstrap.policy;
|
|
821
852
|
control.config.ProcessRuntimeEnabled=true;
|
|
822
853
|
}
|
|
854
|
+
const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);
|
|
855
|
+
if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
|
|
823
856
|
const semanticBootstrap=await prepareSemanticLspConfigBootstrap(semanticLspBootstrap);
|
|
824
857
|
if(semanticBootstrap){
|
|
825
858
|
control.config.SemanticLspConfigFile=semanticBootstrap.path;
|
|
@@ -834,6 +867,10 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
834
867
|
if(control.config.CodingRuntimeEnabled!==true) throw new Error('ObservationRuntimeEnabled requires CodingRuntimeEnabled');
|
|
835
868
|
control.config.ObservationRuntimeEnabled=true;
|
|
836
869
|
}
|
|
870
|
+
if(enableDesktop){
|
|
871
|
+
if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
|
|
872
|
+
control.config.DesktopRuntimeEnabled=true;
|
|
873
|
+
}
|
|
837
874
|
control.config=await prepareBridgeSpec(control.config,bridgeBootstrap);
|
|
838
875
|
control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
|
|
839
876
|
control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
|
|
@@ -860,7 +897,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
860
897
|
launchers=await launcherPlan(state);
|
|
861
898
|
const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,
|
|
862
899
|
workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,
|
|
863
|
-
autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy});
|
|
900
|
+
autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
|
|
864
901
|
newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,
|
|
865
902
|
release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,
|
|
866
903
|
launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
|
|
@@ -875,7 +912,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
875
912
|
coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},new_pointer:publicPointer(newPointer)});
|
|
876
913
|
predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor); oldRuntime=predecessor.runtime; priorStatus=predecessor.status;
|
|
877
914
|
await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
|
|
878
|
-
prior_authority:{coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,host_armed:priorStatus.host_armed}});
|
|
915
|
+
prior_authority:{coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
|
|
879
916
|
const revoke=await revokeAuthority(control.ctx,priorStatus); revoked=revoke.revoked;
|
|
880
917
|
await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
|
|
881
918
|
if(oldRuntime){
|
|
@@ -897,7 +934,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
897
934
|
await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
|
|
898
935
|
const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,restoreStandingGrants);
|
|
899
936
|
await writeState(statePath,journal,'authority_verified',{final_authority:{coding_armed:finalAuthority.coding_armed,
|
|
900
|
-
autonomous_armed:finalAuthority.autonomous_armed,host_armed:finalAuthority.host_armed}});
|
|
937
|
+
autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}});
|
|
901
938
|
const body={schema:UPDATE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
|
|
902
939
|
completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',mechanism:'immutable-release-slot-plus-atomic-pointer-rename',
|
|
903
940
|
predecessor_mode:predecessor.mode,prior_pointer:publicPointer(pointerInfo.pointer),active_pointer:publicPointer(newPointer),release_slot:{release_id:slot.release_id,release_root:slot.release_root,
|
|
@@ -908,8 +945,9 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
908
945
|
host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},
|
|
909
946
|
coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},old_runtime:generationTuple(oldRuntime),new_runtime:generationTuple(newRuntime),stabilization,
|
|
910
947
|
authority:{prior:{coding_armed:priorStatus.coding_armed,
|
|
911
|
-
autonomous_armed:priorStatus.autonomous_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
|
|
912
|
-
|
|
948
|
+
autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
|
|
949
|
+
desktop_restore_policy:'explicit-rearm-required',
|
|
950
|
+
final:{coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
|
|
913
951
|
const receipt=await writeEnvelope(receiptPath,body,key);
|
|
914
952
|
await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
|
|
915
953
|
return {ok:true,update_id:updateId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
|
|
@@ -1042,8 +1080,8 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
|
|
|
1042
1080
|
const args=parseUpdateArgs(argv);
|
|
1043
1081
|
const result=await performSlotUpdate({stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,
|
|
1044
1082
|
expectedManifest:args.expectedManifest,sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
|
|
1045
|
-
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1046
|
-
enableObservation:args.enableObservation,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
|
|
1083
|
+
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1084
|
+
enableObservation:args.enableObservation,enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
|
|
1047
1085
|
console.log(`R27_SLOT_UPDATE_PASS version=${args.expectedVersion}`);
|
|
1048
1086
|
console.log(`UPDATE_ID ${result.update_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
|
|
1049
1087
|
console.log(`STATE_PATH ${result.state_path}`); console.log(`ACTIVE_RELEASE ${result.pointer.release_id}`);
|