deadbyte-mcp 0.11.3 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/MANIFEST.SHA256 +126 -54
  2. package/R41-PARENT.json +15 -0
  3. package/README.txt +5 -4
  4. package/acceptance/r40/desktop-live-drift.mjs +106 -0
  5. package/acceptance/r40/desktop-live-ground.mjs +110 -0
  6. package/acceptance/r40/desktop-live-input.mjs +128 -0
  7. package/acceptance/r40/desktop-live-observe.mjs +100 -0
  8. package/acceptance/r40/desktop-live-uia.mjs +121 -0
  9. package/bin/appcontainer-stage.obj +0 -0
  10. package/bin/bootstrap-advapi32.obj +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.obj +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.obj +0 -0
  15. package/bin/contained-reverse-worker.obj +0 -0
  16. package/bin/contained-transform-worker.obj +0 -0
  17. package/bin/containment-probe.obj +0 -0
  18. package/bin/deadbyte-contain.obj +0 -0
  19. package/bin/deadbyte-desktop-input-r40.exe +0 -0
  20. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  21. package/bin/deadbyte-desktop-observer-r40.exe +0 -0
  22. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  23. package/bin/deadbyte-desktop-uia-r40.exe +0 -0
  24. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.obj +0 -0
  27. package/bin/deadbyte-tunnel-host.obj +0 -0
  28. package/controller/README.TXT +7 -5
  29. package/controller/deadbyte-controller.config.psd1.example +3 -0
  30. package/controller/deadbyte-controller.ps1 +149 -4
  31. package/controller/deadbyte-desktop-policy.json +41 -0
  32. package/controller/deadbyte-process-policy.json +4 -4
  33. package/docs/ARCHITECTURE.md +6 -1
  34. package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
  35. package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
  36. package/native/deadbyte-desktop-input.cpp +455 -0
  37. package/native/deadbyte-desktop-observer.cpp +253 -0
  38. package/native/deadbyte-desktop-uia.cpp +307 -0
  39. package/package.json +12 -8
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/build-containment.ps1 +24 -14
  42. package/scripts/build-desktop-r40.ps1 +112 -0
  43. package/scripts/deferred-slot-operation-r40.mjs +126 -0
  44. package/scripts/deferred-slot-operation-r41.mjs +127 -0
  45. package/scripts/final-closure-r40.mjs +114 -0
  46. package/scripts/final-closure-r41.mjs +116 -0
  47. package/scripts/final-closure-verify-r40.mjs +212 -0
  48. package/scripts/final-closure-verify-r41.mjs +215 -0
  49. package/scripts/gate-windows-r40.ps1 +43 -0
  50. package/scripts/gate-windows-r41.ps1 +48 -0
  51. package/scripts/init-desktop-policy-r40.ps1 +85 -0
  52. package/scripts/r40-compact-ledger-probe.mjs +4 -0
  53. package/scripts/release-parity-r40.mjs +212 -0
  54. package/scripts/release-parity-r41.mjs +221 -0
  55. package/scripts/release-parity-tests-r40.ps1 +9 -0
  56. package/scripts/release-parity-tests-r41.ps1 +9 -0
  57. package/scripts/verify-memory-r41.mjs +88 -0
  58. package/scripts/verify-r41-parent.mjs +61 -0
  59. package/scripts/windows-gate-evidence-r40.mjs +52 -0
  60. package/scripts/windows-gate-evidence-r41.mjs +53 -0
  61. package/src/autonomous-context-engine.mjs +3 -1
  62. package/src/autonomous-planner-contracts.mjs +19 -1
  63. package/src/autonomous-planner.mjs +24 -0
  64. package/src/autonomous-runtime.mjs +164 -25
  65. package/src/capability-ledger.mjs +6 -1
  66. package/src/compact-gateway-mcp-tools.mjs +14 -2
  67. package/src/deadbyte-cli.mjs +72 -44
  68. package/src/desktop-contract.mjs +263 -0
  69. package/src/desktop-input-r40.mjs +148 -0
  70. package/src/desktop-mcp-tools-r40.mjs +126 -0
  71. package/src/desktop-memory-r40.mjs +272 -0
  72. package/src/desktop-observer-r40.mjs +231 -0
  73. package/src/desktop-policy-r40.mjs +110 -0
  74. package/src/desktop-runtime-r40.mjs +318 -0
  75. package/src/desktop-uia-r40.mjs +123 -0
  76. package/src/mcp-server.mjs +87 -19
  77. package/src/memory-r41.mjs +692 -0
  78. package/src/r40-compact-disarmed-probe.mjs +71 -0
  79. package/src/r40-full-surface-probe.mjs +34 -0
  80. package/src/release-generation.mjs +38 -5
  81. package/src/remote-console.mjs +41 -0
  82. package/src/remote-live-log.mjs +208 -45
  83. package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
  84. package/src/remote-mcp-r40-parity-client.mjs +200 -0
  85. package/src/remote-mcp-smoke-client.mjs +20 -6
  86. package/src/remote-result-journal.mjs +10 -2
  87. package/src/runtime-update.mjs +55 -17
  88. package/src/tool-audit.mjs +29 -2
  89. package/src/version.mjs +1 -1
  90. package/test/autonomous-capability-r36.test.mjs +43 -1
  91. package/test/autonomous-desktop-r40.test.mjs +188 -0
  92. package/test/capability-ledger-r36.test.mjs +1 -0
  93. package/test/cli-entrypoint.test.mjs +1 -1
  94. package/test/compact-catalog-r36.test.mjs +2 -2
  95. package/test/compact-gateway-r36.test.mjs +56 -4
  96. package/test/compact-max-catalog-r40.test.mjs +95 -0
  97. package/test/controller-desktop-r40.test.mjs +86 -0
  98. package/test/core.test.mjs +1 -1
  99. package/test/desktop-adversarial-r40.test.mjs +62 -0
  100. package/test/desktop-contract-r40.test.mjs +200 -0
  101. package/test/desktop-input-r40.test.mjs +63 -0
  102. package/test/desktop-mcp-tools-r40.test.mjs +61 -0
  103. package/test/desktop-memory-r40.test.mjs +141 -0
  104. package/test/desktop-observer-r40.test.mjs +119 -0
  105. package/test/desktop-policy-r40.test.mjs +111 -0
  106. package/test/desktop-runtime-r40.test.mjs +278 -0
  107. package/test/desktop-uia-r40.test.mjs +42 -0
  108. package/test/full-max-catalog-r40.test.mjs +91 -0
  109. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  110. package/test/memory-autonomous-integration-r41.test.mjs +31 -0
  111. package/test/memory-history-r41.test.mjs +126 -0
  112. package/test/memory-projections-r41.test.mjs +92 -0
  113. package/test/memory-retrieval-r41.test.mjs +124 -0
  114. package/test/memory-snapshot-r41.test.mjs +106 -0
  115. package/test/r33-closeout-regression.test.mjs +7 -7
  116. package/test/r33-finalization.test.mjs +15 -15
  117. package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
  118. package/test/r40-finalization.test.mjs +118 -0
  119. package/test/r41-finalization.test.mjs +38 -0
  120. package/test/release-generation.test.mjs +41 -9
  121. package/test/release-version.test.mjs +20 -61
  122. package/test/remote-console-r40.test.mjs +38 -0
  123. package/test/remote-live-log-r37.test.mjs +89 -32
  124. package/test/remote-r24-catalog.test.mjs +12 -3
  125. package/test/remote-result-journal.test.mjs +15 -0
  126. package/test/remote-session-cli.test.mjs +69 -46
  127. package/test/runtime-self-update.test.mjs +44 -0
