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
@@ -82,15 +82,42 @@ function outcomeReceiptHashes(result){
82
82
  visit(result?.structuredContent??result);
83
83
  return [...found].sort();
84
84
  }
85
+ function safeOutcomeReason(value){
86
+ if(value===null||value===undefined) return null;
87
+ let text=typeof value==='string'?value:(value instanceof Error?String(value.message??value):null);
88
+ if(text===null) return null;
89
+ text=text.replace(/[\\r\\n\\t]+/g,' ').replace(/\\s{2,}/g,' ').trim();
90
+ if(!text) return null;
91
+ const originalBytes=Buffer.from(text,'utf8');
92
+ if(secretValuePattern.test(text)) return `<redacted reason bytes=${originalBytes.length} sha256=${sha256(originalBytes)}>`;
93
+ const max=240;
94
+ if(text.length>max) text=`${text.slice(0,max-1)}…`;
95
+ return text;
96
+ }
97
+ function resultOutcomeReason(result){
98
+ const structured=result?.structuredContent;
99
+ const candidates=[
100
+ structured?.error,structured?.message,structured?.reason,structured?.result?.error,structured?.result?.message,
101
+ ...(Array.isArray(result?.content)?result.content.filter(item=>item?.type==='text').map(item=>item.text):[])
102
+ ];
103
+ for(const candidate of candidates){
104
+ const reason=safeOutcomeReason(candidate);
105
+ if(reason) return reason;
106
+ }
107
+ return null;
108
+ }
85
109
  function summarizeOutcome(result,error,durationMs){
86
110
  const duration=Math.max(0,Math.round(Number(durationMs)||0));
87
111
  if(error){
88
112
  const message=String(error?.message??error);
89
- return {status:'threw',is_error:true,duration_ms:duration,receipt_sha256s:[],error:{name:String(error?.name||'Error').slice(0,128),byte_count:Buffer.byteLength(message,'utf8'),sha256:sha256(Buffer.from(message,'utf8'))}};
113
+ return {status:'threw',is_error:true,duration_ms:duration,receipt_sha256s:[],reason:safeOutcomeReason(message),
114
+ error:{name:String(error?.name||'Error').slice(0,128),byte_count:Buffer.byteLength(message,'utf8'),sha256:sha256(Buffer.from(message,'utf8'))}};
90
115
  }
91
116
  const structured=result?.structuredContent;
92
117
  const status=typeof structured?.status==='string'?structured.status:(result?.isError===true?'error':'ok');
93
- return {status:String(status).slice(0,128),is_error:result?.isError===true,duration_ms:duration,receipt_sha256s:outcomeReceiptHashes(result)};
118
+ const isError=result?.isError===true;
119
+ return {status:String(status).slice(0,128),is_error:isError,duration_ms:duration,receipt_sha256s:outcomeReceiptHashes(result),
120
+ ...(isError?{reason:resultOutcomeReason(result)}:{})};
94
121
  }
95
122
 
