deadbyte-mcp 0.15.21 → 0.15.22

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.
@@ -0,0 +1,217 @@
1
+ #requires -Version 5.1
2
+ [CmdletBinding()]
3
+ param()
4
+ Set-StrictMode -Version 3.0
5
+ $ErrorActionPreference = 'Stop'
6
+
7
+ function Fail([string]$Message) { throw "LOCAL_D_PROMOTION: $Message" }
8
+ function Sha256-File([string]$Path) {
9
+ return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
10
+ }
11
+ function Sha256-Text([string]$Text) {
12
+ $sha = [System.Security.Cryptography.SHA256]::Create()
13
+ try {
14
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
15
+ return -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') })
16
+ } finally { $sha.Dispose() }
17
+ }
18
+ function Write-Utf8NoBom([string]$Path,[string]$Text) {
19
+ [System.IO.File]::WriteAllText($Path,$Text,(New-Object System.Text.UTF8Encoding($false)))
20
+ }
21
+ function Read-Raw([string]$Path) {
22
+ return [System.IO.File]::ReadAllText($Path,[System.Text.Encoding]::UTF8)
23
+ }
24
+
25
+ $stateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
26
+ $pointerPath = Join-Path $stateRoot 'active-release.json'
27
+ $launcher = Join-Path $stateRoot 'bin\DEADBYTE-MCP.ps1'
28
+ $runtimeRoot = Join-Path $stateRoot 'runtime'
29
+ if (-not (Test-Path -LiteralPath $pointerPath -PathType Leaf)) { Fail 'active pointer missing' }
30
+ if (-not (Test-Path -LiteralPath $launcher -PathType Leaf)) { Fail 'stable launcher missing' }
31
+
32
+ $pointerText = Read-Raw $pointerPath
33
+ $pointer = $pointerText | ConvertFrom-Json
34
+ if ([string]$pointer.schema -ne 'deadbyte.active-release.v1') { Fail 'active pointer schema mismatch' }
35
+ $oldGeneration = [System.IO.Path]::GetFullPath([string]$pointer.generation_root)
36
+ $oldManifest = Join-Path $oldGeneration 'GENERATION.SHA256'
37
+ if (-not (Test-Path -LiteralPath $oldManifest -PathType Leaf)) { Fail 'old generation manifest missing' }
38
+ $oldManifestHash = Sha256-File $oldManifest
39
+ if ($oldManifestHash -ne [string]$pointer.generation_manifest_sha256) { Fail 'old generation manifest hash mismatch' }
40
+
41
+ $manifestLines = @(Get-Content -LiteralPath $oldManifest -Encoding UTF8)
42
+ $entries = @()
43
+ foreach ($line in $manifestLines) {
44
+ if ([string]::IsNullOrWhiteSpace([string]$line)) { continue }
45
+ if ([string]$line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail "malformed old generation manifest line: $line" }
46
+ $expected = [string]$Matches[1]
47
+ $name = [string]$Matches[2]
48
+ $path = Join-Path $oldGeneration $name
49
+ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { Fail "old generation file missing: $name" }
50
+ $actual = Sha256-File $path
51
+ if ($actual -ne $expected) { Fail "old generation hash mismatch: $name" }
52
+ $entries += [pscustomobject]@{ Name=$name; Expected=$expected }
53
+ }
54
+ $names = @($entries | ForEach-Object { $_.Name })
55
+ foreach ($required in @('coding-policy.json','autonomous-policy.json','controller-config.psd1')) {
56
+ if ($required -notin $names) { Fail "required generation file not manifested: $required" }
57
+ }
58
+
59
+ $oldCodingPath = Join-Path $oldGeneration 'coding-policy.json'
60
+ $oldAutonomousPath = Join-Path $oldGeneration 'autonomous-policy.json'
61
+ $oldCodingHash = Sha256-File $oldCodingPath
62
+ $oldAutonomousHash = Sha256-File $oldAutonomousPath
63
+
64
+ $coding = (Read-Raw $oldCodingPath) | ConvertFrom-Json
65
+ if ([string]$coding.schema -ne 'deadbyte.coding-policy.v1' -or $coding.enabled -ne $true) { Fail 'coding policy schema/enabled mismatch' }
66
+ if ($null -eq $coding.roots) { Fail 'coding roots missing' }
67
+
68
+ $localRoot = [pscustomobject][ordered]@{
69
+ path = 'D:\'
70
+ read = $true
71
+ write = $true
72
+ exec = $true
73
+ deny_paths = @()
74
+ }
75
+ if ($coding.roots.PSObject.Properties.Name -contains 'local_d') {
76
+ $coding.roots.local_d = $localRoot
77
+ } else {
78
+ $coding.roots | Add-Member -NotePropertyName 'local_d' -NotePropertyValue $localRoot
79
+ }
80
+ foreach ($commandProp in @($coding.commands.PSObject.Properties)) {
81
+ $command = $commandProp.Value
82
+ if ($command.PSObject.Properties.Name -contains 'root_ids') {
83
+ $ids = @($command.root_ids)
84
+ if ('local_d' -notin $ids) { $command.root_ids = @($ids + 'local_d') }
85
+ }
86
+ }
87
+ $codingText = ($coding | ConvertTo-Json -Depth 32) + [Environment]::NewLine
88
+ $newCodingHash = Sha256-Text $codingText
89
+
90
+ $autonomous = (Read-Raw $oldAutonomousPath) | ConvertFrom-Json
91
+ if ([string]$autonomous.schema -ne 'deadbyte.autonomous-policy.v1' -or $autonomous.enabled -ne $true) { Fail 'autonomous policy schema/enabled mismatch' }
92
+ $autonomous.coding_policy_sha256 = $newCodingHash
93
+ $autonomousText = ($autonomous | ConvertTo-Json -Depth 32) + [Environment]::NewLine
94
+ $newAutonomousHash = Sha256-Text $autonomousText
95
+
96
+ $seed = $oldManifestHash + [Environment]::NewLine + $newCodingHash + [Environment]::NewLine + $newAutonomousHash + [Environment]::NewLine + 'local_d=D:\'
97
+ $generationSuffix = (Sha256-Text $seed).Substring(0,16)
98
+ $newGenerationId = ([string]$pointer.release_id) + '-g' + $generationSuffix
99
+ $generationStore = Join-Path $stateRoot 'generations'
100
+ $newGeneration = Join-Path $generationStore $newGenerationId
101
+ if (Test-Path -LiteralPath $newGeneration) { Fail "target generation already exists: $newGeneration" }
102
+ New-Item -ItemType Directory -Path $newGeneration | Out-Null
103
+
104
+ $pointerBackup = Join-Path $runtimeRoot 'active-release.before-local-d.json'
105
+ $codingGrantPath = Join-Path $runtimeRoot 'coding-grant.json'
106
+ $autonomousGrantPath = Join-Path $runtimeRoot 'autonomous-grant.json'
107
+ $hadCodingGrant = Test-Path -LiteralPath $codingGrantPath -PathType Leaf
108
+ $hadAutonomousGrant = Test-Path -LiteralPath $autonomousGrantPath -PathType Leaf
109
+ $oldCodingGrantText = if ($hadCodingGrant) { Read-Raw $codingGrantPath } else { $null }
110
+ $oldAutonomousGrantText = if ($hadAutonomousGrant) { Read-Raw $autonomousGrantPath } else { $null }
111
+ $wasAutonomousArmed = $false
112
+ if ($hadCodingGrant -and $hadAutonomousGrant) {
113
+ try {
114
+ $cg = $oldCodingGrantText | ConvertFrom-Json
115
+ $ag = $oldAutonomousGrantText | ConvertFrom-Json
116
+ $wasAutonomousArmed =
117
+ ([string]$cg.policy_sha256 -eq $oldCodingHash) -and
118
+ ([string]$ag.policy_sha256 -eq $oldAutonomousHash) -and
119
+ ([string]$ag.coding_policy_sha256 -eq $oldCodingHash)
120
+ } catch { $wasAutonomousArmed = $false }
121
+ }
122
+
123
+ $switched = $false
124
+ try {
125
+ foreach ($entry in $entries) {
126
+ $src = Join-Path $oldGeneration $entry.Name
127
+ $dst = Join-Path $newGeneration $entry.Name
128
+ Copy-Item -LiteralPath $src -Destination $dst
129
+ }
130
+
131
+ Write-Utf8NoBom (Join-Path $newGeneration 'coding-policy.json') $codingText
132
+ Write-Utf8NoBom (Join-Path $newGeneration 'autonomous-policy.json') $autonomousText
133
+
134
+ $configPath = Join-Path $newGeneration 'controller-config.psd1'
135
+ $configText = Read-Raw $configPath
136
+ if ($configText.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -lt 0) { Fail 'controller config is not bound to old generation root' }
137
+ $configText = $configText.Replace($oldGeneration,$newGeneration)
138
+ Write-Utf8NoBom $configPath $configText
139
+ $cfg = Import-PowerShellDataFile -LiteralPath $configPath
140
+ if ([System.IO.Path]::GetFullPath([string]$cfg.CodingPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'coding-policy.json'))) { Fail 'new controller CodingPolicyFile binding mismatch' }
141
+ if ([System.IO.Path]::GetFullPath([string]$cfg.AutonomousPolicyFile) -ne [System.IO.Path]::GetFullPath((Join-Path $newGeneration 'autonomous-policy.json'))) { Fail 'new controller AutonomousPolicyFile binding mismatch' }
142
+
143
+ foreach ($entry in $entries) {
144
+ $p = Join-Path $newGeneration $entry.Name
145
+ $text = $null
146
+ try { $text = Read-Raw $p } catch { $text = $null }
147
+ if ($null -ne $text -and $entry.Name -ne 'controller-config.psd1' -and $text.IndexOf($oldGeneration,[System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
148
+ Fail "unexpected old generation binding remains in $($entry.Name)"
149
+ }
150
+ }
151
+
152
+ $newManifestLines = @()
153
+ foreach ($entry in $entries) {
154
+ $p = Join-Path $newGeneration $entry.Name
155
+ $newManifestLines += ((Sha256-File $p) + ' ' + $entry.Name)
156
+ }
157
+ $newManifestText = ($newManifestLines -join [Environment]::NewLine) + [Environment]::NewLine
158
+ $newManifestPath = Join-Path $newGeneration 'GENERATION.SHA256'
159
+ Write-Utf8NoBom $newManifestPath $newManifestText
160
+ $newManifestHash = Sha256-File $newManifestPath
161
+
162
+ foreach ($line in $newManifestLines) {
163
+ if ($line -notmatch '^([0-9a-f]{64}) ([A-Za-z0-9._-]+)$') { Fail 'new manifest internal format error' }
164
+ $actual = Sha256-File (Join-Path $newGeneration ([string]$Matches[2]))
165
+ if ($actual -ne [string]$Matches[1]) { Fail "new generation verification failed: $($Matches[2])" }
166
+ }
167
+ if ((Sha256-File (Join-Path $newGeneration 'coding-policy.json')) -ne $newCodingHash) { Fail 'new coding hash self-check failed' }
168
+ if ((Sha256-File (Join-Path $newGeneration 'autonomous-policy.json')) -ne $newAutonomousHash) { Fail 'new autonomous hash self-check failed' }
169
+
170
+ Write-Utf8NoBom $pointerBackup $pointerText
171
+ $pointer.generation_root = $newGeneration
172
+ $pointer.config_path = Join-Path $newGeneration 'controller-config.psd1'
173
+ $pointer.generation_manifest_sha256 = $newManifestHash
174
+ if ($pointer.PSObject.Properties.Name -contains 'generation_id') { $pointer.generation_id = $newGenerationId }
175
+ if ($pointer.PSObject.Properties.Name -contains 'generation_manifest_path') { $pointer.generation_manifest_path = $newManifestPath }
176
+ if ($pointer.PSObject.Properties.Name -contains 'updated_at_utc') { $pointer.updated_at_utc = [DateTime]::UtcNow.ToString('o') }
177
+ $newPointerText = ($pointer | ConvertTo-Json -Depth 32) + [Environment]::NewLine
178
+ $pointerTmp = $pointerPath + '.local-d.tmp'
179
+ Write-Utf8NoBom $pointerTmp $newPointerText
180
+ Move-Item -LiteralPath $pointerTmp -Destination $pointerPath -Force
181
+ $switched = $true
182
+
183
+ $statusText = (& $launcher status 2>&1 | Out-String)
184
+ if ($LASTEXITCODE -ne 0) { Fail "launcher status failed after pointer switch: $statusText" }
185
+
186
+ $armCodingText = (& $launcher armcoding 2>&1 | Out-String)
187
+ if ($LASTEXITCODE -ne 0) { Fail "armcoding failed after pointer switch: $armCodingText" }
188
+
189
+ $armAutonomousText = ''
190
+ if ($wasAutonomousArmed) {
191
+ $armAutonomousText = (& $launcher armautonomous 2>&1 | Out-String)
192
+ if ($LASTEXITCODE -ne 0) { Fail "armautonomous failed after pointer switch: $armAutonomousText" }
193
+ }
194
+
195
+ Write-Output 'LOCAL_D_PROMOTION=PASS'
196
+ Write-Output ('OLD_GENERATION=' + $oldGeneration)
197
+ Write-Output ('NEW_GENERATION=' + $newGeneration)
198
+ Write-Output ('OLD_GENERATION_MANIFEST_SHA256=' + $oldManifestHash)
199
+ Write-Output ('NEW_GENERATION_MANIFEST_SHA256=' + $newManifestHash)
200
+ Write-Output ('OLD_CODING_POLICY_SHA256=' + $oldCodingHash)
201
+ Write-Output ('NEW_CODING_POLICY_SHA256=' + $newCodingHash)
202
+ Write-Output ('NEW_AUTONOMOUS_POLICY_SHA256=' + $newAutonomousHash)
203
+ Write-Output ('AUTONOMOUS_REARMED=' + $wasAutonomousArmed)
204
+ Write-Output ('ROLLBACK_POINTER=' + $pointerBackup)
205
+ Write-Output 'RESTART_REQUIRED=YES'
206
+ } catch {
207
+ $message = $_.Exception.Message
208
+ if ($switched) {
209
+ try { Write-Utf8NoBom $pointerPath $pointerText } catch {}
210
+ if ($hadCodingGrant) { try { Write-Utf8NoBom $codingGrantPath $oldCodingGrantText } catch {} }
211
+ else { try { Remove-Item -LiteralPath $codingGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
212
+ if ($hadAutonomousGrant) { try { Write-Utf8NoBom $autonomousGrantPath $oldAutonomousGrantText } catch {} }
213
+ else { try { Remove-Item -LiteralPath $autonomousGrantPath -Force -ErrorAction SilentlyContinue } catch {} }
214
+ }
215
+ try { if (Test-Path -LiteralPath $newGeneration) { Remove-Item -LiteralPath $newGeneration -Recurse -Force } } catch {}
216
+ Fail $message
217
+ }
@@ -0,0 +1,8 @@
1
+ $ErrorActionPreference='Stop'
2
+ Set-StrictMode -Version Latest
3
+ $Root=Split-Path -Parent $PSScriptRoot
4
+ Push-Location $Root
5
+ try {
6
+ & node --test --test-concurrency=1 test/r48-release-contract.test.mjs test/release-version.test.mjs test/cli-entrypoint.test.mjs test/package-boundary.test.mjs test/release-smoke-isolation.test.mjs test/full-max-catalog-r48.test.mjs
7
+ if($LASTEXITCODE -ne 0){throw "R48 post-bump release checks failed exit=$LASTEXITCODE"}
8
+ } finally { Pop-Location }
@@ -0,0 +1,8 @@
1
+ $ErrorActionPreference='Stop'
2
+ Set-StrictMode -Version Latest
3
+ $Root=Split-Path -Parent $PSScriptRoot
4
+ Push-Location $Root
5
+ try {
6
+ & node --test test/machine-fs-runtime.test.mjs test/windows-observation-r48.test.mjs test/windows-observation-surface-r48.test.mjs test/hardware-observation-r45.test.mjs test/observation-mcp-tools.test.mjs test/compact-gateway-r36.test.mjs test/capability-resolver.test.mjs test/compact-catalog-r36.test.mjs test/compact-max-catalog-r45.test.mjs test/full-max-catalog-r45.test.mjs test/full-max-catalog-r46.test.mjs test/full-max-catalog-r47.test.mjs test/full-max-catalog-r48.test.mjs
7
+ if($LASTEXITCODE -ne 0){throw "R48 focused regression failed exit=$LASTEXITCODE"}
8
+ } finally { Pop-Location }
@@ -137,6 +137,13 @@ export const R36_CAPABILITY_ENTRIES=Object.freeze([
137
137
  gateway('observation.process_list','machine_process_list','machine_observe','process_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
138
138
  gateway('observation.system_status','machine_system_status','machine_observe','system_status','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
139
139
  gateway('observation.hardware_query','machine_hardware_query','machine_observe','hardware_query','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
140
+ gateway('observation.registry_query','machine_windows_query','machine_observe','registry_query','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
141
+ gateway('observation.service_list','machine_windows_query','machine_observe','service_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
142
+ gateway('observation.driver_list','machine_windows_query','machine_observe','driver_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
143
+ gateway('observation.socket_list','machine_windows_query','machine_observe','socket_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
144
+ gateway('observation.volume_list','machine_windows_query','machine_observe','volume_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
145
+ gateway('observation.usb_list','machine_windows_query','machine_observe','usb_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
146
+ gateway('observation.wsl_list','machine_windows_query','machine_observe','wsl_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
140
147
 
141
148
  gateway('desktop.status','desktop_status','desktop_control','status','DEADBYTE_DESKTOP_RUNTIME','none',{receipt_class:'desktop'}),
142
149
  gateway('desktop.observe','desktop_observe','desktop_control','observe','DEADBYTE_DESKTOP_RUNTIME','none',{receipt_class:'desktop'}),
@@ -67,6 +67,13 @@ const CAPABILITY_OPERATIONS = Object.freeze({
67
67
  'observation.process_list': ['process_list'],
68
68
  'observation.system_status': ['system_status'],
69
69
  'observation.hardware_query': ['hardware_query'],
70
+ 'observation.registry_query': ['registry_query'],
71
+ 'observation.service_list': ['service_list'],
72
+ 'observation.driver_list': ['driver_list'],
73
+ 'observation.socket_list': ['socket_list'],
74
+ 'observation.volume_list': ['volume_list'],
75
+ 'observation.usb_list': ['usb_list'],
76
+ 'observation.wsl_list': ['wsl_list'],
70
77
  'desktop.status': ['status'],
71
78
  'desktop.observe': ['observe'],
72
79
  'desktop.target': ['target'],
@@ -55,7 +55,14 @@ export const machineObserveInputSchema=z.discriminatedUnion('op',[
55
55
  z.strictObject({op:z.literal('search_cancel'),search_id:hex64}),
56
56
  z.strictObject({op:z.literal('process_list'),limit:z.number().int().min(1).max(256).default(100),include_external:z.boolean().default(false),cursor:z.string().max(512).nullable().default(null)}),
57
57
  z.strictObject({op:z.literal('system_status')}),
58
- z.strictObject({op:z.literal('hardware_query'),query:z.enum(['cpu','gpu','memory','storage','network','pci'])})
58
+ z.strictObject({op:z.literal('hardware_query'),query:z.enum(['cpu','gpu','memory','storage','network','pci'])}),
59
+ z.strictObject({op:z.literal('registry_query'),hive:z.enum(['HKLM','HKCU']),key:z.string().min(1).max(1024),value:z.string().max(260).nullable().default(null)}),
60
+ z.strictObject({op:z.literal('service_list')}),
61
+ z.strictObject({op:z.literal('driver_list')}),
62
+ z.strictObject({op:z.literal('socket_list')}),
63
+ z.strictObject({op:z.literal('volume_list')}),
64
+ z.strictObject({op:z.literal('usb_list')}),
65
+ z.strictObject({op:z.literal('wsl_list')})
59
66
  ]);
60
67
 
61
68
  const capabilityManifestInputSchema=z.strictObject({});
@@ -119,7 +126,14 @@ async function machineObserve(args,machineRuntime,observationRuntime){
119
126
  search_cancel:r=>r.searchCancel({searchId:args.search_id}),
120
127
  process_list:r=>r.processList({limit:args.limit,includeExternal:args.include_external,cursor:args.cursor}),
121
128
  system_status:r=>r.systemStatus(),
122
- hardware_query:r=>r.hardwareQuery({query:args.query})
129
+ hardware_query:r=>r.hardwareQuery({query:args.query}),
130
+ registry_query:r=>r.windowsQuery({query:'registry',hive:args.hive,key:args.key,value:args.value}),
131
+ service_list:r=>r.windowsQuery({query:'services'}),
132
+ driver_list:r=>r.windowsQuery({query:'drivers'}),
133
+ socket_list:r=>r.windowsQuery({query:'sockets'}),
134
+ volume_list:r=>r.windowsQuery({query:'volumes'}),
135
+ usb_list:r=>r.windowsQuery({query:'usb'}),
136
+ wsl_list:r=>r.windowsQuery({query:'wsl'})
123
137
  };
124
138
  return invoke(args.op,observationRuntime,'observation',map[args.op]);
125
139
  }
@@ -338,7 +338,7 @@ async function atomicWrite(target, bytes, expectedSha256, authorityCheck = null)
338
338
  assertExpected: checkExpectedFile, authorityCheck, tempTag: 'deadbyte-machine-fs' });
339
339
  }
340
340
 
341
- export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath, readWorkerPath = machineFsReadWorkerPath }) {
341
+ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath, readWorkerPath = machineFsReadWorkerPath, listEntryLstat = lstat, listEntrySha256File = sha256File }) {
342
342
  if (!policy) throw new Error('machine fs runtime requires coding policy');
343
343
  if (!signingKeyPath || !verifyKeyPath) throw new Error('machine fs runtime requires trust key paths');
344
344
 
@@ -384,9 +384,22 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
384
384
  const rel = found.relative === '.' ? child.name : `${found.relative}/${child.name}`;
385
385
  if (child.isSymbolicLink()) return { path: rel, kind: 'reparse', bytes: 0, mtime_utc: null, sha256: null };
386
386
  const childPath = path.join(found.resolved, child.name);
387
- const childInfo = await lstat(childPath);
387
+ let childInfo;
388
+ try { childInfo = await listEntryLstat(childPath); }
389
+ catch (error) {
390
+ const code = typeof error?.code === 'string' && /^[A-Z0-9_]+$/.test(error.code) ? error.code : 'UNKNOWN';
391
+ return { path: rel, kind: 'unreadable', bytes: 0, mtime_utc: null, sha256: null, error: { code, operation: 'lstat' } };
392
+ }
388
393
  const kind = kindOf(childInfo);
389
- const digest = hashFiles && kind === 'file' ? await sha256File(childPath) : null;
394
+ let digest = null;
395
+ if (hashFiles && kind === 'file') {
396
+ try { digest = await listEntrySha256File(childPath); }
397
+ catch (error) {
398
+ const code = typeof error?.code === 'string' && /^[A-Z0-9_]+$/.test(error.code) ? error.code : 'UNKNOWN';
399
+ return { path: rel, kind, bytes: childInfo.size, mtime_utc: childInfo.mtime.toISOString(), sha256: null,
400
+ error: { code, operation: 'sha256' } };
401
+ }
402
+ }
390
403
  return { path: rel, kind, bytes: kind === 'file' ? childInfo.size : 0, mtime_utc: childInfo.mtime.toISOString(), sha256: digest };
391
404
  }));
392
405
  const nextCursor = filtered.length > selected.length && selected.length
@@ -1,7 +1,7 @@
1
1
  import * as z from 'zod/v4';
2
2
  import {
3
3
  observationHardwareQueryOutputSchema,observationProcessListOutputSchema,observationSearchCancelOutputSchema,observationSearchPageOutputSchema,
4
- observationSearchStartOutputSchema,observationSearchStatusOutputSchema,observationStructuredResult,observationSystemStatusOutputSchema
4
+ observationSearchStartOutputSchema,observationSearchStatusOutputSchema,observationStructuredResult,observationSystemStatusOutputSchema,observationWindowsQueryOutputSchema
5
5
  } from './observation-output-contracts.mjs';
6
6
 
7
7
  const hex64=z.string().regex(/^[0-9a-f]{64}$/);
@@ -45,4 +45,16 @@ export function registerObservationTools(server,{runtime}){
45
45
  description:'Read one typed native hardware snapshot (CPU, GPU, memory, storage, network, or PCI) with signed observation evidence. No shell or Host authority is used.',
46
46
  inputSchema:z.strictObject({query:z.enum(['cpu','gpu','memory','storage','network','pci'])}),outputSchema:observationHardwareQueryOutputSchema
47
47
  },async args=>{try{return ok(observationHardwareQueryOutputSchema,await runtime.hardwareQuery({query:args.query}));}catch(error){return fail(observationHardwareQueryOutputSchema,error);}});
48
+ server.registerTool('machine_windows_query',{
49
+ description:'Read one typed native Windows snapshot for Registry, services, drivers, sockets, volumes, USB, or WSL. No shell parsing is used.',
50
+ inputSchema:z.discriminatedUnion('query',[
51
+ z.strictObject({query:z.literal('registry'),hive:z.enum(['HKLM','HKCU']),key:z.string().min(1).max(1024),value:z.string().max(260).nullable().default(null)}),
52
+ z.strictObject({query:z.literal('services')}),
53
+ z.strictObject({query:z.literal('drivers')}),
54
+ z.strictObject({query:z.literal('sockets')}),
55
+ z.strictObject({query:z.literal('volumes')}),
56
+ z.strictObject({query:z.literal('usb')}),
57
+ z.strictObject({query:z.literal('wsl')})
58
+ ]),outputSchema:observationWindowsQueryOutputSchema
59
+ },async args=>{try{return ok(observationWindowsQueryOutputSchema,await runtime.windowsQuery(args));}catch(error){return fail(observationWindowsQueryOutputSchema,error);}});
48
60
  }
@@ -55,6 +55,14 @@ export const observationHardwareQueryOutputSchema=statusContract({
55
55
  ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
56
56
  },['observed_at_utc','query','data','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
57
57
 
58
+ export const observationWindowsQueryOutputSchema=statusContract({
59
+ observed_at_utc:z.string().min(1).optional(),
60
+ query:z.enum(['registry','services','drivers','sockets','volumes','usb','wsl']).optional(),
61
+ data:z.record(z.string(),z.json()).optional(),
62
+ observer_sha256:hex64.optional(),
63
+ ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
64
+ },['observed_at_utc','query','data','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
65
+
58
66
  export function observationStructuredResult(schema,value,isError=false){
59
67
  return structuredToolResult(schema,value,isError);
60
68
  }
@@ -15,6 +15,8 @@ const ZERO='0'.repeat(64);
15
15
  const HEX64=/^[0-9a-f]{64}$/;
16
16
  const ID=/^[A-Za-z0-9._-]{1,64}$/;
17
17
  const HARDWARE_QUERIES=new Set(['cpu','gpu','memory','storage','network','pci']);
18
+ const WINDOWS_QUERIES=new Set(['registry','services','drivers','sockets','volumes','usb','wsl']);
19
+ const REGISTRY_HIVES=new Set(['HKLM','HKCU']);
18
20
  const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
19
21
  const sha256Object=value=>sha256Bytes(Buffer.from(canonicalJson(value),'utf8'));
20
22
  const nowUtc=()=>new Date().toISOString();
@@ -131,12 +133,49 @@ async function defaultHardwareSnapshotProvider(query){
131
133
  }
132
134
  return {...parsed,observer_sha256:executableSha256};
133
135
  }
136
+ function normalizeWindowsQueryArgs(input={}){
137
+ const query=String(input.query??'');
138
+ if(!WINDOWS_QUERIES.has(query)) throw new Error('windows query must be one of registry,services,drivers,sockets,volumes,usb,wsl');
139
+ if(query==='registry'){
140
+ const hive=String(input.hive??'');
141
+ const key=String(input.key??'');
142
+ const value=input.value===null||input.value===undefined?null:String(input.value);
143
+ if(!REGISTRY_HIVES.has(hive)) throw new Error('registry hive must be HKLM or HKCU');
144
+ if(key.length<1||key.length>1024||key.includes('\0')) throw new Error('registry key must contain 1-1024 non-NUL characters');
145
+ if(value!==null&&(value.length>260||value.includes('\0'))) throw new Error('registry value must be null or at most 260 non-NUL characters');
146
+ return {query,hive,key,value};
147
+ }
148
+ const extra=Object.keys(input).filter(k=>k!=='query'&&input[k]!==undefined&&input[k]!==null);
149
+ if(extra.length) throw new Error(`windows query '${query}' does not accept additional selectors`);
150
+ return {query};
151
+ }
152
+ async function defaultWindowsSnapshotProvider(input){
153
+ if(process.platform!=='win32') throw new Error('native Windows observation is supported on win32 only');
154
+ const args=normalizeWindowsQueryArgs(input);
155
+ const executable=fileURLToPath(new URL('../bin/deadbyte-hardware-observer-r45.exe',import.meta.url));
156
+ const executableSha256=sha256Bytes(await readFile(executable));
157
+ const argv=[args.query];
158
+ if(args.query==='registry'){
159
+ argv.push(args.hive,args.key);
160
+ if(args.value!==null) argv.push(args.value);
161
+ }
162
+ const result=spawnSync(executable,argv,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:16*1024*1024});
163
+ if(result.error) throw result.error;
164
+ if(result.status!==0) throw new Error(`Windows observation failed query=${args.query}: ${String(result.stderr??result.stdout??'').trim()}`);
165
+ let parsed;
166
+ try{parsed=JSON.parse(String(result.stdout??'').trim());}
167
+ catch{throw new Error(`Windows observation returned invalid JSON query=${args.query}`);}
168
+ if(parsed?.schema!=='deadbyte.windows-query.v1'||parsed?.query!==args.query||!parsed.data||typeof parsed.data!=='object'||Array.isArray(parsed.data)){
169
+ throw new Error(`Windows observation contract mismatch query=${args.query}`);
170
+ }
171
+ return {...parsed,observer_sha256:executableSha256};
172
+ }
134
173
  function requestSha(policy,operation,args){
135
174
  return sha256Object({schema:'deadbyte.observation-request.v1',policy_sha256:policy.policy_sha256,operation,args});
136
175
  }
137
176
  export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,processRuntime=null,stateRoot=path.join(os.homedir(),'.deadbyte-mcp'),
138
177
  processSnapshotProvider=defaultProcessSnapshotProvider,systemSnapshotProvider=defaultSystemSnapshotProvider,
139
- ownedRuntimePidProvider=defaultOwnedRuntimePidProvider,hardwareSnapshotProvider=defaultHardwareSnapshotProvider,
178
+ ownedRuntimePidProvider=defaultOwnedRuntimePidProvider,hardwareSnapshotProvider=defaultHardwareSnapshotProvider,windowsSnapshotProvider=defaultWindowsSnapshotProvider,
140
179
  searchSessionTtlMs=5*60*1000,maxSearchSessions=128,clockMs=()=>Date.now()}={}){
141
180
  if(!policy||!signingKeyPath||!verifyKeyPath) throw new Error('observation runtime requires policy and trust keys');
142
181
  if(!Number.isInteger(searchSessionTtlMs)||searchSessionTtlMs<10||searchSessionTtlMs>24*60*60*1000) throw new Error('searchSessionTtlMs out of range');
@@ -337,5 +376,18 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
337
376
  const receipt=await writeReceipt(runtime,'machine_hardware_query',req,head,{observed_at_utc:observedAt,query,observer_sha256:result.observer_sha256??null});
338
377
  return {...result,request_sha256:req,result_head_sha256:head,...receipt};
339
378
  }
340
- return Object.freeze({policy,searchStart,searchPage,searchStatus,searchCancel,processList,systemStatus,hardwareQuery});
379
+ async function windowsQuery(input={}){
380
+ const args=normalizeWindowsQueryArgs(input);
381
+ const observedAt=nowUtc();
382
+ const snapshot=await windowsSnapshotProvider(args);
383
+ if(!snapshot||snapshot.schema!=='deadbyte.windows-query.v1'||snapshot.query!==args.query||!snapshot.data||typeof snapshot.data!=='object'||Array.isArray(snapshot.data)){
384
+ throw new Error('windows query provider contract mismatch');
385
+ }
386
+ const result={status:'ok',observed_at_utc:observedAt,query:args.query,data:snapshot.data};
387
+ if(typeof snapshot.observer_sha256==='string'&&HEX64.test(snapshot.observer_sha256)) result.observer_sha256=snapshot.observer_sha256;
388
+ const req=requestSha(policy,'machine_windows_query',args); const head=sha256Object(result);
389
+ const receipt=await writeReceipt(runtime,'machine_windows_query',req,head,{observed_at_utc:observedAt,query:args.query,observer_sha256:result.observer_sha256??null});
390
+ return {...result,request_sha256:req,result_head_sha256:head,...receipt};
391
+ }
392
+ return Object.freeze({policy,searchStart,searchPage,searchStatus,searchCancel,processList,systemStatus,hardwareQuery,windowsQuery});
341
393
  }
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.21';
1
+ export const DEADBYTE_VERSION = '0.15.22';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -15,7 +15,7 @@ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy
15
15
  const catalogPath=path.join(repoRoot,'docs','R47-FULL-TOOL-CATALOG.json');
16
16
  const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
17
 
18
- test('R47 runtime full stdio catalog exactly matches canonical registration surface',async()=>{
18
+ test('R47 runtime full stdio catalog exactly matches canonical registration surface',{skip:'historical R47 catalog; R48 supersedes it'},async()=>{
19
19
  const canonical=JSON.parse(await readFile(catalogPath,'utf8'));
20
20
  assert.equal(canonical.schema,'deadbyte.r47-full-catalog.v1');
21
21
  assert.equal(canonical.train,'R47');
@@ -0,0 +1,78 @@
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 catalogPath=path.join(repoRoot,'docs','R48-FULL-TOOL-CATALOG.json');
16
+ const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
+
18
+ test('R48 runtime full stdio catalog exactly matches canonical registration surface',async()=>{
19
+ const canonical=JSON.parse(await readFile(catalogPath,'utf8'));
20
+ assert.equal(canonical.schema,'deadbyte.r48-full-catalog.v1');
21
+ assert.equal(canonical.train,'R48');
22
+ assert.ok(Array.isArray(canonical.tools));
23
+ assert.equal(canonical.tool_count,canonical.tools.length);
24
+ assert.equal(new Set(canonical.tools).size,canonical.tools.length);
25
+ assert.deepEqual([...canonical.tools].sort(),canonical.tools);
26
+
27
+ const f=await createR34Fixture({authority:'disarmed'});
28
+ const processPolicyPath=path.join(f.root,'process-policy.json');
29
+ const processGrant=path.join(f.root,'process-grant.json');
30
+ const processState=path.join(f.root,'process-state');
31
+ const hostPolicyPath=path.join(f.root,'host-policy.json');
32
+ const hostArm=path.join(f.root,'host-arm.json');
33
+ const desktopRaw=JSON.parse(await readFile(desktopPolicyPath,'utf8'));
34
+ const desktopGrant=desktopRaw.grant_file;
35
+ await mkdir(processState,{recursive:true});
36
+ const nodeSha=createHash('sha256').update(await readFile(process.execPath)).digest('hex');
37
+ await writeFile(processPolicyPath,JSON.stringify({
38
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
39
+ native_host:{path:process.execPath,sha256:nodeSha},
40
+ roots:{core:{path:f.project,deny_paths:[]}},
41
+ profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,max_input_bytes:65536,max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],input_actions:{ping:'PING\\r\\n'}}}
42
+ },null,2));
43
+ await writeFile(hostPolicyPath,JSON.stringify({
44
+ schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
45
+ roots:{core:{path:f.project,read:true,write:true,exec:true}},
46
+ commands:{node_script:{kind:'script',executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],extensions:['.mjs'],timeout_ms:5000,max_output_bytes:65536}}
47
+ },null,2));
48
+ const codingLease=f.codingPolicy.lease_file;
49
+ const autonomousLease=f.policy.lease_file;
50
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority unexpectedly armed before start: '+q);
51
+
52
+ const client=new Client({name:'deadbyte-r48-full-catalog',version:'0.15.22'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
53
+ const env={
54
+ ...getDefaultEnvironment(),DEADBYTE_CAPABILITY_PROFILE:'full',DEADBYTE_AUDIT_RUNTIME:'0',
55
+ DEADBYTE_STATE_ROOT:path.join(f.root,'state'),DEADBYTE_WORKSPACE_ROOT:f.root,
56
+ DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
57
+ DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'1',
58
+ DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:processPolicyPath,DEADBYTE_CODING_RUNTIME:'1',
59
+ DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),DEADBYTE_MACHINE_FS_RUNTIME:'1',
60
+ DEADBYTE_OBSERVATION_RUNTIME:'1',DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
61
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
62
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'
63
+ };
64
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
65
+ try{
66
+ await client.connect(transport);
67
+ const listed=await client.listTools();
68
+ const names=listed.tools.map(t=>t.name).sort();
69
+ assert.deepEqual(names,canonical.tools);
70
+ assert.equal(new Set(names).size,names.length,'runtime duplicate tool names');
71
+ for(const tool of listed.tools) if(tool.outputSchema!==undefined) assert.equal(tool.outputSchema?.additionalProperties,false,'non-strict output '+tool.name);
72
+ for(const name of ['agent_task_submit','autonomous_goal_submit','coding_task_submit','process_session_start','machine_fs_write','machine_search_start','desktop_action','host_exec','native_diff_apply','skill_catalog']) assert.ok(names.includes(name),'missing R48 surface '+name);
73
+ } finally {
74
+ await client.close().catch(()=>{});
75
+ for(const q of [processGrant,hostArm,desktopGrant,codingLease,autonomousLease]) assert.equal(await absent(q),true,'authority file appeared during catalog probe: '+q);
76
+ await f.cleanup();
77
+ }
78
+ });
@@ -1,7 +1,7 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { createHash } from 'node:crypto';
4
- import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { generateTrustKeypair } from '../src/trust.mjs';
@@ -10,7 +10,7 @@ import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machi
10
10
 
11
11
  const sha = bytes => createHash('sha256').update(bytes).digest('hex');
12
12
 
13
- async function fixture({ readWorkerPath = undefined } = {}) {
13
+ async function fixture({ readWorkerPath = undefined, listEntryLstat = undefined } = {}) {
14
14
  const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-'));
15
15
  const project = path.join(root, 'project');
16
16
  await mkdir(project);
@@ -30,7 +30,7 @@ async function fixture({ readWorkerPath = undefined } = {}) {
30
30
  privateKeyPath: path.join(root, 'private.pem'), publicKeyPath: path.join(root, 'public.pem')
31
31
  });
32
32
  const runtime = createMachineFsRuntime({
33
- policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath, readWorkerPath
33
+ policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath, readWorkerPath, listEntryLstat
34
34
  });
35
35
  return { root, project, policy, keys, runtime };
36
36
  }
@@ -87,6 +87,31 @@ test('R31 machine fs supports exact mkdir/write/stat/list/range-read/edit/move/d
87
87
  assert.equal(second.next_cursor, null);
88
88
  });
89
89
 
90
+ test('R48 machine fs listing isolates one unreadable child instead of failing the page', async () => {
91
+ let projectPath = null;
92
+ const injectedLstat = async target => {
93
+ if (projectPath && path.basename(target) === 'bad.txt') {
94
+ const error = new Error('simulated Windows lstat EINVAL');
95
+ error.code = 'EINVAL';
96
+ error.syscall = 'lstat';
97
+ throw error;
98
+ }
99
+ return lstat(target);
100
+ };
101
+ const f = await fixture({ listEntryLstat: injectedLstat });
102
+ projectPath = f.project;
103
+ await writeFile(path.join(f.project, 'bad.txt'), 'bad');
104
+ await writeFile(path.join(f.project, 'good.txt'), 'good');
105
+ const listed = await f.runtime.list({ rootId:'project', relativePath:'.', limit:10, hashFiles:true });
106
+ assert.deepEqual(listed.entries.map(item => item.path), ['bad.txt','good.txt']);
107
+ assert.deepEqual(listed.entries[0], {
108
+ path:'bad.txt', kind:'unreadable', bytes:0, mtime_utc:null, sha256:null,
109
+ error:{ code:'EINVAL', operation:'lstat' }
110
+ });
111
+ assert.equal(listed.entries[1].kind, 'file');
112
+ assert.equal(listed.entries[1].sha256, sha(Buffer.from('good')));
113
+ });
114
+
90
115
  test('R31 machine fs fails closed on traversal, denied paths and stale source identity', async () => {
91
116
  const f = await fixture();
92
117
  await mkdir(path.join(f.project, 'blocked'));
@@ -16,14 +16,15 @@ function runtime(){
16
16
  searchCancel:async()=>({status:'ok',search_id:hex('a'),state:'cancelled',cancel_requested:true}),
17
17
  processList:async()=>({status:'ok',snapshot_id:hex('e'),observed_at_utc:new Date(0).toISOString(),processes:[],next_cursor:null,request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
18
18
  systemStatus:async()=>({status:'ok',observed_at_utc:new Date(0).toISOString(),hostname:'x',platform:'win32',architecture:'x64',uptime_seconds:1,total_memory_bytes:2,free_memory_bytes:1,cpu_count:1,load_average:[0,0,0],request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
19
- hardwareQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{vendor:'x'},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')})
19
+ hardwareQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{vendor:'x'},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')}),
20
+ windowsQuery:async({query})=>({status:'ok',query,observed_at_utc:new Date(0).toISOString(),observer_sha256:hex('e'),data:{query},request_sha256:hex('b'),result_head_sha256:hex('c'),receipt_id:receipt,receipt_sha256:hex('d')})
20
21
  };
21
22
  }
22
23
 
23
- test('R45 registers exactly seven bounded observation tools including typed native hardware',()=>{
24
+ test('R48 registers exactly eight bounded observation tools including typed native Windows observation',()=>{
24
25
  const server=new FakeServer(); registerObservationTools(server,{runtime:runtime()});
25
26
  assert.deepEqual([...server.tools.keys()].sort(),[
26
- 'machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'
27
+ 'machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status','machine_windows_query'
27
28
  ]);
28
29
  for(const [name,tool] of server.tools){ assert.ok(tool.spec.inputSchema,`${name} input`); assert.ok(tool.spec.outputSchema,`${name} output`); }
29
30
  });