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.
- package/MANIFEST.SHA256 +31 -22
- package/PROVENANCE.json +10 -10
- package/README.txt +7 -7
- package/RELEASE.json +45 -16
- package/SBOM.cdx.json +33 -7
- package/TEST-MATRIX.json +85 -18
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/docs/R48-FULL-TOOL-CATALOG.json +124 -0
- package/native/deadbyte-hardware-observer.cpp +535 -12
- package/package.json +94 -93
- package/scripts/build-hardware-r45.ps1 +19 -0
- package/scripts/full-regression-r48-batch.mjs +27 -0
- package/scripts/promote-local-c-generation.ps1 +217 -0
- package/scripts/promote-local-d-generation.ps1 +217 -0
- package/scripts/test-r48-post-bump.ps1 +8 -0
- package/scripts/test-r48-windows-observation.ps1 +8 -0
- package/src/capability-ledger.mjs +7 -0
- package/src/capability-resolver.mjs +7 -0
- package/src/compact-gateway-mcp-tools.mjs +16 -2
- package/src/machine-fs.mjs +16 -3
- package/src/observation-mcp-tools.mjs +13 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +54 -2
- package/src/version.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +1 -1
- package/test/full-max-catalog-r48.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +28 -3
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/r48-release-contract.test.mjs +18 -14
- package/test/windows-observation-r48.test.mjs +63 -0
- package/test/windows-observation-surface-r48.test.mjs +70 -0
|
@@ -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
|
}
|
package/src/machine-fs.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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('
|
|
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
|
});
|