96
123
  async function appendSynced(file,line){
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.11.3';
1
+ export const DEADBYTE_VERSION = '0.13.0';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -32,7 +32,11 @@ const typedCases=[
32
32
  ['machine_fs_delete',{kind:'machine_fs_delete',path:'empty',expected_source_identity:H}],
33
33
  ['observation_search_start',{kind:'observation_search_start',path:'.',pattern:'FAIL',case_sensitive:false,max_results:100,max_files:1000,max_bytes:1048576,max_file_bytes:262144}],
34
34
  ['observation_search_page',{kind:'observation_search_page',search_id:H,cursor:null,limit:50}],
35
- ['observation_system_status',{kind:'observation_system_status'}]
35
+ ['observation_system_status',{kind:'observation_system_status'}],
36
+ ['desktop_status',{kind:'desktop_status'}],
37
+ ['desktop_observe',{kind:'desktop_observe'}],
38
+ ['desktop_target',{kind:'desktop_target',frame_id:H,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}],
39
+ ['desktop_action',{kind:'desktop_action',action_kind:'click_primary',expected_frame_id:H,target_id:null,point:{x:10,y:20},scroll_delta:null,key_chord:null,text:null}]
36
40
  ];
37
41
 
38
42
  test('R36 planner parses first-class capability actions without delegate wrapper',()=>{
@@ -44,6 +48,20 @@ test('R36 planner parses first-class capability actions without delegate wrapper
44
48
  }
45
49
  });
46
50
 
51
+ test('R40 T7 planner rejects ambiguous Desktop actions before runtime',()=>{
52
+ assert.throws(()=>parseR34PlannerDecision(wrap({
53
+ kind:'desktop_action',action_kind:'click_primary',expected_frame_id:H,
54
+ target_id:H,point:{x:1,y:2},scroll_delta:null,key_chord:null,text:null
55
+ }),policy),/exactly one/);
56
+ assert.throws(()=>parseR34PlannerDecision(wrap({
57
+ kind:'desktop_action',action_kind:'key_chord',expected_frame_id:H,
58
+ target_id:null,point:null,scroll_delta:null,key_chord:['CTRL','CTRL'],text:null
59
+ }),policy),/duplicate/);
60
+ assert.throws(()=>parseR34PlannerDecision(wrap({
61
+ kind:'desktop_target',frame_id:H,uia_query:{kind:'root',value:'not-empty',max_results:1,ordinal:0}
62
+ }),policy),/root query value/);
63
+ });
64
+
47
65
  test('R36 legacy delegate is accepted only for a known operation and normalized into typed action',()=>{
48
66
  const parsed=parseR34PlannerDecision(wrap({kind:'delegate',operation:{kind:'process_status',session_id:H}}),policy);
49
67
  assert.equal(parsed.action.kind,'process_status');
@@ -62,6 +80,30 @@ test('R36 planner prompt exposes configured capability manifest and never advert
62
80
  assert.doesNotMatch(prompt,/host\.exec|host_file|host authority/i);
63
81
  });
64
82
 
83
+ test('R40 T6 autonomous planner receives bounded Desktop memory projection as context only',async()=>{
84
+ let calls=0;
85
+ const desktopMemoryProvider={plannerContext:async()=>{
86
+ calls++;
87
+ return Object.freeze({
88
+ schema:'deadbyte.desktop-memory-planner.v1',
89
+ snapshot_sha256:'1'.repeat(64),memory_sha256:'2'.repeat(64),policy_sha256:'3'.repeat(64),
90
+ source_receipt_count:7,last_verified_desktop:{frame_id:'4'.repeat(64),desktop_epoch:'5'.repeat(64),topology_sha256:'6'.repeat(64)},
91
+ windows:[],grounded_targets:[],rejected_target_fingerprints:[],recent_effects:[],unresolved_unknown_effects:[]
92
+ });
93
+ }};
94
+ const f=await createR34Fixture({desktopMemoryProvider});
95
+ try{
96
+ const goal=await f.runtime.submit({submissionId:'r40-t6-memory',rootId:'project',title:'desktop memory context',goal:'inspect project',acceptance:'planner sees authenticated desktop summary'});
97
+ const result=await f.runtime.run({goalId:goal.goal_id,maxCycles:1});
98
+ assert.equal(result.status,'input_required');
99
+ assert.equal(calls,1);
100
+ assert.match(result.planner_request.prompt,/deadbyte\.desktop-memory-planner\.v1/);
101
+ assert.match(result.planner_request.prompt,/"desktop_memory"/);
102
+ assert.match(result.planner_request.prompt,/"source_receipt_count":7/);
103
+ assert.doesNotMatch(result.planner_request.prompt,/data_base64|authority_instance_nonce|SECRET/);
104
+ }finally{await f.cleanup();}
105
+ });
106
+
65
107
  test('R36 progress fingerprint ignores rationale but binds capability, evidence, session and cursor identity',()=>{
66
108
  const base={before:{mutation_epoch:2},after:{mutation_epoch:2},decision:{kind:'process_read',rationale:'first'},evidence:{capability_id:'process.read',session_id:H,cursor:10,observation_sha256:'b'.repeat(64),receipt_sha256:'c'.repeat(64)}};
67
109
  const a=judgeProgress(base);
@@ -0,0 +1,188 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { createR34Fixture,sendPlannerDecision,inspectDecision } from './helpers/autonomous-r34-fixture.mjs';
5
+
6
+ const H=v=>createHash('sha256').update(String(v)).digest('hex');
7
+ const FRAME=H('desktop-frame');
8
+ const GROUNDED_FRAME=H('desktop-grounded-frame');
9
+ const TARGET=H('desktop-target');
10
+ const POLICY=H('desktop-policy');
11
+ const WIN={hwnd:'0x1234',pid:77,process_image_sha256:H('desktop-app'),title_sha256:H('desktop-title')};
12
+
13
+ function fakeDesktopRuntime({armed=false,unknown=false}={}){
14
+ const calls={status:0,observe:0,ground:0,action:0,verify:0};
15
+ const receipts=new Map();
16
+ let seq=0;
17
+ const issue=kind=>{
18
+ const receipt_id=(++seq).toString(16).padStart(24,'0');
19
+ const receipt_sha256=H(kind+':'+receipt_id);
20
+ receipts.set(receipt_id,{ok:true,receipt_id,receipt_sha256,kind});
21
+ return {receipt_id,receipt_sha256};
22
+ };
23
+ const frame=id=>({frame_id:id,desktop_epoch:H('epoch'),topology_sha256:H('topology'),
24
+ observed_at_utc:'2026-09-19T10:00:00.000Z',bounds:{left:0,top:0,width:1920,height:1080},
25
+ active_window:WIN,image_sha256:H('image:'+id)});
26
+ return {
27
+ calls,
28
+ async status(){calls.status++;return {status:'ok',configured:true,armed,reason:armed?'standing-grant':'disarmed',
29
+ action_kinds:armed?['click_primary']:[],policy_sha256:POLICY,observer_backend:'fake'};},
30
+ async observe(){calls.observe++;const r=issue('desktop_observation');return {status:'ok',frame:frame(FRAME),backend:'fake',byte_length:4,...r};},
31
+ async ground({frameId,query}){calls.ground++;assert.equal(frameId,FRAME);assert.equal(query.kind,'root');
32
+ const r=issue('desktop_grounding');return {status:'ok',frame:frame(GROUNDED_FRAME),
33
+ target:{target_id:TARGET,frame_id:GROUNDED_FRAME,rect:{left:100,top:100,width:200,height:80},source:'uia'},
34
+ grounding:{query_sha256:H('query'),query_kind:'root',match_count:1,ordinal:0,provider_receipt_sha256:H('provider'),...r}};},
35
+ async action(args){calls.action++;
36
+ assert.equal(args.effectContext.goalId.length,64);
37
+ if(unknown){
38
+ assert.equal(args.expectedFrameId,FRAME);assert.equal(args.effectContext.observationHeadSha256,FRAME);
39
+ const e=new Error('transport lost after SendInput');e.side_effect_state='unknown';const r=issue('desktop_action_outcome_unknown');
40
+ e.receipt_id=r.receipt_id;e.receipt_sha256=r.receipt_sha256;throw e;
41
+ }
42
+ assert.equal(args.expectedFrameId,GROUNDED_FRAME);assert.equal(args.targetId,TARGET);
43
+ assert.equal(args.effectContext.observationHeadSha256,GROUNDED_FRAME);
44
+ const started=issue('desktop_action_started'),post=issue('desktop_post_observation'),done=issue('desktop_action_completed');
45
+ return {status:'ok',injected:true,side_effect_state:'confirmed_injected',
46
+ action:{intent_id:args.intentId,kind:'click_primary',frame_id:GROUNDED_FRAME,desktop_epoch:H('epoch'),
47
+ topology_sha256:H('topology'),expected_window:WIN,target_id:TARGET,point:{x:200,y:140},
48
+ scroll_delta:null,key_chord:null,text:null,text_sha256:null,action_sha256:H('action')},
49
+ guard:{guard_sha256:H('guard')},post_observation:{change_observed:true,window_changed:false,
50
+ post_observation_sha256:H('post-observation')},action_chain:{chain_sha256:H('chain')},
51
+ effect_id:H('effect'),effect_fingerprint:H('effect-fp'),effect_completion:{schema:'deadbyte.effect-completion.v1',result_sha256:H('effect-result')},
52
+ started_receipt_id:started.receipt_id,started_receipt_sha256:started.receipt_sha256,
53
+ post_receipt_id:post.receipt_id,post_receipt_sha256:post.receipt_sha256,
54
+ receipt_id:done.receipt_id,receipt_sha256:done.receipt_sha256};},
55
+ async verifyReceipt({receiptId}){calls.verify++;const r=receipts.get(receiptId);if(!r)throw new Error('unknown fake receipt');return r;}
56
+ };
57
+ }
58
+
59
+ const plan=steps=>({schema:'deadbyte.autonomous-decision.v2',kind:'plan_create',title:'Desktop flow',
60
+ reason:'prove bounded desktop loop',steps:steps.map(step_id=>({step_id,intent:step_id}))});
61
+ const complete=(step_id,tag)=>({schema:'deadbyte.autonomous-decision.v2',kind:'step_complete',step_id,
62
+ evidence:[{kind:'desktop',id:tag,sha256:H(tag)}]});
63
+
64
+ async function toAct(f,goal,result){
65
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,inspectDecision());
66
+ assert.equal(result.loop.phase,'ACTING');
67
+ return result;
68
+ }
69
+
70
+ test('T7 capability manifest exposes Desktop only when runtime exists and never Host',async()=>{
71
+ const d=fakeDesktopRuntime();
72
+ const f=await createR34Fixture({desktopRuntime:d});
73
+ try{
74
+ const goal=await f.runtime.submit({submissionId:'r40-t7-manifest',rootId:'project',title:'desktop manifest',goal:'inspect desktop',acceptance:'desktop capability visible'});
75
+ const result=await f.runtime.run({goalId:goal.goal_id,maxCycles:1});
76
+ assert.equal(result.status,'input_required');
77
+ assert.match(result.planner_request.prompt,/"id":"desktop\.observe"/);
78
+ assert.match(result.planner_request.prompt,/"id":"desktop\.action"/);
79
+ assert.match(result.planner_request.prompt,/"authority":"desktop"/);
80
+ assert.doesNotMatch(result.planner_request.prompt,/host\.exec|host_file/);
81
+ }finally{await f.cleanup();}
82
+ });
83
+
84
+ test('T7 read-only Desktop observe works while Desktop mutation authority is DISARMED',async()=>{
85
+ const d=fakeDesktopRuntime({armed:false});
86
+ const f=await createR34Fixture({desktopRuntime:d});
87
+ try{
88
+ const goal=await f.runtime.submit({submissionId:'r40-t7-observe',rootId:'project',title:'desktop observe',goal:'observe desktop',acceptance:'signed observation'});
89
+ let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
90
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe']));
91
+ result=await toAct(f,goal,result);
92
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
93
+ assert.equal(result.loop.phase,'VERIFYING');
94
+ assert.equal(d.calls.observe,1);assert.equal(d.calls.action,0);assert.equal(d.calls.verify,1);
95
+ const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
96
+ const completed=journal.events.find(e=>e.type==='delegated_action_completed'&&e.payload?.operation?.kind==='desktop_observe');
97
+ assert.equal(completed.payload.capability_id,'desktop.observe');
98
+ assert.equal(completed.payload.evidence.kind,'desktop_receipts');
99
+ }finally{await f.cleanup();}
100
+ });
101
+
102
+ test('T7 Autonomous authority cannot substitute for missing Desktop mutation grant',async()=>{
103
+ const d=fakeDesktopRuntime({armed:false});
104
+ const f=await createR34Fixture({desktopRuntime:d});
105
+ try{
106
+ const goal=await f.runtime.submit({submissionId:'r40-t7-disarmed',rootId:'project',title:'desktop disarmed',goal:'click only if separately authorized',acceptance:'no input without Desktop grant'});
107
+ let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
108
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['act']));
109
+ result=await toAct(f,goal,result);
110
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
111
+ kind:'desktop_action',action_kind:'click_primary',expected_frame_id:FRAME,target_id:null,point:{x:20,y:20},
112
+ scroll_delta:null,key_chord:null,text:null
113
+ }});
114
+ assert.equal(result.status,'paused');
115
+ assert.equal(result.reason,'desktop_authority_unavailable');
116
+ assert.equal(d.calls.action,0);
117
+ const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
118
+ assert.ok(journal.events.some(e=>e.type==='waiting_entered'&&e.payload?.reason==='desktop_authority_unavailable'));
119
+ assert.equal(journal.events.some(e=>e.type==='delegated_action_started'&&e.payload?.operation?.kind==='desktop_action'),false);
120
+ }finally{await f.cleanup();}
121
+ });
122
+
123
+ test('T7 armed flow binds observe to UIA target to one Desktop action and verifies every Desktop receipt',async()=>{
124
+ const d=fakeDesktopRuntime({armed:true});
125
+ const f=await createR34Fixture({desktopRuntime:d});
126
+ try{
127
+ const goal=await f.runtime.submit({submissionId:'r40-t7-flow',rootId:'project',title:'desktop flow',goal:'observe target click',acceptance:'one receipt-verified action'});
128
+ let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
129
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe','target','act']));
130
+
131
+ result=await toAct(f,goal,result);
132
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
133
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('observe','observe-proof'));
134
+
135
+ result=await toAct(f,goal,result);
136
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
137
+ kind:'desktop_target',frame_id:FRAME,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}
138
+ }});
139
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('target','target-proof'));
140
+
141
+ result=await toAct(f,goal,result);
142
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
143
+ kind:'desktop_action',action_kind:'click_primary',expected_frame_id:GROUNDED_FRAME,target_id:TARGET,point:null,
144
+ scroll_delta:null,key_chord:null,text:null
145
+ }});
146
+ assert.equal(result.loop.phase,'VERIFYING');
147
+ assert.equal(d.calls.observe,1);assert.equal(d.calls.ground,1);assert.equal(d.calls.action,1);
148
+ assert.equal(d.calls.verify,5);
149
+
150
+ const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
151
+ const completed=journal.events.filter(e=>e.type==='delegated_action_completed'&&String(e.payload?.operation?.kind).startsWith('desktop_'));
152
+ assert.deepEqual(completed.map(e=>e.payload.operation.kind),['desktop_observe','desktop_target','desktop_action']);
153
+ const action=completed.at(-1);
154
+ assert.equal(action.payload.capability_id,'desktop.action');
155
+ assert.equal(action.payload.evidence.kind,'desktop_receipts');
156
+ assert.deepEqual(action.payload.evidence.receipts.map(x=>x.kind),[
157
+ 'desktop_action_started','desktop_post_observation','desktop_action_completed'
158
+ ]);
159
+ assert.equal(Object.hasOwn(action.payload.result.action_summary,'text'),false);
160
+ }finally{await f.cleanup();}
161
+ });
162
+
163
+ test('T7 UNKNOWN Desktop outcome pauses for critic/replan and is never replayed',async()=>{
164
+ const d=fakeDesktopRuntime({armed:true,unknown:true});
165
+ const f=await createR34Fixture({desktopRuntime:d});
166
+ try{
167
+ const goal=await f.runtime.submit({submissionId:'r40-t7-unknown',rootId:'project',title:'desktop unknown',goal:'never replay unknown',acceptance:'pause on uncertain side effect'});
168
+ let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
169
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe','act']));
170
+ result=await toAct(f,goal,result);
171
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
172
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('observe','observe-proof-unknown'));
173
+ result=await toAct(f,goal,result);
174
+ result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
175
+ kind:'desktop_action',action_kind:'click_primary',expected_frame_id:FRAME,target_id:null,point:{x:40,y:40},
176
+ scroll_delta:null,key_chord:null,text:null
177
+ }});
178
+ assert.equal(result.status,'paused');
179
+ assert.equal(result.reason,'desktop_outcome_unknown');
180
+ assert.equal(d.calls.action,1);
181
+ const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
182
+ assert.ok(journal.events.some(e=>e.type==='waiting_entered'&&e.payload?.reason==='desktop_outcome_unknown'&&e.payload?.resume_phase==='CRITIC'));
183
+ assert.ok(journal.events.some(e=>e.type==='goal_paused'&&e.payload?.side_effect_state==='unknown'));
184
+ const again=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
185
+ assert.equal(again.status,'paused');
186
+ assert.equal(d.calls.action,1,'UNKNOWN action was replayed');
187
+ }finally{await f.cleanup();}
188
+ });
@@ -36,6 +36,7 @@ test('R36 capability ledger rejects duplicate ids', () => {
36
36
 
37
37
  test('R36 capability ledger validates authority and canonicalizes identity', () => {
38
38
  assert.throws(() => createCapabilityLedger([entry({authority:'root'})]), /authority/i);
39
+ assert.doesNotThrow(()=>createCapabilityLedger([entry({authority:'desktop'})]));
39
40
  const a=createCapabilityLedger([
40
41
  entry({id:'process.read',full_tool:'process_session_read',compact_tool:'process_session',compact_op:'read',runtime_flag:'DEADBYTE_PROCESS_RUNTIME',authority:'process'}),
41
42
  entry({})
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
21
21
  for(const arg of ['--version','-v','version']){
22
22
  const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
23
23
  assert.equal(result.status,0,result.stderr);
24
- assert.equal(result.stdout.trim(),'0.11.3');
24
+ assert.equal(result.stdout.trim(),'0.13.0');
25
25
  }
26
26
  });
27
27
 
@@ -8,7 +8,7 @@ import path from 'node:path';
8
8
  const here=path.dirname(fileURLToPath(import.meta.url));
9
9
  const repoRoot=path.resolve(here,'..');
10
10
  const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
11
- const gatewayNames=['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'];
11
+ const gatewayNames=['capability_manifest','coding_inspect','desktop_control','machine_fs','machine_observe','process_session'];
12
12
 
13
13
  test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async()=>{
14
14
  const client=new Client({name:'deadbyte-r36-compact-test',version:'0.10.0-dev'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
@@ -17,7 +17,7 @@ test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async(
17
17
  DEADBYTE_CAPABILITY_PROFILE:'compact',
18
18
  DEADBYTE_AUDIT_RUNTIME:'0',DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_AGENT_RUNTIME:'0',
19
19
  DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_CODING_RUNTIME:'0',DEADBYTE_MACHINE_FS_RUNTIME:'0',
20
- DEADBYTE_OBSERVATION_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0'
20
+ DEADBYTE_OBSERVATION_RUNTIME:'0',DEADBYTE_DESKTOP_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0'
21
21
  };
22
22
  const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
23
23
  try{
@@ -7,6 +7,7 @@ import {
7
7
  machineObserveInputSchema,
8
8
  registerCompactGatewayTools
9
9
  } from '../src/compact-gateway-mcp-tools.mjs';
10
+ import { desktopControlInputSchema } from '../src/desktop-mcp-tools-r40.mjs';
10
11
 
11
12
  const fakeServer=()=>{
12
13
  const tools=new Map();
@@ -24,6 +25,7 @@ test('R36 compact gateway schemas reject generic arbitrary dispatch',()=>{
24
25
  assert.equal(machineFsGatewayInputSchema.safeParse({op:'exec',command:'whoami'}).success,false);
25
26
  assert.equal(processSessionGatewayInputSchema.safeParse({op:'stdin',session_id:'0'.repeat(64),input:'raw'}).success,false);
26
27
  assert.equal(machineObserveInputSchema.safeParse({op:'revoke',reason:'no'}).success,false);
28
+ assert.equal(desktopControlInputSchema.safeParse({op:'call',tool_name:'host_exec'}).success,false);
27
29
  });
28
30
 
29
31
  test('R36 compact schemas preserve representative preconditions',()=>{
@@ -36,14 +38,15 @@ test('R36 compact schemas preserve representative preconditions',()=>{
36
38
  assert.equal(processSessionGatewayInputSchema.safeParse({op:'action',session_id:'b'.repeat(64),action_id:'ping'}).success,true);
37
39
  });
38
40
 
39
- test('R36 registers exactly five compact gateway names',()=>{
41
+ test('R40 registers exactly six compact gateway names including desktop_control',()=>{
40
42
  const server=fakeServer();
41
43
  registerCompactGatewayTools(server,{
42
44
  profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
43
- codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null
45
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
46
+ catalogProvider:()=>[...server.tools.keys()]
44
47
  });
45
48
  assert.deepEqual([...server.tools.keys()].sort(),[
46
- 'capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'
49
+ 'capability_manifest','coding_inspect','desktop_control','machine_fs','machine_observe','process_session'
47
50
  ]);
48
51
  });
49
52
 
@@ -55,7 +58,8 @@ test('R36 coding_inspect gateway calls runtime directly and never MCP-recurses',
55
58
  const server=fakeServer();
56
59
  registerCompactGatewayTools(server,{
57
60
  profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
58
- codingRuntime,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null
61
+ codingRuntime,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,
62
+ catalogProvider:()=>[...server.tools.keys()]
59
63
  });
60
64
  const tool=server.tools.get('coding_inspect');
61
65
  const result=await tool.handler({op:'stat',root_id:'core',path:'src/a.mjs',hash:false});
@@ -64,3 +68,51 @@ test('R36 coding_inspect gateway calls runtime directly and never MCP-recurses',
64
68
  assert.equal(result.structuredContent.op,'stat');
65
69
  assert.equal(result.structuredContent.result.path,'src/a.mjs');
66
70
  });
71
+
72
+ test('R40 capability manifest v2 attests the exact registered compact catalog',async()=>{
73
+ const server=fakeServer();
74
+ registerCompactGatewayTools(server,{
75
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'a'.repeat(64)},
76
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
77
+ catalogProvider:()=>[...server.tools.keys()]
78
+ });
79
+ const result=await server.tools.get('capability_manifest').handler({});
80
+ const manifest=result.structuredContent.result;
81
+ assert.equal(manifest.schema,'deadbyte.capability-manifest.v2');
82
+ assert.equal(manifest.profile,'compact');
83
+ assert.deepEqual(manifest.tool_catalog,[...server.tools.keys()].sort());
84
+ assert.equal(manifest.tool_count,server.tools.size);
85
+ assert.match(manifest.catalog_sha256,/^[0-9a-f]{64}$/);
86
+ });
87
+
88
+
89
+ test('R40 desktop_control is stable when runtime is absent and routes typed read-only operations when present',async()=>{
90
+ const server=fakeServer();
91
+ const calls=[];
92
+ const desktopRuntime={
93
+ status:async()=>{calls.push('status');return {status:'ok',configured:true,armed:false,reason:'disarmed',action_kinds:[],policy_sha256:'a'.repeat(64),observer_backend:'fake'};},
94
+ observe:async()=>{calls.push('observe');return {status:'ok',frame:{frame_id:'b'.repeat(64)}};}
95
+ };
96
+ registerCompactGatewayTools(server,{
97
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
98
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime,
99
+ catalogProvider:()=>[...server.tools.keys()]
100
+ });
101
+ const tool=server.tools.get('desktop_control');
102
+ const status=await tool.handler({op:'status'});
103
+ assert.equal(status.isError,false);
104
+ assert.equal(status.structuredContent.result.armed,false);
105
+ const observed=await tool.handler({op:'observe'});
106
+ assert.equal(observed.structuredContent.result.frame.frame_id,'b'.repeat(64));
107
+ assert.deepEqual(calls,['status','observe']);
108
+
109
+ const absent=fakeServer();
110
+ registerCompactGatewayTools(absent,{
111
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
112
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
113
+ catalogProvider:()=>[...absent.tools.keys()]
114
+ });
115
+ const unavailable=await absent.tools.get('desktop_control').handler({op:'status'});
116
+ assert.equal(unavailable.isError,true);
117
+ assert.equal(unavailable.structuredContent.status,'unavailable');
118
+ });
@@ -0,0 +1,95 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
10
+
11
+ const here=path.dirname(fileURLToPath(import.meta.url));
12
+ const repoRoot=path.resolve(here,'..');
13
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
14
+ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy.json');
15
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R40 real max-runtime compact stdio catalog is exactly 42 while all authority remains disarmed',async()=>{
19
+ const f=await createR34Fixture({authority:'disarmed'});
20
+ const processPolicyPath=path.join(f.root,'process-policy.json');
21
+ const processGrant=path.join(f.root,'process-grant.json');
22
+ const processState=path.join(f.root,'process-state');
23
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
24
+ const hostArm=path.join(f.root,'host-arm.json');
25
+ const nodeSha=sha(await readFile(process.execPath));
26
+ await mkdir(processState,{recursive:true});
27
+ await writeFile(processPolicyPath,JSON.stringify({
28
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
29
+ native_host:{path:process.execPath,sha256:nodeSha},
30
+ roots:{core:{path:f.project,deny_paths:[]}},
31
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],
32
+ prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,
33
+ max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],
34
+ input_actions:{ping:'PING\r\n'}}}
35
+ },null,2));
36
+ await writeFile(hostPolicyPath,JSON.stringify({
37
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
38
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
39
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,
40
+ root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
41
+ },null,2));
42
+
43
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
44
+ const desktopGrant=desktopRaw.grant_file;
45
+ const codingLease=f.codingPolicy.lease_file;
46
+ const autonomousLease=f.policy.lease_file;
47
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
48
+
49
+ const client=new Client({name:'deadbyte-r40-max-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
50
+ const env={
51
+ ...getDefaultEnvironment(),
52
+ DEADBYTE_CAPABILITY_PROFILE:'compact',
53
+ DEADBYTE_AUDIT_RUNTIME:'0',
54
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),
55
+ DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,
57
+ DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
58
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,
59
+ DEADBYTE_MACHINE_RUNTIME:'1',
60
+ DEADBYTE_AGENT_RUNTIME:'0',
61
+ DEADBYTE_PROCESS_RUNTIME:'1',
62
+ DEADBYTE_PROCESS_POLICY:processPolicyPath,
63
+ DEADBYTE_CODING_RUNTIME:'1',
64
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),
65
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
66
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
67
+ DEADBYTE_DESKTOP_RUNTIME:'1',
68
+ DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
69
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
70
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
71
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
72
+ };
73
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
74
+ try{
75
+ await client.connect(transport);
76
+ const listed=await client.listTools();
77
+ const names=listed.tools.map(t=>t.name).sort();
78
+ assert.equal(names.length,42,'expected exact compact max catalog 42, got '+names.length+': '+names.join(','));
79
+ for(const name of ['capability_manifest','desktop_control','coding_inspect','machine_fs','machine_observe','process_session',
80
+ 'host_file_read','host_file_write','host_exec']) assert.ok(names.includes(name),'missing '+name);
81
+ const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
82
+ assert.equal(manifest.isError,false);
83
+ const m=manifest.structuredContent.result;
84
+ assert.equal(m.schema,'deadbyte.capability-manifest.v2');
85
+ assert.equal(m.profile,'compact');
86
+ assert.equal(m.tool_count,42);
87
+ assert.deepEqual(m.tool_catalog,names);
88
+ assert.match(m.catalog_sha256,/^[0-9a-f]{64}$/);
89
+ console.log(JSON.stringify({tool_count:m.tool_count,catalog_sha256:m.catalog_sha256,ledger_sha256:m.ledger_sha256,desktop:true,authority:'DISARMED'}));
90
+ } finally {
91
+ await client.close().catch(()=>{});
92
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
93
+ await f.cleanup();
94
+ }
95
+ });
@@ -0,0 +1,86 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const controller=fileURLToPath(new URL('../controller/deadbyte-controller.ps1',import.meta.url));
7
+ const example=fileURLToPath(new URL('../controller/deadbyte-controller.config.psd1.example',import.meta.url));
8
+ const init=fileURLToPath(new URL('../scripts/init-desktop-policy-r40.ps1',import.meta.url));
9
+
10
+ test('R40 controller exposes Desktop runtime config but never auto-arms it',async()=>{
11
+ const source=await readFile(controller,'utf8');
12
+ const config=await readFile(example,'utf8');
13
+ for(const name of ['armdesktop','disarmdesktop','desktopstatus']) assert.match(source,new RegExp(name));
14
+ assert.match(source,/DesktopRuntimeEnabled must be boolean/);
15
+ assert.match(source,/DesktopRuntimeEnabled requires DesktopPolicyFile/);
16
+ assert.match(source,/DEADBYTE_DESKTOP_RUNTIME/);
17
+ assert.match(source,/DEADBYTE_DESKTOP_POLICY/);
18
+ assert.match(config,/DesktopRuntimeEnabled = \$false/);
19
+ assert.match(config,/DesktopPolicyFile = ''/);
20
+ const start=source.slice(source.indexOf('function Start-Deadbyte'),source.indexOf('function Park-Deadbyte'));
21
+ assert.doesNotMatch(start,/Arm-Desktop/);
22
+ const dispatch=source.slice(source.indexOf('function Run-Command'),source.indexOf("if ($Command -ne 'menu')"));
23
+ assert.doesNotMatch(dispatch,/'start'[^\n]*Arm-Desktop/);
24
+ assert.doesNotMatch(dispatch,/'resume'[^\n]*Arm-Desktop/);
25
+ });
26
+
27
+ test('R40 Desktop standing grant is policy-digest and action-scope bound',async()=>{
28
+ const source=await readFile(controller,'utf8');
29
+ assert.match(source,/deadbyte\.desktop-policy\.v1/);
30
+ assert.match(source,/deadbyte\.desktop-grant\.v1/);
31
+ assert.match(source,/policy_sha256/);
32
+ assert.match(source,/instance_nonce/);
33
+ assert.match(source,/action_kinds/);
34
+ assert.match(source,/actions-outside-policy/);
35
+ assert.match(source,/desktop \$\(\$provider\.Name\) executable SHA-256 mismatch/);
36
+ assert.match(source,/Get-ExecutableHash \$provider\.Path/);
37
+ assert.match(source,/grant_file must live under controller runtime directory/);
38
+ assert.match(source,/evidence_dir must live under controller runtime directory/);
39
+ });
40
+
41
+ test('R40 Desktop environment reaches Bridge only and is restored after spawn',async()=>{
42
+ const source=await readFile(controller,'utf8');
43
+ assert.match(source,/\$env:DEADBYTE_DESKTOP_RUNTIME = if \(\[bool\]\$Cfg\.DesktopRuntimeEnabled\)/);
44
+ assert.match(source,/\$env:DEADBYTE_DESKTOP_POLICY = \[string\]\$Cfg\.DesktopPolicyFile/);
45
+ assert.match(source,/Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME/);
46
+ assert.match(source,/Remove-Item Env:DEADBYTE_DESKTOP_POLICY/);
47
+ assert.match(source,/\$env:DEADBYTE_DESKTOP_RUNTIME = \$previousDesktopRuntimeEnv/);
48
+ assert.match(source,/\$env:DEADBYTE_DESKTOP_POLICY = \$previousDesktopPolicyEnv/);
49
+ const supervisor=source.slice(source.indexOf("} elseif ($Name -eq 'Supervisor'"),source.indexOf('} else {',source.indexOf("} elseif ($Name -eq 'Supervisor'")));
50
+ assert.match(supervisor,/Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME/);
51
+ assert.match(supervisor,/Remove-Item Env:DEADBYTE_DESKTOP_POLICY/);
52
+ });
53
+
54
+ test('R40 lifecycle revokes Desktop before park stop restart and supervisor recovery',async()=>{
55
+ const source=await readFile(controller,'utf8');
56
+ const park=source.slice(source.indexOf('function Park-Deadbyte'),source.indexOf('function Stop-Deadbyte'));
57
+ const stop=source.slice(source.indexOf('function Stop-Deadbyte'),source.indexOf('function Get-HostPolicyMetadata'));
58
+ const recover=source.slice(source.indexOf('function Recover-AutonomousSupervisor'),source.indexOf('function Show-State'));
59
+ assert.match(park,/Disarm-Desktop \$Cfg -Quiet[\s\S]*Stop-OwnedComponent 'Bridge'/);
60
+ assert.match(stop,/Disarm-Desktop \$Cfg -Quiet[\s\S]*Stop-OwnedComponent 'Bridge'/);
61
+ assert.match(recover,/Disarm-Desktop \$Cfg -Quiet[\s\S]*Start-Deadbyte/);
62
+ assert.match(recover,/\$desktop = Get-DesktopGrantStatus \$Cfg/);
63
+ assert.match(recover,/\$desktop\.Armed/);
64
+ const dispatch=source.slice(source.indexOf('function Run-Command'),source.indexOf("if ($Command -ne 'menu')"));
65
+ assert.match(dispatch,/'restart'[^\n]*Stop-Deadbyte/);
66
+ });
67
+
68
+ test('R40 policy initializer pins current native observer and input and starts DISARMED',async()=>{
69
+ const source=await readFile(init,'utf8');
70
+ assert.match(source,/deadbyte-desktop-observer-r40\.exe/);
71
+ assert.match(source,/deadbyte-desktop-input-r40\.exe/);
72
+ assert.match(source,/deadbyte-desktop-uia-r40\.exe/);
73
+ assert.match(source,/System\.Security\.Cryptography\.SHA256/);
74
+ assert.match(source,/Get-Sha256Hex -Path \$Observer/);
75
+ assert.match(source,/Remove-Item -LiteralPath \$Grant/);
76
+ assert.match(source,/grant_state='DISARMED'/);
77
+ assert.match(source,/deadbyte\.desktop-policy\.v1/);
78
+ assert.match(source,/max_text_bytes=4096/);
79
+ });
80
+
81
+ test('R40 status exposes Desktop runtime and authority independently',async()=>{
82
+ const source=await readFile(controller,'utf8');
83
+ assert.match(source,/Desktop runtime: \{0\}/);
84
+ assert.match(source,/Desktop plane : \{0\}/);
85
+ assert.match(source,/Show-DesktopStatus \$Cfg/);
86
+ });
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.11.3');
108
+ assert.equal(result.receipt.runtime.package_version, '0.13.0');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);