deadbyte-mcp 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +126 -54
- package/R41-PARENT.json +15 -0
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +12 -8
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +112 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/init-desktop-policy-r40.ps1 +85 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/src/autonomous-context-engine.mjs +3 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +24 -0
- package/src/autonomous-runtime.mjs +164 -25
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +87 -19
- package/src/memory-r41.mjs +692 -0
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +86 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +15 -15
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/r41-finalization.test.mjs +38 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +20 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
package/src/tool-audit.mjs
CHANGED
|
@@ -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:[],
|
|
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
|
-
|
|
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
|
@@ -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.
|
|
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('
|
|
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
|
+
});
|
package/test/core.test.mjs
CHANGED
|
@@ -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.
|
|
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}$/);
|