deadbyte-mcp 0.11.2 → 0.12.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 +101 -39
- package/README.txt +10 -9
- 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/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/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/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -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 +10 -8
- package/scripts/build-desktop-r40.ps1 +106 -0
- package/scripts/deferred-slot-operation-r39.mjs +124 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/final-closure-r39.mjs +113 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-verify-r39.mjs +208 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/gate-windows-r39.ps1 +42 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/init-desktop-policy-r40.ps1 +79 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r39.mjs +200 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-tests-r39.ps1 +9 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/windows-gate-evidence-r39.mjs +52 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +22 -0
- package/src/autonomous-runtime.mjs +144 -24
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +79 -47
- 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 +74 -19
- 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 +215 -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 +85 -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/production-docs.test.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +8 -8
- package/test/r33-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +7 -6
- package/test/r39-finalization.test.mjs +74 -0
- package/test/r40-finalization.test.mjs +118 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +19 -58
- 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 +112 -55
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -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.12.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,85 @@
|
|
|
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,/Get-FileHash/);
|
|
74
|
+
assert.match(source,/Remove-Item -LiteralPath \$Grant/);
|
|
75
|
+
assert.match(source,/grant_state='DISARMED'/);
|
|
76
|
+
assert.match(source,/deadbyte\.desktop-policy\.v1/);
|
|
77
|
+
assert.match(source,/max_text_bytes=4096/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('R40 status exposes Desktop runtime and authority independently',async()=>{
|
|
81
|
+
const source=await readFile(controller,'utf8');
|
|
82
|
+
assert.match(source,/Desktop runtime: \{0\}/);
|
|
83
|
+
assert.match(source,/Desktop plane : \{0\}/);
|
|
84
|
+
assert.match(source,/Show-DesktopStatus \$Cfg/);
|
|
85
|
+
});
|
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.12.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}$/);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createDesktopAction } from '../src/desktop-contract.mjs';
|
|
8
|
+
import { captureNativeDesktop } from '../src/desktop-observer-r40.mjs';
|
|
9
|
+
import { invokeNativeDesktopInput } from '../src/desktop-input-r40.mjs';
|
|
10
|
+
import { invokeNativeDesktopGrounder } from '../src/desktop-uia-r40.mjs';
|
|
11
|
+
|
|
12
|
+
const H=v=>createHash('sha256').update(String(v)).digest('hex');
|
|
13
|
+
const WIN={hwnd:'0x1234',pid:99};
|
|
14
|
+
const BOUNDS={left:0,top:0,width:1920,height:1080};
|
|
15
|
+
function action(){
|
|
16
|
+
return createDesktopAction({
|
|
17
|
+
intentId:H('helper-substitution'),kind:'key_chord',
|
|
18
|
+
frameId:H('frame'),desktopEpoch:H('epoch'),topologySha256:H('topology'),
|
|
19
|
+
expectedWindow:WIN,keyChord:['SHIFT']
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('T9 observer helper substitution is detected before native process launch',async()=>{
|
|
24
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-t9-observer-pin-'));
|
|
25
|
+
try{
|
|
26
|
+
const fake=path.join(root,'observer.exe');
|
|
27
|
+
await writeFile(fake,'not the pinned observer');
|
|
28
|
+
await assert.rejects(captureNativeDesktop({
|
|
29
|
+
observerPath:fake,expectedObserverSha256:H('expected-observer'),maxFrameBytes:1024*1024,timeoutMs:1000
|
|
30
|
+
}),/observer executable hash mismatch/);
|
|
31
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('T9 input helper substitution is definite not_started before SendInput dispatch',async()=>{
|
|
35
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-t9-input-pin-'));
|
|
36
|
+
try{
|
|
37
|
+
const fake=path.join(root,'input.exe');
|
|
38
|
+
await writeFile(fake,'not the pinned input helper');
|
|
39
|
+
let error;
|
|
40
|
+
try{
|
|
41
|
+
await invokeNativeDesktopInput(action(),{
|
|
42
|
+
inputPath:fake,expectedInputSha256:H('expected-input'),timeoutMs:1000,
|
|
43
|
+
inputDesktop:'Default',expectedBounds:BOUNDS,expectedWindow:WIN
|
|
44
|
+
});
|
|
45
|
+
}catch(e){error=e;}
|
|
46
|
+
assert.ok(error);
|
|
47
|
+
assert.match(error.message,/input executable hash mismatch/);
|
|
48
|
+
assert.equal(error.side_effect_state,'not_started');
|
|
49
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('T9 UIA helper substitution is detected before COM traversal process launch',async()=>{
|
|
53
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-t9-uia-pin-'));
|
|
54
|
+
try{
|
|
55
|
+
const fake=path.join(root,'uia.exe');
|
|
56
|
+
await writeFile(fake,'not the pinned UIA helper');
|
|
57
|
+
await assert.rejects(invokeNativeDesktopGrounder({
|
|
58
|
+
grounderPath:fake,expectedGrounderSha256:H('expected-uia'),timeoutMs:1000,
|
|
59
|
+
expectedWindow:WIN,expectedBounds:BOUNDS,query:{kind:'root',value:'',max_results:1,ordinal:0}
|
|
60
|
+
}),/UIA executable hash mismatch/);
|
|
61
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
62
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import {
|
|
5
|
+
createDesktopAction,createDesktopFrame,createDesktopTarget,guardDesktopAction,
|
|
6
|
+
bindDesktopPostObservation,bindDesktopActionChain,verifyDesktopFrame,desktopActionKinds
|
|
7
|
+
} from '../src/desktop-contract.mjs';
|
|
8
|
+
|
|
9
|
+
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
10
|
+
const EPOCH=H('epoch-1');
|
|
11
|
+
const TOPO=H('topology-1');
|
|
12
|
+
const IMG1=H('image-1');
|
|
13
|
+
const IMG2=H('image-2');
|
|
14
|
+
const WIN={hwnd:'0x1234',pid:4242,process_image_sha256:H('app.exe'),title_sha256:H('Editor')};
|
|
15
|
+
const NOW='2026-09-19T02:00:01.500Z';
|
|
16
|
+
|
|
17
|
+
function frame(overrides={}){
|
|
18
|
+
return createDesktopFrame({
|
|
19
|
+
desktopEpoch:EPOCH,topologySha256:TOPO,observedAtUtc:'2026-09-19T02:00:00.000Z',
|
|
20
|
+
bounds:{left:-1920,top:0,width:3840,height:1080},activeWindow:WIN,imageSha256:IMG1,...overrides
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function action(f,overrides={}){
|
|
24
|
+
return createDesktopAction({
|
|
25
|
+
intentId:H('intent-1'),kind:'click_primary',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
26
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,point:{x:100,y:200},...overrides
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('desktop frame identity is deterministic and self-verifying',()=>{
|
|
31
|
+
const a=frame(),b=frame();
|
|
32
|
+
assert.equal(a.frame_id,b.frame_id);
|
|
33
|
+
assert.deepEqual(verifyDesktopFrame(a),{ok:true,frame:a});
|
|
34
|
+
const tampered={...a,image_sha256:IMG2};
|
|
35
|
+
const checked=verifyDesktopFrame(tampered);
|
|
36
|
+
assert.equal(checked.ok,false);
|
|
37
|
+
assert.match(checked.reason,/hash mismatch/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('virtual desktop supports negative monitor coordinates but rejects overflow',()=>{
|
|
41
|
+
const f=frame();
|
|
42
|
+
const a=createDesktopAction({
|
|
43
|
+
intentId:H('negative-monitor'),kind:'pointer_move',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
44
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,point:{x:-1000,y:500}
|
|
45
|
+
});
|
|
46
|
+
assert.equal(guardDesktopAction({frame:f,action:a,nowUtc:NOW}).frame_id,f.frame_id);
|
|
47
|
+
const outside=createDesktopAction({
|
|
48
|
+
intentId:H('outside'),kind:'pointer_move',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
49
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,point:{x:1920,y:500}
|
|
50
|
+
});
|
|
51
|
+
assert.throws(()=>guardDesktopAction({frame:f,action:outside,nowUtc:NOW}),/outside frame bounds/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('T9 coordinate overflow and far-negative coordinates are denied before guard/effect',()=>{
|
|
55
|
+
const f=frame();
|
|
56
|
+
assert.throws(()=>createDesktopAction({
|
|
57
|
+
intentId:H('overflow-positive'),kind:'click_primary',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
58
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,point:{x:Number.MAX_SAFE_INTEGER,y:1}
|
|
59
|
+
}),/action point\.x invalid/);
|
|
60
|
+
assert.throws(()=>createDesktopAction({
|
|
61
|
+
intentId:H('overflow-negative'),kind:'click_primary',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
62
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,point:{x:-131073,y:1}
|
|
63
|
+
}),/action point\.x invalid/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('stale frame is denied before effect',()=>{
|
|
67
|
+
const f=frame();
|
|
68
|
+
assert.throws(()=>guardDesktopAction({
|
|
69
|
+
frame:f,action:action(f),nowUtc:'2026-09-19T02:00:03.001Z',maxFrameAgeMs:2000
|
|
70
|
+
}),/frame is stale/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('action is bound to exact frame identity',()=>{
|
|
74
|
+
const f=frame();
|
|
75
|
+
const another=frame({observedAtUtc:'2026-09-19T02:00:00.250Z'});
|
|
76
|
+
assert.notEqual(f.frame_id,another.frame_id);
|
|
77
|
+
assert.throws(()=>guardDesktopAction({frame:another,action:action(f),nowUtc:NOW}),/stale frame id/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('desktop epoch mismatch is denied',()=>{
|
|
81
|
+
const f=frame();
|
|
82
|
+
const a=action(f,{desktopEpoch:H('other-epoch')});
|
|
83
|
+
assert.throws(()=>guardDesktopAction({frame:f,action:a,nowUtc:NOW}),/desktop epoch mismatch/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('topology mismatch is denied',()=>{
|
|
87
|
+
const f=frame();
|
|
88
|
+
const a=action(f,{topologySha256:H('other-topology')});
|
|
89
|
+
assert.throws(()=>guardDesktopAction({frame:f,action:a,nowUtc:NOW}),/topology mismatch/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('foreground window mismatch is denied',()=>{
|
|
93
|
+
const f=frame();
|
|
94
|
+
const a=action(f,{expectedWindow:{...WIN,hwnd:'0xbeef'}});
|
|
95
|
+
assert.throws(()=>guardDesktopAction({frame:f,action:a,nowUtc:NOW}),/foreground window mismatch/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('target is frame-bound, bounded, and deterministic',()=>{
|
|
99
|
+
const f=frame();
|
|
100
|
+
const input={frame:f,rect:{left:50,top:50,width:200,height:100},source:'uia',confidence:0.99,semanticLabelSha256:H('Save'),windowBinding:f.active_window};
|
|
101
|
+
const a=createDesktopTarget(input),b=createDesktopTarget(input);
|
|
102
|
+
assert.equal(a.target_id,b.target_id);
|
|
103
|
+
assert.equal(a.frame_id,f.frame_id);
|
|
104
|
+
assert.throws(()=>createDesktopTarget({...input,rect:{left:1900,top:50,width:100,height:100}}),/outside desktop bounds/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('key chord is narrow and duplicate-free',()=>{
|
|
108
|
+
const f=frame();
|
|
109
|
+
const ok=createDesktopAction({
|
|
110
|
+
intentId:H('keys'),kind:'key_chord',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
111
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,keyChord:['CTRL','SHIFT','S']
|
|
112
|
+
});
|
|
113
|
+
assert.equal(guardDesktopAction({frame:f,action:ok,nowUtc:NOW}).action_sha256,ok.action_sha256);
|
|
114
|
+
assert.throws(()=>createDesktopAction({
|
|
115
|
+
intentId:H('badkeys'),kind:'key_chord',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
116
|
+
topologySha256:f.topology_sha256,keyChord:['CTRL','CTRL']
|
|
117
|
+
}),/duplicate key/);
|
|
118
|
+
assert.throws(()=>createDesktopAction({
|
|
119
|
+
intentId:H('badkeys2'),kind:'key_chord',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
120
|
+
topologySha256:f.topology_sha256,keyChord:['CTRL','POWER']
|
|
121
|
+
}),/unsupported key/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('text input is bounded and hash-bound',()=>{
|
|
125
|
+
const f=frame();
|
|
126
|
+
const a=createDesktopAction({
|
|
127
|
+
intentId:H('text'),kind:'text_input',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
128
|
+
topologySha256:f.topology_sha256,expectedWindow:f.active_window,text:'hello'
|
|
129
|
+
});
|
|
130
|
+
assert.equal(a.text_sha256,H('hello'));
|
|
131
|
+
assert.equal(guardDesktopAction({frame:f,action:a,nowUtc:NOW}).action_sha256,a.action_sha256);
|
|
132
|
+
assert.throws(()=>createDesktopAction({
|
|
133
|
+
intentId:H('nul'),kind:'text_input',frameId:f.frame_id,desktopEpoch:f.desktop_epoch,
|
|
134
|
+
topologySha256:f.topology_sha256,text:'a\u0000b'
|
|
135
|
+
}),/text input invalid/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('post-observation binds before/action/after without claiming success',()=>{
|
|
139
|
+
const before=frame();
|
|
140
|
+
const a=action(before);
|
|
141
|
+
const guard=guardDesktopAction({frame:before,action:a,nowUtc:NOW});
|
|
142
|
+
const after=frame({
|
|
143
|
+
observedAtUtc:'2026-09-19T02:00:02.000Z',
|
|
144
|
+
imageSha256:IMG2,
|
|
145
|
+
activeWindow:{...WIN,title_sha256:H('Editor - changed')}
|
|
146
|
+
});
|
|
147
|
+
const post=bindDesktopPostObservation({guard,beforeFrame:before,action:a,afterFrame:after});
|
|
148
|
+
assert.equal(post.change_observed,true);
|
|
149
|
+
assert.equal(post.window_changed,true);
|
|
150
|
+
assert.equal(post.foreground_transition.changed,true);
|
|
151
|
+
assert.deepEqual(post.foreground_transition.before,before.active_window);
|
|
152
|
+
assert.deepEqual(post.foreground_transition.after,after.active_window);
|
|
153
|
+
assert.equal(post.topology_changed,false);
|
|
154
|
+
assert.equal(post.desktop_epoch_changed,false);
|
|
155
|
+
assert.equal(post.success,null);
|
|
156
|
+
assert.match(post.post_observation_sha256,/^[0-9a-f]{64}$/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('T4 action chain binds before action after post receipt and effect identity',()=>{
|
|
160
|
+
const before=frame();
|
|
161
|
+
const a=action(before);
|
|
162
|
+
const guard=guardDesktopAction({frame:before,action:a,nowUtc:NOW});
|
|
163
|
+
const after=frame({observedAtUtc:'2026-09-19T02:00:02.000Z',imageSha256:IMG2});
|
|
164
|
+
const post=bindDesktopPostObservation({guard,beforeFrame:before,action:a,afterFrame:after});
|
|
165
|
+
const first=bindDesktopActionChain({
|
|
166
|
+
beforeFrameId:before.frame_id,actionSha256:a.action_sha256,afterFrameId:after.frame_id,
|
|
167
|
+
guardSha256:guard.guard_sha256,postObservationSha256:post.post_observation_sha256,
|
|
168
|
+
postReceiptSha256:H('post-receipt'),effectId:H('effect-id'),effectFingerprint:H('effect-fingerprint')
|
|
169
|
+
});
|
|
170
|
+
const again=bindDesktopActionChain({
|
|
171
|
+
beforeFrameId:before.frame_id,actionSha256:a.action_sha256,afterFrameId:after.frame_id,
|
|
172
|
+
guardSha256:guard.guard_sha256,postObservationSha256:post.post_observation_sha256,
|
|
173
|
+
postReceiptSha256:H('post-receipt'),effectId:H('effect-id'),effectFingerprint:H('effect-fingerprint')
|
|
174
|
+
});
|
|
175
|
+
assert.equal(first.chain_sha256,again.chain_sha256);
|
|
176
|
+
assert.match(first.chain_sha256,/^[0-9a-f]{64}$/);
|
|
177
|
+
const changed=bindDesktopActionChain({
|
|
178
|
+
beforeFrameId:before.frame_id,actionSha256:a.action_sha256,afterFrameId:after.frame_id,
|
|
179
|
+
guardSha256:guard.guard_sha256,postObservationSha256:post.post_observation_sha256,
|
|
180
|
+
postReceiptSha256:H('substituted'),effectId:H('effect-id'),effectFingerprint:H('effect-fingerprint')
|
|
181
|
+
});
|
|
182
|
+
assert.notEqual(changed.chain_sha256,first.chain_sha256);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('post-observation rejects substitution and time travel',()=>{
|
|
186
|
+
const before=frame();
|
|
187
|
+
const a=action(before);
|
|
188
|
+
const guard=guardDesktopAction({frame:before,action:a,nowUtc:NOW});
|
|
189
|
+
const other=action(before,{intentId:H('other')});
|
|
190
|
+
const after=frame({observedAtUtc:'2026-09-19T02:00:02.000Z',imageSha256:IMG2});
|
|
191
|
+
assert.throws(()=>bindDesktopPostObservation({guard,beforeFrame:before,action:other,afterFrame:after}),/action mismatch/);
|
|
192
|
+
const tooOld=frame({observedAtUtc:'2026-09-19T02:00:01.000Z',imageSha256:IMG2});
|
|
193
|
+
assert.throws(()=>bindDesktopPostObservation({guard,beforeFrame:before,action:a,afterFrame:tooOld}),/predates guarded action/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('action vocabulary is fixed for R40-T1',()=>{
|
|
197
|
+
assert.deepEqual(desktopActionKinds(),[
|
|
198
|
+
'pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input'
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createDesktopAction } from '../src/desktop-contract.mjs';
|
|
5
|
+
import { serializeNativeDesktopInputRequest,parseNativeDesktopInputResponse } from '../src/desktop-input-r40.mjs';
|
|
6
|
+
|
|
7
|
+
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
8
|
+
const bounds={left:-1920,top:0,width:3840,height:1080};
|
|
9
|
+
const window={hwnd:'0x1234',pid:99};
|
|
10
|
+
|
|
11
|
+
function action(kind,extra={}){
|
|
12
|
+
return createDesktopAction({
|
|
13
|
+
intentId:H('intent'),kind,frameId:H('frame'),desktopEpoch:H('epoch'),topologySha256:H('topology'),
|
|
14
|
+
expectedWindow:window,point:null,scrollDelta:null,keyChord:null,text:null,...extra
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test('desktop native input protocol serializes one fixed click with frame-derived environment binding',()=>{
|
|
19
|
+
const value=serializeNativeDesktopInputRequest(action('click_primary',{point:{x:-100,y:200}}),{
|
|
20
|
+
inputDesktop:'Default',expectedBounds:bounds,expectedWindow:window
|
|
21
|
+
});
|
|
22
|
+
const text=value.toString('utf8');
|
|
23
|
+
assert.match(text,/^DEADBYTE_DESKTOP_INPUT_V1\n/);
|
|
24
|
+
assert.match(text,/kind=click_primary\n/);
|
|
25
|
+
assert.match(text,/expected_hwnd=0x1234\nexpected_pid=99\n/);
|
|
26
|
+
assert.match(text,/left=-1920\ntop=0\nwidth=3840\nheight=1080\n/);
|
|
27
|
+
assert.match(text,/x=-100\ny=200\n/);
|
|
28
|
+
assert.match(text,/key_count=0\ntext_bytes=0\n$/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('desktop native input text protocol is byte-counted and preserves raw UTF-8 after bounded header',()=>{
|
|
32
|
+
const value=serializeNativeDesktopInputRequest(action('text_input',{text:'ไทย🙂'}),{
|
|
33
|
+
inputDesktop:'Default',expectedBounds:bounds,expectedWindow:window
|
|
34
|
+
});
|
|
35
|
+
const marker=Buffer.from('text_bytes=13\n','utf8');
|
|
36
|
+
const at=value.indexOf(marker);
|
|
37
|
+
assert.ok(at>0);
|
|
38
|
+
assert.equal(value.subarray(at+marker.length).toString('utf8'),'ไทย🙂');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('desktop native input protocol rejects out-of-bounds point before process launch',()=>{
|
|
42
|
+
assert.throws(()=>serializeNativeDesktopInputRequest(action('click_primary',{point:{x:3000,y:1}}),{
|
|
43
|
+
inputDesktop:'Default',expectedBounds:bounds,expectedWindow:window
|
|
44
|
+
}),/outside bounds/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('desktop native input response is bound to exact action desktop and foreground identity',()=>{
|
|
48
|
+
const a=action('key_chord',{keyChord:['CTRL','A']});
|
|
49
|
+
const bytes=Buffer.from(JSON.stringify({
|
|
50
|
+
schema:'deadbyte.desktop-native-input.v1',injected:true,backend:'win32-sendinput',
|
|
51
|
+
action_sha256:a.action_sha256,kind:'key_chord',event_count:4,input_desktop:'Default',
|
|
52
|
+
foreground_hwnd:'0x1234',foreground_pid:99
|
|
53
|
+
})+'\n','utf8');
|
|
54
|
+
const parsed=parseNativeDesktopInputResponse(bytes,{
|
|
55
|
+
actionSha256:a.action_sha256,kind:'key_chord',inputDesktop:'Default',expectedWindow:window
|
|
56
|
+
});
|
|
57
|
+
assert.equal(parsed.injected,true);
|
|
58
|
+
assert.equal(parsed.event_count,4);
|
|
59
|
+
assert.match(parsed.provider_receipt_sha256,/^[0-9a-f]{64}$/);
|
|
60
|
+
assert.throws(()=>parseNativeDesktopInputResponse(bytes,{
|
|
61
|
+
actionSha256:H('other'),kind:'key_chord',inputDesktop:'Default',expectedWindow:window
|
|
62
|
+
}),/action mismatch/);
|
|
63
|
+
});
|