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.
Files changed (127) hide show
  1. package/MANIFEST.SHA256 +126 -54
  2. package/R41-PARENT.json +15 -0
  3. package/README.txt +5 -4
  4. package/acceptance/r40/desktop-live-drift.mjs +106 -0
  5. package/acceptance/r40/desktop-live-ground.mjs +110 -0
  6. package/acceptance/r40/desktop-live-input.mjs +128 -0
  7. package/acceptance/r40/desktop-live-observe.mjs +100 -0
  8. package/acceptance/r40/desktop-live-uia.mjs +121 -0
  9. package/bin/appcontainer-stage.obj +0 -0
  10. package/bin/bootstrap-advapi32.obj +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.obj +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.obj +0 -0
  15. package/bin/contained-reverse-worker.obj +0 -0
  16. package/bin/contained-transform-worker.obj +0 -0
  17. package/bin/containment-probe.obj +0 -0
  18. package/bin/deadbyte-contain.obj +0 -0
  19. package/bin/deadbyte-desktop-input-r40.exe +0 -0
  20. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  21. package/bin/deadbyte-desktop-observer-r40.exe +0 -0
  22. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  23. package/bin/deadbyte-desktop-uia-r40.exe +0 -0
  24. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.obj +0 -0
  27. package/bin/deadbyte-tunnel-host.obj +0 -0
  28. package/controller/README.TXT +7 -5
  29. package/controller/deadbyte-controller.config.psd1.example +3 -0
  30. package/controller/deadbyte-controller.ps1 +149 -4
  31. package/controller/deadbyte-desktop-policy.json +41 -0
  32. package/controller/deadbyte-process-policy.json +4 -4
  33. package/docs/ARCHITECTURE.md +6 -1
  34. package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
  35. package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
  36. package/native/deadbyte-desktop-input.cpp +455 -0
  37. package/native/deadbyte-desktop-observer.cpp +253 -0
  38. package/native/deadbyte-desktop-uia.cpp +307 -0
  39. package/package.json +12 -8
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/build-containment.ps1 +24 -14
  42. package/scripts/build-desktop-r40.ps1 +112 -0
  43. package/scripts/deferred-slot-operation-r40.mjs +126 -0
  44. package/scripts/deferred-slot-operation-r41.mjs +127 -0
  45. package/scripts/final-closure-r40.mjs +114 -0
  46. package/scripts/final-closure-r41.mjs +116 -0
  47. package/scripts/final-closure-verify-r40.mjs +212 -0
  48. package/scripts/final-closure-verify-r41.mjs +215 -0
  49. package/scripts/gate-windows-r40.ps1 +43 -0
  50. package/scripts/gate-windows-r41.ps1 +48 -0
  51. package/scripts/init-desktop-policy-r40.ps1 +85 -0
  52. package/scripts/r40-compact-ledger-probe.mjs +4 -0
  53. package/scripts/release-parity-r40.mjs +212 -0
  54. package/scripts/release-parity-r41.mjs +221 -0
  55. package/scripts/release-parity-tests-r40.ps1 +9 -0
  56. package/scripts/release-parity-tests-r41.ps1 +9 -0
  57. package/scripts/verify-memory-r41.mjs +88 -0
  58. package/scripts/verify-r41-parent.mjs +61 -0
  59. package/scripts/windows-gate-evidence-r40.mjs +52 -0
  60. package/scripts/windows-gate-evidence-r41.mjs +53 -0
  61. package/src/autonomous-context-engine.mjs +3 -1
  62. package/src/autonomous-planner-contracts.mjs +19 -1
  63. package/src/autonomous-planner.mjs +24 -0
  64. package/src/autonomous-runtime.mjs +164 -25
  65. package/src/capability-ledger.mjs +6 -1
  66. package/src/compact-gateway-mcp-tools.mjs +14 -2
  67. package/src/deadbyte-cli.mjs +72 -44
  68. package/src/desktop-contract.mjs +263 -0
  69. package/src/desktop-input-r40.mjs +148 -0
  70. package/src/desktop-mcp-tools-r40.mjs +126 -0
  71. package/src/desktop-memory-r40.mjs +272 -0
  72. package/src/desktop-observer-r40.mjs +231 -0
  73. package/src/desktop-policy-r40.mjs +110 -0
  74. package/src/desktop-runtime-r40.mjs +318 -0
  75. package/src/desktop-uia-r40.mjs +123 -0
  76. package/src/mcp-server.mjs +87 -19
  77. package/src/memory-r41.mjs +692 -0
  78. package/src/r40-compact-disarmed-probe.mjs +71 -0
  79. package/src/r40-full-surface-probe.mjs +34 -0
  80. package/src/release-generation.mjs +38 -5
  81. package/src/remote-console.mjs +41 -0
  82. package/src/remote-live-log.mjs +208 -45
  83. package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
  84. package/src/remote-mcp-r40-parity-client.mjs +200 -0
  85. package/src/remote-mcp-smoke-client.mjs +20 -6
  86. package/src/remote-result-journal.mjs +10 -2
  87. package/src/runtime-update.mjs +55 -17
  88. package/src/tool-audit.mjs +29 -2
  89. package/src/version.mjs +1 -1
  90. package/test/autonomous-capability-r36.test.mjs +43 -1
  91. package/test/autonomous-desktop-r40.test.mjs +188 -0
  92. package/test/capability-ledger-r36.test.mjs +1 -0
  93. package/test/cli-entrypoint.test.mjs +1 -1
  94. package/test/compact-catalog-r36.test.mjs +2 -2
  95. package/test/compact-gateway-r36.test.mjs +56 -4
  96. package/test/compact-max-catalog-r40.test.mjs +95 -0
  97. package/test/controller-desktop-r40.test.mjs +86 -0
  98. package/test/core.test.mjs +1 -1
  99. package/test/desktop-adversarial-r40.test.mjs +62 -0
  100. package/test/desktop-contract-r40.test.mjs +200 -0
  101. package/test/desktop-input-r40.test.mjs +63 -0
  102. package/test/desktop-mcp-tools-r40.test.mjs +61 -0
  103. package/test/desktop-memory-r40.test.mjs +141 -0
  104. package/test/desktop-observer-r40.test.mjs +119 -0
  105. package/test/desktop-policy-r40.test.mjs +111 -0
  106. package/test/desktop-runtime-r40.test.mjs +278 -0
  107. package/test/desktop-uia-r40.test.mjs +42 -0
  108. package/test/full-max-catalog-r40.test.mjs +91 -0
  109. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  110. package/test/memory-autonomous-integration-r41.test.mjs +31 -0
  111. package/test/memory-history-r41.test.mjs +126 -0
  112. package/test/memory-projections-r41.test.mjs +92 -0
  113. package/test/memory-retrieval-r41.test.mjs +124 -0
  114. package/test/memory-snapshot-r41.test.mjs +106 -0
  115. package/test/r33-closeout-regression.test.mjs +7 -7
  116. package/test/r33-finalization.test.mjs +15 -15
  117. package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
  118. package/test/r40-finalization.test.mjs +118 -0
  119. package/test/r41-finalization.test.mjs +38 -0
  120. package/test/release-generation.test.mjs +41 -9
  121. package/test/release-version.test.mjs +20 -61
  122. package/test/remote-console-r40.test.mjs +38 -0
  123. package/test/remote-live-log-r37.test.mjs +89 -32
  124. package/test/remote-r24-catalog.test.mjs +12 -3
  125. package/test/remote-result-journal.test.mjs +15 -0
  126. package/test/remote-session-cli.test.mjs +69 -46
  127. 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 compactProfile = JSON.stringify(names) === JSON.stringify(r36CompactTools);
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
- if (manifest.schema !== 'deadbyte.capability-manifest.v1' || manifest.profile !== 'compact' ||
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('R36 compact capability manifest malformed');
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(`R36 compact capability route missing: ${String(item.id)}`);
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('R36 compact machine ping binding mismatch');
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);
@@ -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
- const codingPolicy=await readJson(codingPath); const autonomousPolicy=await readJson(autonomousPath); const hostPolicy=hostPath?await readJson(hostPath):null; const processPolicy=processPath?await readJson(processPath):null;
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
- final:{coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
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}`);