deadbyte-mcp 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +126 -54
- package/R41-PARENT.json +15 -0
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +12 -8
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +112 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/init-desktop-policy-r40.ps1 +85 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/src/autonomous-context-engine.mjs +3 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +24 -0
- package/src/autonomous-runtime.mjs +164 -25
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +87 -19
- package/src/memory-r41.mjs +692 -0
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +86 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +15 -15
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/r41-finalization.test.mjs +38 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +20 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp,writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import {
|
|
8
|
+
DESKTOP_POLICY_SCHEMA,DESKTOP_GRANT_SCHEMA,DESKTOP_ACTION_KINDS,
|
|
9
|
+
loadDesktopPolicy,desktopSessionStatus,assertDesktopArmed
|
|
10
|
+
} from '../src/desktop-policy-r40.mjs';
|
|
11
|
+
|
|
12
|
+
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
13
|
+
|
|
14
|
+
async function fixture(){
|
|
15
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-policy-'));
|
|
16
|
+
const grant=path.join(root,'desktop-grant.json');
|
|
17
|
+
const evidence=path.join(root,'evidence');
|
|
18
|
+
const observer=path.join(root,'observer.exe');
|
|
19
|
+
const input=path.join(root,'input.exe');
|
|
20
|
+
const grounder=path.join(root,'grounder.exe');
|
|
21
|
+
await writeFile(observer,'observer');
|
|
22
|
+
await writeFile(input,'input');
|
|
23
|
+
await writeFile(grounder,'grounder');
|
|
24
|
+
const policyPath=path.join(root,'desktop-policy.json');
|
|
25
|
+
const raw={
|
|
26
|
+
schema:DESKTOP_POLICY_SCHEMA,enabled:true,grant_file:grant,evidence_dir:evidence,
|
|
27
|
+
observer:{executable:observer,sha256:H('observer'),backend:'gdi-bitblt-fallback'},
|
|
28
|
+
input:{executable:input,sha256:H('input')},
|
|
29
|
+
grounder:{executable:grounder,sha256:H('grounder')},
|
|
30
|
+
allowed_input_desktops:['Default'],
|
|
31
|
+
allowed_actions:DESKTOP_ACTION_KINDS,
|
|
32
|
+
limits:{max_frame_age_ms:2000,max_frame_bytes:8*1024*1024,capture_timeout_ms:5000,input_timeout_ms:2000,ground_timeout_ms:2000,post_observe_timeout_ms:5000,max_preview_bytes:512*1024,max_text_bytes:4096}
|
|
33
|
+
};
|
|
34
|
+
await writeFile(policyPath,JSON.stringify(raw));
|
|
35
|
+
const policy=await loadDesktopPolicy(policyPath);
|
|
36
|
+
return {root,grant,policyPath,policy};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('desktop policy loads bounded observer/input authority metadata',async()=>{
|
|
40
|
+
const {policy}=await fixture();
|
|
41
|
+
assert.equal(policy.schema,DESKTOP_POLICY_SCHEMA);
|
|
42
|
+
assert.equal(policy.allowed_input_desktops[0],'Default');
|
|
43
|
+
assert.deepEqual(policy.allowed_actions,DESKTOP_ACTION_KINDS);
|
|
44
|
+
assert.equal(policy.limits.max_frame_age_ms,2000);
|
|
45
|
+
assert.equal(policy.limits.ground_timeout_ms,2000);
|
|
46
|
+
assert.equal(policy.grounder.sha256,H('grounder'));
|
|
47
|
+
assert.match(policy.policy_sha256,/^[0-9a-f]{64}$/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('desktop plane is fail-closed when no grant exists',async()=>{
|
|
51
|
+
const {policy}=await fixture();
|
|
52
|
+
assert.deepEqual(await desktopSessionStatus(policy),{
|
|
53
|
+
configured:true,armed:false,reason:'disarmed',standing:false,action_kinds:[]
|
|
54
|
+
});
|
|
55
|
+
await assert.rejects(assertDesktopArmed(policy,'click_primary'),/desktop plane is disarmed/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('desktop standing grant is policy-hash-bound and action-scoped',async()=>{
|
|
59
|
+
const {policy,grant}=await fixture();
|
|
60
|
+
await writeFile(grant,JSON.stringify({
|
|
61
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
62
|
+
action_kinds:['pointer_move','click_primary']
|
|
63
|
+
}));
|
|
64
|
+
const status=await desktopSessionStatus(policy);
|
|
65
|
+
assert.equal(status.armed,true);
|
|
66
|
+
assert.equal(status.reason,'standing-grant');
|
|
67
|
+
assert.deepEqual(status.action_kinds,['pointer_move','click_primary']);
|
|
68
|
+
assert.equal((await assertDesktopArmed(policy,'click_primary')).armed,true);
|
|
69
|
+
await assert.rejects(assertDesktopArmed(policy,'text_input'),/outside active grant/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('desktop grant rejects policy substitution',async()=>{
|
|
73
|
+
const {policy,grant}=await fixture();
|
|
74
|
+
await writeFile(grant,JSON.stringify({
|
|
75
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:H('wrong-policy'),instance_nonce:H('nonce'),
|
|
76
|
+
action_kinds:['click_primary']
|
|
77
|
+
}));
|
|
78
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'policy-digest');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('desktop grant rejects unknown or duplicate action kinds',async()=>{
|
|
82
|
+
const {policy,grant}=await fixture();
|
|
83
|
+
await writeFile(grant,JSON.stringify({
|
|
84
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
85
|
+
action_kinds:['click_primary','click_primary']
|
|
86
|
+
}));
|
|
87
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'actions-invalid');
|
|
88
|
+
await writeFile(grant,JSON.stringify({
|
|
89
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
90
|
+
action_kinds:['click_primary','power_off']
|
|
91
|
+
}));
|
|
92
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'actions-invalid');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('desktop policy rejects unsafe limits and malformed desktops',async()=>{
|
|
96
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-policy-bad-'));
|
|
97
|
+
const base={
|
|
98
|
+
schema:DESKTOP_POLICY_SCHEMA,enabled:true,
|
|
99
|
+
grant_file:path.join(root,'grant.json'),evidence_dir:path.join(root,'evidence'),
|
|
100
|
+
observer:{executable:path.join(root,'observer.exe'),sha256:H('observer')},
|
|
101
|
+
input:{executable:path.join(root,'input.exe'),sha256:H('input')},
|
|
102
|
+
grounder:{executable:path.join(root,'grounder.exe'),sha256:H('grounder')},
|
|
103
|
+
allowed_actions:['click_primary']
|
|
104
|
+
};
|
|
105
|
+
const p1=path.join(root,'bad-limit.json');
|
|
106
|
+
await writeFile(p1,JSON.stringify({...base,allowed_input_desktops:['Default'],limits:{max_frame_age_ms:999999}}));
|
|
107
|
+
await assert.rejects(loadDesktopPolicy(p1),/max_frame_age_ms/);
|
|
108
|
+
const p2=path.join(root,'bad-desktop.json');
|
|
109
|
+
await writeFile(p2,JSON.stringify({...base,allowed_input_desktops:['Default','Default']}));
|
|
110
|
+
await assert.rejects(loadDesktopPolicy(p2),/allowed_input_desktops invalid/);
|
|
111
|
+
});
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash,generateKeyPairSync } from 'node:crypto';
|
|
4
|
+
import { mkdtemp,readFile,writeFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createDesktopFrame } from '../src/desktop-contract.mjs';
|
|
8
|
+
import { DESKTOP_POLICY_SCHEMA,DESKTOP_GRANT_SCHEMA } from '../src/desktop-policy-r40.mjs';
|
|
9
|
+
import { createDesktopRuntime,verifyDesktopReceiptObject } from '../src/desktop-runtime-r40.mjs';
|
|
10
|
+
|
|
11
|
+
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
12
|
+
const EPOCH=H('epoch');
|
|
13
|
+
const TOPO=H('topology');
|
|
14
|
+
const WIN={hwnd:'0x1234',pid:55,process_image_sha256:H('app'),title_sha256:H('title')};
|
|
15
|
+
|
|
16
|
+
function F(at,image=H('same'),window=WIN){
|
|
17
|
+
return createDesktopFrame({
|
|
18
|
+
desktopEpoch:EPOCH,topologySha256:TOPO,observedAtUtc:at,
|
|
19
|
+
bounds:{left:0,top:0,width:1000,height:800},activeWindow:window,imageSha256:image
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function fixture({grantActions=['click_primary','text_input'],captureFrames=null,times=null,inputProvider=null,grounderProvider=null}={}){
|
|
23
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-runtime-'));
|
|
24
|
+
const grant=path.join(root,'grant.json'),evidence=path.join(root,'evidence');
|
|
25
|
+
const policy={
|
|
26
|
+
schema:DESKTOP_POLICY_SCHEMA,enabled:true,policy_path:path.join(root,'policy.json'),
|
|
27
|
+
policy_sha256:H('policy'),grant_file:grant,evidence_dir:evidence,
|
|
28
|
+
observer:{executable:path.join(root,'observer.exe'),sha256:H('observer'),backend:'fake'},
|
|
29
|
+
input:{executable:path.join(root,'input.exe'),sha256:H('input')},
|
|
30
|
+
allowed_input_desktops:['Default'],
|
|
31
|
+
allowed_actions:['pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input'],
|
|
32
|
+
limits:{max_frame_age_ms:2000,max_frame_bytes:8*1024*1024,capture_timeout_ms:5000,input_timeout_ms:2000,post_observe_timeout_ms:5000,max_preview_bytes:512*1024,max_text_bytes:4096}
|
|
33
|
+
};
|
|
34
|
+
await writeFile(grant,JSON.stringify({
|
|
35
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('grant-nonce'),action_kinds:grantActions
|
|
36
|
+
}));
|
|
37
|
+
const expected=F('2026-09-19T02:00:00.000Z');
|
|
38
|
+
const frames=captureFrames??[
|
|
39
|
+
{frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4},
|
|
40
|
+
{frame:F('2026-09-19T02:00:01.000Z',H('after')),backend:'fake',byte_length:4}
|
|
41
|
+
];
|
|
42
|
+
const records=new Map([[expected.frame_id,{frame:expected}]]);
|
|
43
|
+
let captureIndex=0;
|
|
44
|
+
const observerRuntime={
|
|
45
|
+
frameStatus(id){const r=records.get(id);if(!r)throw new Error('frame missing');return r;},
|
|
46
|
+
async capture(){
|
|
47
|
+
const item=frames[captureIndex++];
|
|
48
|
+
if(!item)throw new Error('capture exhausted');
|
|
49
|
+
records.set(item.frame.frame_id,item);return item;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const calls=[];
|
|
53
|
+
const provider=inputProvider??(async(action)=>{calls.push(action);return {injected:true,provider_receipt_sha256:H('provider')};});
|
|
54
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
55
|
+
const clock=[...(times??[
|
|
56
|
+
'2026-09-19T02:00:00.500Z',
|
|
57
|
+
'2026-09-19T02:00:00.800Z',
|
|
58
|
+
'2026-09-19T02:00:00.810Z',
|
|
59
|
+
'2026-09-19T02:00:01.000Z'
|
|
60
|
+
])];
|
|
61
|
+
const now=()=>clock.length?clock.shift():'2026-09-19T02:00:01.000Z';
|
|
62
|
+
const runtime=createDesktopRuntime({policy,observerRuntime,inputProvider:provider,grounderProvider,privateKey,keyId:'test-key',evidenceDir:evidence,now});
|
|
63
|
+
return {root,grant,evidence,policy,expected,runtime,calls,publicKey};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test('desktop runtime performs observe-target-fresh-guard-effect-post chain',async()=>{
|
|
67
|
+
const fx=await fixture();
|
|
68
|
+
const target=await fx.runtime.target({
|
|
69
|
+
frameId:fx.expected.frame_id,rect:{left:100,top:100,width:200,height:100},source:'uia',confidence:0.99
|
|
70
|
+
});
|
|
71
|
+
const result=await fx.runtime.action({
|
|
72
|
+
intentId:H('intent'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,targetId:target.target.target_id
|
|
73
|
+
});
|
|
74
|
+
assert.equal(fx.calls.length,1);
|
|
75
|
+
assert.deepEqual(fx.calls[0].point,{x:200,y:150});
|
|
76
|
+
assert.equal(result.injected,true);
|
|
77
|
+
assert.equal(result.task_success,null);
|
|
78
|
+
assert.equal(result.post_observation.change_observed,true);
|
|
79
|
+
assert.equal(result.post_observation.success,null);
|
|
80
|
+
assert.equal(result.side_effect_state,'confirmed_injected');
|
|
81
|
+
assert.match(result.started_receipt_sha256,/^[0-9a-f]{64}$/);
|
|
82
|
+
assert.match(result.post_receipt_sha256,/^[0-9a-f]{64}$/);
|
|
83
|
+
assert.match(result.receipt_sha256,/^[0-9a-f]{64}$/);
|
|
84
|
+
assert.match(result.action_chain.chain_sha256,/^[0-9a-f]{64}$/);
|
|
85
|
+
assert.match(result.effect_id,/^[0-9a-f]{64}$/);
|
|
86
|
+
assert.match(result.effect_fingerprint,/^[0-9a-f]{64}$/);
|
|
87
|
+
assert.equal(result.effect_completion.schema,'deadbyte.effect-completion.v1');
|
|
88
|
+
|
|
89
|
+
const postReceipt=JSON.parse(await readFile(path.join(fx.evidence,`${result.post_receipt_id}.json`),'utf8'));
|
|
90
|
+
assert.equal(postReceipt.kind,'desktop_post_observation');
|
|
91
|
+
assert.equal(postReceipt.payload.post_observation_sha256,result.post_observation.post_observation_sha256);
|
|
92
|
+
const receipt=JSON.parse(await readFile(path.join(fx.evidence,`${result.receipt_id}.json`),'utf8'));
|
|
93
|
+
assert.deepEqual(verifyDesktopReceiptObject(receipt,{
|
|
94
|
+
policySha256:fx.policy.policy_sha256,publicKey:fx.publicKey,keyId:'test-key'
|
|
95
|
+
}),{ok:true,receipt_sha256:result.receipt_sha256,kind:'desktop_action_completed',receipt_id:result.receipt_id});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('desktop runtime refuses effect when expected frame is stale',async()=>{
|
|
99
|
+
const fx=await fixture({times:['2026-09-19T02:00:03.500Z']});
|
|
100
|
+
await assert.rejects(fx.runtime.action({
|
|
101
|
+
intentId:H('stale'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:10,y:10}
|
|
102
|
+
}),/expected frame is stale/);
|
|
103
|
+
assert.equal(fx.calls.length,0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('desktop runtime re-observes and blocks pixels changed before effect',async()=>{
|
|
107
|
+
const changed={frame:F('2026-09-19T02:00:00.700Z',H('changed-before')),backend:'fake',byte_length:4};
|
|
108
|
+
const fx=await fixture({captureFrames:[changed]});
|
|
109
|
+
await assert.rejects(fx.runtime.action({
|
|
110
|
+
intentId:H('changed'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:10,y:10}
|
|
111
|
+
}),/pixels changed before action/);
|
|
112
|
+
assert.equal(fx.calls.length,0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('desktop runtime key_chord is window-scoped and tolerates unrelated pixel drift',async()=>{
|
|
116
|
+
const changedPre={frame:F('2026-09-19T02:00:00.700Z',H('dynamic-before')),backend:'fake',byte_length:4};
|
|
117
|
+
const changedPost={frame:F('2026-09-19T02:00:01.000Z',H('dynamic-after')),backend:'fake',byte_length:4};
|
|
118
|
+
const fx=await fixture({grantActions:['key_chord'],captureFrames:[changedPre,changedPost]});
|
|
119
|
+
const result=await fx.runtime.action({
|
|
120
|
+
intentId:H('window-key'),kind:'key_chord',expectedFrameId:fx.expected.frame_id,keyChord:['SHIFT']
|
|
121
|
+
});
|
|
122
|
+
assert.equal(fx.calls.length,1);
|
|
123
|
+
assert.equal(result.injected,true);
|
|
124
|
+
assert.deepEqual(fx.calls[0].key_chord,['SHIFT']);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('desktop runtime text_input remains pixel-stable because keyboard focus is control-sensitive',async()=>{
|
|
128
|
+
const changed={frame:F('2026-09-19T02:00:00.700Z',H('changed-text')),backend:'fake',byte_length:4};
|
|
129
|
+
const fx=await fixture({grantActions:['text_input'],captureFrames:[changed]});
|
|
130
|
+
await assert.rejects(fx.runtime.action({
|
|
131
|
+
intentId:H('changed-text'),kind:'text_input',expectedFrameId:fx.expected.frame_id,text:'x'
|
|
132
|
+
}),/pixels changed before action/);
|
|
133
|
+
assert.equal(fx.calls.length,0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('desktop runtime re-observes and blocks foreground-window drift',async()=>{
|
|
137
|
+
const changed={frame:F('2026-09-19T02:00:00.700Z',H('same'),{...WIN,hwnd:'0x9999'}),backend:'fake',byte_length:4};
|
|
138
|
+
const fx=await fixture({captureFrames:[changed]});
|
|
139
|
+
await assert.rejects(fx.runtime.action({
|
|
140
|
+
intentId:H('window'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:10,y:10}
|
|
141
|
+
}),/foreground window changed before action/);
|
|
142
|
+
assert.equal(fx.calls.length,0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('T9 capture failure blocks input before any side effect dispatch',async()=>{
|
|
146
|
+
const fx=await fixture({captureFrames:[]});
|
|
147
|
+
await assert.rejects(fx.runtime.action({
|
|
148
|
+
intentId:H('capture-fail'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
149
|
+
}),/capture exhausted/);
|
|
150
|
+
assert.equal(fx.calls.length,0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('T9 Desktop receipt substitution is detected by runtime verifier',async()=>{
|
|
154
|
+
const fx=await fixture();
|
|
155
|
+
const result=await fx.runtime.action({
|
|
156
|
+
intentId:H('receipt-substitution'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
157
|
+
});
|
|
158
|
+
const clean=await fx.runtime.verifyReceipt({receiptId:result.receipt_id});
|
|
159
|
+
assert.equal(clean.ok,true);
|
|
160
|
+
assert.equal(clean.receipt_sha256,result.receipt_sha256);
|
|
161
|
+
const receiptPath=path.join(fx.evidence,result.receipt_id+'.json');
|
|
162
|
+
const tampered=JSON.parse(await readFile(receiptPath,'utf8'));
|
|
163
|
+
tampered.payload.action_sha256=H('forged-action');
|
|
164
|
+
await writeFile(receiptPath,JSON.stringify(tampered));
|
|
165
|
+
await assert.rejects(fx.runtime.verifyReceipt({receiptId:result.receipt_id}),/receipt verification failed/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('desktop runtime enforces standing-grant action scope',async()=>{
|
|
169
|
+
const fx=await fixture({grantActions:['click_primary']});
|
|
170
|
+
await assert.rejects(fx.runtime.action({
|
|
171
|
+
intentId:H('text'),kind:'text_input',expectedFrameId:fx.expected.frame_id,text:'hello'
|
|
172
|
+
}),/outside active grant/);
|
|
173
|
+
assert.equal(fx.calls.length,0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('desktop runtime denies mutation when grant is revoked',async()=>{
|
|
177
|
+
const fx=await fixture();
|
|
178
|
+
await writeFile(fx.grant,'{}');
|
|
179
|
+
await assert.rejects(fx.runtime.action({
|
|
180
|
+
intentId:H('revoked'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
181
|
+
}),/desktop plane is disarmed/);
|
|
182
|
+
assert.equal(fx.calls.length,0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('provider failure after dispatch is signed OUTCOME_UNKNOWN and never blind-retried',async()=>{
|
|
186
|
+
let calls=0;
|
|
187
|
+
const fx=await fixture({inputProvider:async()=>{calls++;throw new Error('transport lost after dispatch');}});
|
|
188
|
+
let error;
|
|
189
|
+
try{
|
|
190
|
+
await fx.runtime.action({
|
|
191
|
+
intentId:H('fail-unknown'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
192
|
+
});
|
|
193
|
+
}catch(e){error=e;}
|
|
194
|
+
assert.equal(calls,1);
|
|
195
|
+
assert.equal(error.side_effect_state,'unknown');
|
|
196
|
+
assert.match(error.message,/outcome unknown/);
|
|
197
|
+
assert.match(error.receipt_id,/^[0-9a-f]{24}$/);
|
|
198
|
+
const receipt=JSON.parse(await readFile(path.join(fx.evidence,`${error.receipt_id}.json`),'utf8'));
|
|
199
|
+
assert.equal(receipt.kind,'desktop_action_outcome_unknown');
|
|
200
|
+
assert.equal(receipt.payload.side_effect_state,'unknown');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('definite pre-dispatch provider denial is signed as not_started',async()=>{
|
|
204
|
+
const provider=async()=>{const e=new Error('binary pin mismatch');e.side_effect_state='not_started';throw e;};
|
|
205
|
+
const fx=await fixture({inputProvider:provider});
|
|
206
|
+
let error;
|
|
207
|
+
try{
|
|
208
|
+
await fx.runtime.action({
|
|
209
|
+
intentId:H('fail-not-started'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
210
|
+
});
|
|
211
|
+
}catch(e){error=e;}
|
|
212
|
+
assert.equal(error.side_effect_state,'not_started');
|
|
213
|
+
assert.match(error.message,/did not start/);
|
|
214
|
+
const receipt=JSON.parse(await readFile(path.join(fx.evidence,`${error.receipt_id}.json`),'utf8'));
|
|
215
|
+
assert.equal(receipt.kind,'desktop_action_effect_failed');
|
|
216
|
+
assert.equal(receipt.payload.side_effect_state,'not_started');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('post-observation failure after confirmed injection is OUTCOME_UNKNOWN',async()=>{
|
|
220
|
+
const preOnly={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
221
|
+
const fx=await fixture({captureFrames:[preOnly]});
|
|
222
|
+
let error;
|
|
223
|
+
try{
|
|
224
|
+
await fx.runtime.action({
|
|
225
|
+
intentId:H('post-fail'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20}
|
|
226
|
+
});
|
|
227
|
+
}catch(e){error=e;}
|
|
228
|
+
assert.equal(fx.calls.length,1);
|
|
229
|
+
assert.equal(error.side_effect_state,'unknown');
|
|
230
|
+
assert.match(error.message,/post-observation\/evidence incomplete/);
|
|
231
|
+
const receipt=JSON.parse(await readFile(path.join(fx.evidence,`${error.receipt_id}.json`),'utf8'));
|
|
232
|
+
assert.equal(receipt.kind,'desktop_action_outcome_unknown');
|
|
233
|
+
assert.equal(receipt.payload.injection_confirmed,true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('read-only observe works independently of desktop mutation grant',async()=>{
|
|
237
|
+
const fx=await fixture();
|
|
238
|
+
await writeFile(fx.grant,'{}');
|
|
239
|
+
const observed=await fx.runtime.observe();
|
|
240
|
+
assert.equal(observed.status,undefined);
|
|
241
|
+
assert.match(observed.frame.frame_id,/^[0-9a-f]{64}$/);
|
|
242
|
+
assert.match(observed.receipt_sha256,/^[0-9a-f]{64}$/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
test('desktop runtime enforces policy max_text_bytes before observation or input effect',async()=>{
|
|
247
|
+
const fx=await fixture({grantActions:['text_input']});
|
|
248
|
+
const multibyte='ก'.repeat(2000);
|
|
249
|
+
assert.ok(multibyte.length<=4096);
|
|
250
|
+
assert.ok(Buffer.byteLength(multibyte,'utf8')>fx.policy.limits.max_text_bytes);
|
|
251
|
+
await assert.rejects(fx.runtime.action({
|
|
252
|
+
intentId:H('oversize-text'),kind:'text_input',expectedFrameId:fx.expected.frame_id,text:multibyte
|
|
253
|
+
}),/exceeds policy byte limit/);
|
|
254
|
+
assert.equal(fx.calls.length,0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('T5 UIA grounding re-observes and binds candidate to fresh frame without mutation authority',async()=>{
|
|
258
|
+
const groundedFrame={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
259
|
+
const grounderProvider=async({frame,query})=>{
|
|
260
|
+
assert.equal(frame.frame_id,groundedFrame.frame.frame_id);
|
|
261
|
+
assert.equal(query.value,'Save');
|
|
262
|
+
return {
|
|
263
|
+
status:'ok',query_sha256:H('query-save'),query_kind:'name_exact',ordinal:0,visited:9,truncated:false,
|
|
264
|
+
matches:[{rect:{left:100,top:120,width:80,height:30},control_type:50000,enabled:true,offscreen:false}],
|
|
265
|
+
provider_receipt_sha256:H('uia-provider')
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const fx=await fixture({captureFrames:[groundedFrame],grounderProvider});
|
|
269
|
+
await writeFile(fx.grant,'{}');
|
|
270
|
+
const result=await fx.runtime.ground({frameId:fx.expected.frame_id,query:{kind:'name_exact',value:'Save',max_results:8,ordinal:0}});
|
|
271
|
+
assert.equal(result.target.source,'uia');
|
|
272
|
+
assert.equal(result.target.frame_id,groundedFrame.frame.frame_id);
|
|
273
|
+
assert.deepEqual(result.target.rect,{left:100,top:120,width:80,height:30});
|
|
274
|
+
assert.equal(result.grounding.query_sha256,H('query-save'));
|
|
275
|
+
assert.match(result.grounding.receipt_sha256,/^[0-9a-f]{64}$/);
|
|
276
|
+
assert.equal(JSON.stringify(result).includes('Save'),false);
|
|
277
|
+
assert.equal(fx.calls.length,0);
|
|
278
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { parseDesktopUiaResponse,serializeDesktopUiaRequest } from '../src/desktop-uia-r40.mjs';
|
|
5
|
+
|
|
6
|
+
const H=v=>createHash('sha256').update(String(v)).digest('hex');
|
|
7
|
+
const win={hwnd:'0x1234',pid:42};
|
|
8
|
+
const bounds={left:-100,top:0,width:2020,height:1080};
|
|
9
|
+
|
|
10
|
+
test('T5 UIA request is fixed bounded and keeps semantic query in stdin only',()=>{
|
|
11
|
+
const out=serializeDesktopUiaRequest({expectedWindow:win,query:{kind:'name_exact',value:'Save',max_results:4,ordinal:1}});
|
|
12
|
+
assert.ok(out.request.length<8192);
|
|
13
|
+
assert.equal(out.query.kind,'name_exact');
|
|
14
|
+
assert.equal(out.query.ordinal,1);
|
|
15
|
+
assert.match(out.query.querySha256,/^[0-9a-f]{64}$/);
|
|
16
|
+
assert.equal(out.request.toString('utf8').endsWith('Save'),true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('T5 UIA response is foreground-bound and filters off-desktop rectangles',()=>{
|
|
20
|
+
const raw=Buffer.from(JSON.stringify({
|
|
21
|
+
schema:'deadbyte.desktop-uia-ground.v1',foreground_hwnd:'0x1234',foreground_pid:42,
|
|
22
|
+
query_kind:'control_type',visited:12,truncated:false,matches:[
|
|
23
|
+
{left:10,top:20,width:100,height:30,control_type:50000,enabled:true,offscreen:false},
|
|
24
|
+
{left:5000,top:20,width:100,height:30,control_type:50000,enabled:true,offscreen:false}
|
|
25
|
+
]
|
|
26
|
+
}));
|
|
27
|
+
const parsed=parseDesktopUiaResponse(raw,{expectedWindow:win,expectedBounds:bounds,query:{kind:'control_type',value:'button',max_results:8,ordinal:0}});
|
|
28
|
+
assert.equal(parsed.matches.length,1);
|
|
29
|
+
assert.deepEqual(parsed.matches[0].rect,{left:10,top:20,width:100,height:30});
|
|
30
|
+
assert.equal(parsed.matches[0].control_type,50000);
|
|
31
|
+
assert.match(parsed.provider_receipt_sha256,/^[0-9a-f]{64}$/);
|
|
32
|
+
assert.match(parsed.query_sha256,/^[0-9a-f]{64}$/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('T5 UIA response rejects foreground substitution and oversized query',()=>{
|
|
36
|
+
const raw=Buffer.from(JSON.stringify({
|
|
37
|
+
schema:'deadbyte.desktop-uia-ground.v1',foreground_hwnd:'0x9999',foreground_pid:42,
|
|
38
|
+
query_kind:'root',visited:1,truncated:false,matches:[]
|
|
39
|
+
}));
|
|
40
|
+
assert.throws(()=>parseDesktopUiaResponse(raw,{expectedWindow:win,expectedBounds:bounds,query:{kind:'root',value:'',max_results:1,ordinal:0}}),/hwnd mismatch/);
|
|
41
|
+
assert.throws(()=>serializeDesktopUiaRequest({expectedWindow:win,query:{kind:'name_exact',value:'x'.repeat(513)}}),/bytes invalid/);
|
|
42
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
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 full stdio catalog is exactly 80 with four Desktop primitives while 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-full-catalog',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
50
|
+
const env={
|
|
51
|
+
...getDefaultEnvironment(),
|
|
52
|
+
DEADBYTE_CAPABILITY_PROFILE:'full',
|
|
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:'1',
|
|
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
|
+
const {R40_EXPECTED_TOOLS}=await import('../src/remote-mcp-r40-parity-client.mjs');
|
|
79
|
+
assert.equal(names.length,80,'expected exact full R40 catalog 80, got '+names.length+': '+names.join(','));
|
|
80
|
+
assert.deepEqual(names,R40_EXPECTED_TOOLS);
|
|
81
|
+
for(const name of ['desktop_status','desktop_observe','desktop_target','desktop_action',
|
|
82
|
+
'host_file_read','host_file_write','host_exec']) assert.ok(names.includes(name),'missing '+name);
|
|
83
|
+
for(const tool of listed.tools) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
|
|
84
|
+
const catalog_sha256=sha(Buffer.from(JSON.stringify(names),'utf8'));
|
|
85
|
+
console.log(JSON.stringify({tool_count:names.length,catalog_sha256,desktop_tools:4,authority:'DISARMED'}));
|
|
86
|
+
} finally {
|
|
87
|
+
await client.close().catch(()=>{});
|
|
88
|
+
for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
|
|
89
|
+
await f.cleanup();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
@@ -12,7 +12,7 @@ import { createAutonomousRuntime } from '../../src/autonomous-runtime.mjs';
|
|
|
12
12
|
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
13
13
|
const nonce = () => randomBytes(32).toString('hex');
|
|
14
14
|
|
|
15
|
-
export async function createR34Fixture({authority='armed',withFailingProfile=false,withStatefulProfile=false,codingRuntimeFactory=null,nativeDiffRuntimeFactory=null}={}) {
|
|
15
|
+
export async function createR34Fixture({authority='armed',withFailingProfile=false,withStatefulProfile=false,codingRuntimeFactory=null,nativeDiffRuntimeFactory=null,desktopMemoryProvider=null,memoryProvider=null,desktopRuntime=null}={}) {
|
|
16
16
|
const root = await mkdtemp(path.join(os.tmpdir(),'deadbyte-r34-e2e-'));
|
|
17
17
|
const project = path.join(root,'project');
|
|
18
18
|
const trust = path.join(root,'trust');
|
|
@@ -73,8 +73,8 @@ export async function createR34Fixture({authority='armed',withFailingProfile=fal
|
|
|
73
73
|
const machineFsRuntime = createMachineFsRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
74
74
|
const baseNativeDiffRuntime = createNativeDiffRuntime({policy,codingRuntime,machineFsRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
75
75
|
const nativeDiffRuntime = nativeDiffRuntimeFactory ? nativeDiffRuntimeFactory(baseNativeDiffRuntime) : baseNativeDiffRuntime;
|
|
76
|
-
const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,machineFsRuntime,
|
|
77
|
-
signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
76
|
+
const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime,desktopMemoryProviderOverride=desktopMemoryProvider,memoryProviderOverride=memoryProvider,desktopRuntimeOverride=desktopRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,machineFsRuntime,
|
|
77
|
+
desktopRuntime:desktopRuntimeOverride,desktopMemoryProvider:desktopMemoryProviderOverride,memoryProvider:memoryProviderOverride,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
78
78
|
return {
|
|
79
79
|
root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime:createRuntime(),
|
|
80
80
|
initial_sha256:sha(await readFile(target)),arm,disarm,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
6
|
+
import { loadPrivateSigningKey,loadPublicVerifyKey } from '../src/trust.mjs';
|
|
7
|
+
import { createR41MemoryProvider } from '../src/memory-r41.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
10
|
+
|
|
11
|
+
test('R41 planner request binds verified bounded memory before a mutating decision',async()=>{
|
|
12
|
+
const f=await createR34Fixture();
|
|
13
|
+
try{
|
|
14
|
+
const signing=await loadPrivateSigningKey(f.keys.privateKeyPath);
|
|
15
|
+
const verify=await loadPublicVerifyKey(f.keys.publicKeyPath);
|
|
16
|
+
const provider=createR41MemoryProvider({memoryRoot:path.join(f.root,'memory'),sourceReleaseManifest:sha('r41-release'),
|
|
17
|
+
writerVersion:'0.13.0',privateKey:signing.privateKey,publicKey:verify.publicKey,keyId:signing.keyId,byteBudget:65536});
|
|
18
|
+
const runtime=f.createRuntime({memoryProviderOverride:provider});
|
|
19
|
+
const goal=await runtime.submit({submissionId:'r41-memory-binding',rootId:'project',title:'repair',goal:'repair x.txt',acceptance:'x is y'});
|
|
20
|
+
const run=await runtime.run({goalId:goal.goal_id,maxCycles:1});
|
|
21
|
+
assert.equal(run.status,'input_required');
|
|
22
|
+
assert.match(run.planner_request.prompt,/"long_term_memory"/);
|
|
23
|
+
assert.match(run.planner_request.prompt,/"working_memory_sha256":"[0-9a-f]{64}"/);
|
|
24
|
+
const journal=await runtime.events({goalId:goal.goal_id,fromSeq:1,limit:100});
|
|
25
|
+
const requested=journal.events.find(event=>event.type==='planner_requested');
|
|
26
|
+
assert.match(requested.payload.working_memory_sha256,/^[0-9a-f]{64}$/);
|
|
27
|
+
assert.match(requested.payload.memory_snapshot_sha256,/^[0-9a-f]{64}$/);
|
|
28
|
+
assert.match(requested.payload.memory_index_sha256,/^[0-9a-f]{64}$/);
|
|
29
|
+
assert.equal(requested.payload.memory_source_release_manifest,sha('r41-release'));
|
|
30
|
+
}finally{await f.cleanup();}
|
|
31
|
+
});
|