@@ -0,0 +1,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
+ });
@@ -0,0 +1,61 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ desktopActionInputSchema,desktopControlInputSchema,desktopTargetInputSchema,registerDesktopTools
5
+ } from '../src/desktop-mcp-tools-r40.mjs';
6
+
7
+ const H=c=>c.repeat(64);
8
+ const frame=H('a');
9
+ const intent=H('b');
10
+
11
+ function fakeServer(){
12
+ const tools=new Map();
13
+ return {tools,registerTool(name,definition,handler){tools.set(name,{definition,handler});}};
14
+ }
15
+
16
+ test('R40 desktop action schema rejects ambiguous or authority-bypassing shapes',()=>{
17
+ assert.equal(desktopActionInputSchema.safeParse({
18
+ intent_id:intent,kind:'click_primary',expected_frame_id:frame,target_id:H('c'),point:{x:1,y:2}
19
+ }).success,false);
20
+ assert.equal(desktopActionInputSchema.safeParse({
21
+ intent_id:intent,kind:'text_input',expected_frame_id:frame,text:'hello',point:{x:1,y:2}
22
+ }).success,false);
23
+ assert.equal(desktopControlInputSchema.safeParse({op:'action',intent_id:intent,kind:'key_chord',expected_frame_id:frame,key_chord:['CTRL','CTRL']}).success,false);
24
+ assert.equal(desktopControlInputSchema.safeParse({op:'action',intent_id:intent,kind:'click_primary',expected_frame_id:frame,point:{x:1,y:2}}).success,true);
25
+ });
26
+
27
+ test('R40 T5 target schema supports local UIA query without arbitrary dispatch',()=>{
28
+ assert.equal(desktopTargetInputSchema.safeParse({frame_id:frame,uia_query:{kind:'name_exact',value:'Save',max_results:8,ordinal:0}}).success,true);
29
+ assert.equal(desktopTargetInputSchema.safeParse({frame_id:frame,uia_query:{kind:'root',value:'leak'}}).success,false);
30
+ assert.equal(desktopControlInputSchema.safeParse({op:'target',frame_id:frame,uia_query:{kind:'control_type',value:'button',max_results:4,ordinal:0}}).success,true);
31
+ });
32
+
33
+ test('R40 full desktop surface registers four narrow typed tools',()=>{
34
+ const server=fakeServer();
35
+ const runtime={status(){},observe(){},target(){},action(){}};
36
+ registerDesktopTools(server,{runtime});
37
+ assert.deepEqual([...server.tools.keys()].sort(),['desktop_action','desktop_observe','desktop_status','desktop_target']);
38
+ });
39
+
40
+ test('R40 public desktop action output removes raw text while preserving content and action hashes',async()=>{
41
+ const server=fakeServer();
42
+ const action={
43
+ schema:'deadbyte.desktop-action.v1',intent_id:intent,kind:'text_input',frame_id:frame,
44
+ desktop_epoch:H('d'),topology_sha256:H('e'),expected_window:null,target_id:null,point:null,
45
+ scroll_delta:null,key_chord:null,text:'DO_NOT_ECHO_ME',text_sha256:H('f'),action_sha256:H('1')
46
+ };
47
+ const runtime={
48
+ action:async()=>({status:'ok',injected:true,task_success:null,action,guard:{guard_sha256:H('2')},
49
+ post_observation:{change_observed:true},started_receipt_id:'1'.repeat(24),started_receipt_sha256:H('3'),
50
+ receipt_id:'2'.repeat(24),receipt_sha256:H('4')})
51
+ };
52
+ registerDesktopTools(server,{runtime});
53
+ const result=await server.tools.get('desktop_action').handler({
54
+ intent_id:intent,kind:'text_input',expected_frame_id:frame,target_id:null,point:null,scroll_delta:null,key_chord:null,text:'DO_NOT_ECHO_ME'
55
+ });
56
+ const encoded=JSON.stringify(result.structuredContent);
57
+ assert.equal(encoded.includes('DO_NOT_ECHO_ME'),false);
58
+ assert.equal(result.structuredContent.result.action,undefined);
59
+ assert.equal(result.structuredContent.result.action_summary.text_sha256,H('f'));
60
+ assert.equal(result.structuredContent.result.action_summary.action_sha256,H('1'));
61
+ });
@@ -0,0 +1,141 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, generateKeyPairSync, sign as cryptoSign } from 'node:crypto';
4
+ import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { canonicalJson } from '../src/canonical-json.mjs';
8
+ import {
9
+ deriveDesktopMemory,signDesktopMemory,verifyDesktopMemorySnapshot,writeDesktopMemorySnapshot,
10
+ loadDesktopEvidenceReceipts,createDesktopMemoryProvider
11
+ } from '../src/desktop-memory-r40.mjs';
12
+
13
+ const H=v=>createHash('sha256').update(String(v)).digest('hex');
14
+ const DOMAIN=Buffer.from('DEADBYTE-DESKTOP-RECEIPT-V1\0','utf8');
15
+ const policy=H('policy');
16
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
17
+ const keyId='memory-test-key';
18
+ let seq=0;
19
+ function receipt(kind,payload,at='2026-09-19T03:00:00.000Z',policySha256=policy){
20
+ const receipt_id=(++seq).toString(16).padStart(24,'0');
21
+ const body={schema:'deadbyte.desktop-receipt.v1',receipt_id,kind,policy_sha256:policySha256,observed_at_utc:at,payload};
22
+ const receipt_sha256=createHash('sha256').update(Buffer.from(canonicalJson(body),'utf8')).digest('hex');
23
+ const signed={...body,receipt_sha256};
24
+ const signature_base64=cryptoSign(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),privateKey).toString('base64');
25
+ return {...signed,attestation:{algorithm:'ed25519',key_id:keyId,signature_base64}};
26
+ }
27
+ const win={hwnd:'0x1234',pid:55,process_image_sha256:H('app'),title_sha256:H('title')};
28
+
29
+ test('T6 memory derives only authenticated compact Desktop facts and never carries raw payload or authority nonce',()=>{
30
+ const effect=H('effect'),fingerprint=H('fingerprint'),target=H('target');
31
+ const receipts=[
32
+ receipt('desktop_observation',{frame_id:H('f1'),desktop_epoch:H('epoch'),topology_sha256:H('topo'),active_window:win,
33
+ preview:{sha256:H('preview'),byte_length:100,width:10,height:10,encoding:'ppm-base64'}},'2026-09-19T03:00:00.000Z'),
34
+ receipt('desktop_grounding',{requested_frame_id:H('f1'),grounded_frame_id:H('f2'),target_id:target,
35
+ query_sha256:H('query'),query_kind:'name_exact',control_type:50000},'2026-09-19T03:00:01.000Z'),
36
+ receipt('desktop_action_started',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('action'),
37
+ target_id:target,kind:'click_primary',pre_frame_id:H('f2'),authority_instance_nonce:H('SECRET-NONCE')},'2026-09-19T03:00:02.000Z'),
38
+ receipt('desktop_post_observation',{frame_id:H('f3'),desktop_epoch:H('epoch'),topology_sha256:H('topo'),active_window:win},'2026-09-19T03:00:03.000Z'),
39
+ receipt('desktop_action_completed',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('action'),
40
+ effect:{side_effect_state:'confirmed_injected'},action_chain:{chain_sha256:H('chain')},
41
+ post_observation:{post_observation_sha256:H('post'),foreground_transition:{changed:false}}},'2026-09-19T03:00:04.000Z')
42
+ ];
43
+ const memory=deriveDesktopMemory(receipts,{policySha256:policy,publicKey,keyId});
44
+ assert.equal(memory.schema,'deadbyte.desktop-memory.v1');
45
+ assert.equal(memory.recent_effects.length,1);
46
+ assert.equal(memory.recent_effects[0].effect_id,effect);
47
+ assert.equal(memory.grounded_targets[0].target_id,target);
48
+ assert.equal(memory.windows.length,1);
49
+ assert.equal(memory.last_verified_desktop.frame_id,H('f3'));
50
+ const encoded=JSON.stringify(memory);
51
+ assert.equal(encoded.includes('SECRET-NONCE'),false);
52
+ assert.equal(encoded.includes('data_base64'),false);
53
+ assert.equal(encoded.includes('"query":"'),false);
54
+ assert.equal(encoded.includes('authority_instance_nonce'),false);
55
+ });
56
+
57
+ test('T6 memory keeps unknown effects unresolved until authenticated completion appears',()=>{
58
+ const effect=H('unknown-effect'),fingerprint=H('unknown-fp');
59
+ const started=receipt('desktop_action_started',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('a'),
60
+ target_id:H('t'),kind:'click_primary',pre_frame_id:H('f')});
61
+ const unknown=receipt('desktop_action_outcome_unknown',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('a'),side_effect_state:'unknown'});
62
+ const memory=deriveDesktopMemory([started,unknown],{policySha256:policy,publicKey,keyId});
63
+ assert.deepEqual(memory.unresolved_unknown_effects.map(x=>x.effect_id),[effect]);
64
+ });
65
+
66
+ test('T6 rejects receipt substitution before memory trust',()=>{
67
+ const r=receipt('desktop_observation',{frame_id:H('f'),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
68
+ const forged=structuredClone(r);forged.payload.frame_id=H('forged');
69
+ assert.throws(()=>deriveDesktopMemory([forged],{policySha256:policy,publicKey,keyId}),/unauthenticated receipt/);
70
+ });
71
+
72
+ test('T6 signed snapshot persists atomically and verifies independently',async()=>{
73
+ const r=receipt('desktop_observation',{frame_id:H('f'),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
74
+ const memory=deriveDesktopMemory([r],{policySha256:policy,publicKey,keyId});
75
+ const snapshot=signDesktopMemory(memory,{privateKey,keyId});
76
+ const proof=verifyDesktopMemorySnapshot(snapshot,{publicKey,keyId,policySha256:policy});
77
+ assert.equal(proof.ok,true);
78
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-'));
79
+ const file=path.join(root,'desktop-memory.json');
80
+ const written=await writeDesktopMemorySnapshot(file,snapshot);
81
+ assert.equal(written.snapshot_sha256,snapshot.snapshot_sha256);
82
+ const disk=JSON.parse(await readFile(file,'utf8'));
83
+ assert.equal(verifyDesktopMemorySnapshot(disk,{publicKey,keyId,policySha256:policy}).ok,true);
84
+ const tampered=structuredClone(disk);tampered.memory.last_verified_desktop.frame_id=H('tamper');
85
+ assert.equal(verifyDesktopMemorySnapshot(tampered,{publicKey,keyId,policySha256:policy}).ok,false);
86
+ });
87
+
88
+
89
+ test('T6 evidence provider loads bounded signed receipts and emits compact planner projection',async()=>{
90
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-provider-'));
91
+ const evidence=path.join(root,'evidence');await mkdir(evidence);
92
+ const r1=receipt('desktop_observation',{frame_id:H('pf'),desktop_epoch:H('pe'),topology_sha256:H('pt'),active_window:win});
93
+ const r2=receipt('desktop_grounding',{requested_frame_id:H('pf'),grounded_frame_id:H('pg'),target_id:H('target'),
94
+ query_sha256:H('query'),query_kind:'root',control_type:50032});
95
+ for(const r of [r1,r2])await writeFile(path.join(evidence,r.receipt_id+'.json'),canonicalJson(r)+'\n');
96
+ await writeFile(path.join(evidence,'desktop-memory.json'),'{"ignored":true}\n');
97
+ const loaded=await loadDesktopEvidenceReceipts(evidence,{maxReceipts:4,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
98
+ assert.equal(loaded.length,2);
99
+ const provider=createDesktopMemoryProvider({
100
+ evidenceDir:evidence,snapshotPath:path.join(evidence,'desktop-memory.json'),policySha256:policy,
101
+ privateKey,publicKey,keyId,maxReceipts:4,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024
102
+ });
103
+ const refreshed=await provider.refresh();
104
+ assert.equal(refreshed.status,'ok');
105
+ assert.equal(refreshed.source_receipt_count,2);
106
+ assert.equal(refreshed.planner_context.schema,'deadbyte.desktop-memory-planner.v1');
107
+ assert.equal(refreshed.planner_context.source_receipt_count,2);
108
+ assert.equal(refreshed.planner_context.grounded_targets.length,1);
109
+ const encoded=JSON.stringify(refreshed.planner_context);
110
+ assert.equal(encoded.includes('data_base64'),false);
111
+ assert.equal(encoded.includes('authority_instance_nonce'),false);
112
+ const disk=JSON.parse(await readFile(path.join(evidence,'desktop-memory.json'),'utf8'));
113
+ assert.equal(verifyDesktopMemorySnapshot(disk,{publicKey,keyId,policySha256:policy}).ok,true);
114
+ });
115
+
116
+ test('T6 evidence loader fails closed instead of silently truncating receipt history',async()=>{
117
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-limit-'));
118
+ for(let i=0;i<3;i++){
119
+ const r=receipt('desktop_observation',{frame_id:H('limit-'+i),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
120
+ await writeFile(path.join(root,r.receipt_id+'.json'),canonicalJson(r)+'\n');
121
+ }
122
+ await assert.rejects(loadDesktopEvidenceReceipts(root,{maxReceipts:2,maxDirectoryEntries:8}),/receipt limit exceeded/);
123
+ });
124
+
125
+
126
+ test('T6 current-policy memory rebuild ignores authenticated historical receipts from superseded Desktop policy',async()=>{
127
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-policy-rollover-'));
128
+ const stalePolicy=H('stale-policy');
129
+ const stale=receipt('desktop_observation',{frame_id:H('stale-f'),desktop_epoch:H('stale-e'),topology_sha256:H('stale-t'),active_window:win},'2026-09-19T02:00:00.000Z',stalePolicy);
130
+ const current1=receipt('desktop_observation',{frame_id:H('current-f1'),desktop_epoch:H('current-e'),topology_sha256:H('current-t'),active_window:win});
131
+ const current2=receipt('desktop_grounding',{requested_frame_id:H('current-f1'),grounded_frame_id:H('current-f2'),target_id:H('current-target'),query_sha256:H('current-query'),query_kind:'root',control_type:50032});
132
+ for(const item of [stale,current1,current2]) await writeFile(path.join(root,item.receipt_id+'.json'),canonicalJson(item)+'\n');
133
+ const filtered=await loadDesktopEvidenceReceipts(root,{policySha256:policy,maxReceipts:2,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
134
+ assert.equal(filtered.length,2);
135
+ assert.ok(filtered.every(item=>item.policy_sha256===policy));
136
+ const provider=createDesktopMemoryProvider({evidenceDir:root,snapshotPath:path.join(root,'desktop-memory.json'),policySha256:policy,privateKey,publicKey,keyId,maxReceipts:2,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
137
+ const refreshed=await provider.refresh();
138
+ assert.equal(refreshed.source_receipt_count,2);
139
+ assert.equal(refreshed.planner_context.source_receipt_count,2);
140
+ assert.equal(refreshed.planner_context.policy_sha256,policy);
141
+ });
@@ -0,0 +1,119 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { parseNativeDesktopFrame,createDesktopObserverRuntime,createDesktopPreview } from '../src/desktop-observer-r40.mjs';
5
+
6
+ const H=value=>createHash('sha256').update(value).digest('hex');
7
+
8
+ function packet(overrides={},pixelByte=0x23){
9
+ const header={
10
+ schema:'deadbyte.desktop-native-frame.v1',
11
+ backend:'gdi-bitblt-fallback',
12
+ pixel_format:'BGRA8',
13
+ left:-4,top:0,width:8,height:2,stride:32,byte_length:64,
14
+ system_dpi:96,session_id:1,input_desktop:'Default',
15
+ foreground_hwnd:'0x1234',foreground_pid:444,
16
+ foreground_title:'Editor',foreground_image:'C:\\Apps\\editor.exe',
17
+ monitors:[
18
+ {left:-4,top:0,width:4,height:2,primary:false,device:'DISPLAY2'},
19
+ {left:0,top:0,width:4,height:2,primary:true,device:'DISPLAY1'}
20
+ ],
21
+ ...overrides
22
+ };
23
+ const pixels=Buffer.alloc(header.byte_length,pixelByte);
24
+ return Buffer.concat([Buffer.from(JSON.stringify(header)+'\n','utf8'),pixels]);
25
+ }
26
+
27
+ test('native parser binds pixels, topology and foreground identity',()=>{
28
+ const parsed=parseNativeDesktopFrame(packet());
29
+ assert.equal(parsed.image_sha256,H(Buffer.alloc(64,0x23)));
30
+ assert.match(parsed.topology_sha256,/^[0-9a-f]{64}$/);
31
+ assert.deepEqual(parsed.bounds,{left:-4,top:0,width:8,height:2});
32
+ assert.equal(parsed.active_window.hwnd,'0x1234');
33
+ assert.equal(parsed.active_window.pid,444);
34
+ assert.equal(parsed.active_window.title_sha256,H('Editor'));
35
+ assert.equal(parsed.active_window.process_image_sha256,H('C:\\Apps\\editor.exe'));
36
+ assert.equal(parsed.pixels.length,64);
37
+ });
38
+
39
+ test('native parser rejects payload length substitution',()=>{
40
+ const value=packet();
41
+ assert.throws(()=>parseNativeDesktopFrame(value.subarray(0,value.length-1)),/payload length mismatch/);
42
+ });
43
+
44
+ test('native parser rejects stride and byte length lies',()=>{
45
+ assert.throws(()=>parseNativeDesktopFrame(packet({stride:64,byte_length:128})),/stride mismatch/);
46
+ assert.throws(()=>parseNativeDesktopFrame(packet({byte_length:68})),/byte_length mismatch/);
47
+ });
48
+
49
+ test('native parser rejects monitor topology outside virtual desktop',()=>{
50
+ assert.throws(()=>parseNativeDesktopFrame(packet({monitors:[
51
+ {left:0,top:0,width:4,height:2,primary:true,device:'DISPLAY1'},
52
+ {left:10,top:0,width:4,height:2,primary:false,device:'BAD'}
53
+ ]})),/outside virtual desktop/);
54
+ });
55
+
56
+ test('native parser requires exactly one primary monitor',()=>{
57
+ assert.throws(()=>parseNativeDesktopFrame(packet({monitors:[
58
+ {left:-4,top:0,width:4,height:2,primary:false,device:'DISPLAY2'},
59
+ {left:0,top:0,width:4,height:2,primary:false,device:'DISPLAY1'}
60
+ ]})),/exactly one primary/);
61
+ });
62
+
63
+ test('observer runtime creates stable epoch family and distinct frame identities',async()=>{
64
+ let index=0;
65
+ const provider=async()=>parseNativeDesktopFrame(packet({},++index));
66
+ const runtime=createDesktopObserverRuntime({
67
+ captureProvider:provider,expectedObserverSha256:H('observer'),runtimeNonce:'r40-test-runtime',
68
+ now:()=>index===1?'2026-09-19T02:10:00.000Z':'2026-09-19T02:10:00.500Z'
69
+ });
70
+ const a=await runtime.capture();
71
+ const b=await runtime.capture();
72
+ assert.equal(a.frame.desktop_epoch,b.frame.desktop_epoch);
73
+ assert.notEqual(a.frame.frame_id,b.frame.frame_id);
74
+ assert.notEqual(a.frame.image_sha256,b.frame.image_sha256);
75
+ assert.equal(runtime.frameStatus(b.frame.frame_id).generation,2);
76
+ assert.equal(runtime.framePixels(b.frame.frame_id).length,64);
77
+ });
78
+
79
+ test('observer runtime evicts old raw frames by bounded cache',async()=>{
80
+ let index=0;
81
+ const runtime=createDesktopObserverRuntime({
82
+ captureProvider:async()=>parseNativeDesktopFrame(packet({},++index)),
83
+ expectedObserverSha256:H('observer'),runtimeNonce:'r40-cache',maxCachedFrames:2,
84
+ now:()=>new Date(1_700_000_000_000+index*100).toISOString()
85
+ });
86
+ const a=await runtime.capture();
87
+ await runtime.capture();
88
+ const c=await runtime.capture();
89
+ assert.throws(()=>runtime.frameStatus(a.frame.frame_id),/expired or unknown/);
90
+ assert.equal(runtime.framePixels(c.frame.frame_id).length,64);
91
+ });
92
+
93
+ test('observer runtime rejects secure or unexpected input desktop',async()=>{
94
+ const runtime=createDesktopObserverRuntime({
95
+ captureProvider:async()=>parseNativeDesktopFrame(packet({input_desktop:'Winlogon'})),
96
+ expectedObserverSha256:H('observer'),runtimeNonce:'r40-secure'
97
+ });
98
+ await assert.rejects(runtime.capture(),/is not allowed/);
99
+ });
100
+
101
+ test('raw title and process image do not escape parsed active-window identity',()=>{
102
+ const parsed=parseNativeDesktopFrame(packet());
103
+ assert.equal(Object.hasOwn(parsed.active_window,'title'),false);
104
+ assert.equal(Object.hasOwn(parsed.active_window,'image'),false);
105
+ assert.equal(JSON.stringify(parsed.active_window).includes('Editor'),false);
106
+ assert.equal(JSON.stringify(parsed.active_window).includes('editor.exe'),false);
107
+ });
108
+
109
+ test('T5 preview is deterministic PPM and hard-bounded by max_preview_bytes',()=>{
110
+ const parsed=parseNativeDesktopFrame(packet());
111
+ const a=createDesktopPreview({pixels:parsed.pixels,width:8,height:2,stride:32,maxBytes:128});
112
+ const b=createDesktopPreview({pixels:parsed.pixels,width:8,height:2,stride:32,maxBytes:128});
113
+ assert.equal(a.schema,'deadbyte.desktop-preview.v1');
114
+ assert.equal(a.encoding,'ppm-base64');
115
+ assert.ok(a.byte_length<=128);
116
+ assert.equal(a.sha256,b.sha256);
117
+ assert.equal(a.data_base64,b.data_base64);
118
+ assert.equal(Buffer.from(a.data_base64,'base64').subarray(0,2).toString('ascii'),'P6');
119
+ });