deadbyte-mcp 0.15.1 → 0.15.2

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 (55) hide show
  1. package/MANIFEST.SHA256 +54 -44
  2. package/PROVENANCE.json +6 -5
  3. package/README.txt +36 -20
  4. package/RELEASE.json +24 -8
  5. package/SBOM.cdx.json +3 -3
  6. package/controller/README.TXT +2 -2
  7. package/controller/deadbyte-controller.ps1 +50 -2
  8. package/controller/deadbyte-process-policy.json +63 -1
  9. package/docs/ARCHITECTURE.md +3 -4
  10. package/package.json +9 -7
  11. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  12. package/scripts/final-closure-r46.mjs +98 -0
  13. package/scripts/final-closure-verify-r46.mjs +119 -0
  14. package/scripts/full-regression-r46.mjs +20 -0
  15. package/scripts/gate-windows-r46.ps1 +56 -0
  16. package/scripts/refresh-process-policy.mjs +64 -7
  17. package/scripts/release-parity-r46.mjs +187 -0
  18. package/scripts/windows-gate-evidence-r46.mjs +110 -0
  19. package/src/capability-ledger.mjs +1 -0
  20. package/src/compact-gateway-mcp-tools.mjs +7 -5
  21. package/src/deadbyte-cli.mjs +7 -3
  22. package/src/machine-fs-mcp-tools.mjs +3 -3
  23. package/src/machine-fs-output-contracts.mjs +1 -1
  24. package/src/machine-fs-read-worker.mjs +46 -0
  25. package/src/machine-fs.mjs +63 -10
  26. package/src/mcp-server.mjs +4 -0
  27. package/src/mcp-smoke-client.mjs +4 -1
  28. package/src/observation-runtime.mjs +25 -4
  29. package/src/process-mcp-tools.mjs +18 -5
  30. package/src/process-output-contracts.mjs +20 -3
  31. package/src/process-policy.mjs +3 -1
  32. package/src/process-runtime.mjs +99 -19
  33. package/src/process-smoke-client.mjs +67 -22
  34. package/src/release-generation.mjs +22 -3
  35. package/src/remote-device-r44.mjs +53 -15
  36. package/src/remote-mcp-smoke-client.mjs +1 -1
  37. package/src/stdio-console-guard.mjs +24 -0
  38. package/src/version.mjs +1 -1
  39. package/test/cli-entrypoint.test.mjs +3 -3
  40. package/test/compact-gateway-r36.test.mjs +28 -0
  41. package/test/core.test.mjs +1 -1
  42. package/test/full-max-catalog-r46.test.mjs +92 -0
  43. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  44. package/test/machine-fs-runtime.test.mjs +18 -2
  45. package/test/observation-runtime.test.mjs +29 -2
  46. package/test/process-mcp-tools.test.mjs +23 -5
  47. package/test/process-policy-refresh.test.mjs +87 -10
  48. package/test/process-runtime.test.mjs +111 -1
  49. package/test/process-supervision.test.mjs +4 -2
  50. package/test/production-docs.test.mjs +3 -3
  51. package/test/r33-finalization.test.mjs +3 -3
  52. package/test/release-generation.test.mjs +25 -11
  53. package/test/release-version.test.mjs +99 -44
  54. package/test/remote-device-r44.test.mjs +75 -0
  55. package/test/remote-r24-catalog.test.mjs +2 -2
@@ -0,0 +1,56 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ Set-StrictMode -Version 2.0
3
+
4
+ function Invoke-GateStep {
5
+ param(
6
+ [Parameter(Mandatory = $true)][string]$Name,
7
+ [Parameter(Mandatory = $true)][string[]]$NpmArgs
8
+ )
9
+ Write-Host "==== $Name ===="
10
+ & npm.cmd @NpmArgs
11
+ if ($LASTEXITCODE -ne 0) {
12
+ throw "DEADBYTE V0.15.2 / R46 gate failed at $Name with exit code $LASTEXITCODE"
13
+ }
14
+ }
15
+
16
+ Invoke-GateStep -Name 'containment-build' -NpmArgs @('run','containment:build')
17
+ Invoke-GateStep -Name 'process-policy-refresh' -NpmArgs @('run','process:policy:refresh')
18
+ Invoke-GateStep -Name 'desktop-policy-refresh' -NpmArgs @('run','desktop:policy:refresh')
19
+ Invoke-GateStep -Name 'hardware-build' -NpmArgs @('run','hardware:build')
20
+
21
+ Write-Host '==== host-policy-hermetic-clean ===='
22
+ $HostPolicyPath = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\controller\deadbyte-host-policy.json'))
23
+ Remove-Item -LiteralPath $HostPolicyPath -Force -ErrorAction SilentlyContinue
24
+ if (Test-Path -LiteralPath $HostPolicyPath) {
25
+ throw "DEADBYTE V0.15.2 / R46 gate could not remove machine-local Host policy before hermetic regression"
26
+ }
27
+ Write-Host 'HOST_POLICY_HERMETIC_ABSENT'
28
+
29
+ Invoke-GateStep -Name 'r46-fileless-focused' -NpmArgs @('run','r46:fileless:test')
30
+ Invoke-GateStep -Name 'full-regression' -NpmArgs @('run','r46:full:test')
31
+ Invoke-GateStep -Name 'mcp-smoke' -NpmArgs @('run','mcp:smoke')
32
+ Invoke-GateStep -Name 'mcp-agent-smoke' -NpmArgs @('run','mcp:agent:smoke')
33
+ Invoke-GateStep -Name 'mcp-coding-smoke' -NpmArgs @('run','mcp:coding:smoke')
34
+ Invoke-GateStep -Name 'mcp-machine-fs-smoke' -NpmArgs @('run','mcp:machine-fs:smoke')
35
+ Invoke-GateStep -Name 'mcp-observation-smoke' -NpmArgs @('run','mcp:observation:smoke')
36
+ Invoke-GateStep -Name 'mcp-process-smoke' -NpmArgs @('run','mcp:process:smoke')
37
+ Invoke-GateStep -Name 'mcp-autonomous-smoke' -NpmArgs @('run','mcp:autonomous:smoke')
38
+ Invoke-GateStep -Name 'tunnel-stdio-compat-smoke' -NpmArgs @('run','mcp:tunnel-compat:smoke')
39
+ Invoke-GateStep -Name 'containment-smoke' -NpmArgs @('run','containment:smoke')
40
+ Invoke-GateStep -Name 'contained-smoke' -NpmArgs @('run','contained:smoke')
41
+ Invoke-GateStep -Name 'mcp-contained-smoke' -NpmArgs @('run','mcp:contained:smoke')
42
+
43
+ Invoke-GateStep -Name 'host-policy-refresh' -NpmArgs @('run','host:policy:init')
44
+ Invoke-GateStep -Name 'release-manifest-write' -NpmArgs @('run','manifest:write')
45
+ Invoke-GateStep -Name 'release-manifest' -NpmArgs @('run','manifest:verify')
46
+ Invoke-GateStep -Name 'doctor' -NpmArgs @('run','doctor')
47
+ Invoke-GateStep -Name 'mcp-host-smoke' -NpmArgs @('run','mcp:host:smoke')
48
+ Invoke-GateStep -Name 'authority-smoke' -NpmArgs @('run','authority:smoke')
49
+ Invoke-GateStep -Name 'release-manifest-final' -NpmArgs @('run','manifest:verify')
50
+
51
+ Write-Host '==== windows-gate-evidence ===='
52
+ & node.exe (Join-Path $PSScriptRoot 'windows-gate-evidence-r46.mjs')
53
+ if ($LASTEXITCODE -ne 0) {
54
+ throw "DEADBYTE V0.15.2 / R46 gate failed at windows-gate-evidence with exit code $LASTEXITCODE"
55
+ }
56
+ Write-Host '==== DEADBYTE V0.15.2 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS ===='
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
- import { open, readFile, rename, rm } from 'node:fs/promises';
2
+ import { lstat, open, readFile, rename, rm } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
 
@@ -14,7 +14,35 @@ function samePath(a, b) {
14
14
  : left === right;
15
15
  }
16
16
 
17
- export async function refreshProcessPolicy({ repoRoot, policyPath }) {
17
+ async function regularPinnedExecutable(file, label) {
18
+ const info = await lstat(file);
19
+ if (!info.isFile() || info.isSymbolicLink()) throw new Error(`${label} must be a regular non-symlink file`);
20
+ return sha256(await readFile(file));
21
+ }
22
+
23
+ function resolveSystemRoot(explicit) {
24
+ const value = explicit ?? process.env.SystemRoot ?? process.env.WINDIR ?? null;
25
+ if (typeof value !== 'string' || value.length < 1) {
26
+ throw new Error('process policy Windows system profiles require SystemRoot/WINDIR');
27
+ }
28
+ return path.resolve(value);
29
+ }
30
+
31
+ async function refreshSystemProfile(policy, id, expectedExecutable) {
32
+ const profile = policy?.profiles?.[id];
33
+ if (!profile || typeof profile !== 'object' || Array.isArray(profile)) {
34
+ throw new Error(`process policy profile '${id}' missing`);
35
+ }
36
+ if (typeof profile.executable !== 'string' || !samePath(profile.executable, expectedExecutable)) {
37
+ throw new Error(`process policy profile '${id}' executable path mismatch`);
38
+ }
39
+ const digest = await regularPinnedExecutable(expectedExecutable, `process profile '${id}'`);
40
+ profile.executable = expectedExecutable;
41
+ profile.executable_sha256 = digest;
42
+ return digest;
43
+ }
44
+
45
+ export async function refreshProcessPolicy({ repoRoot, policyPath, systemRoot = null }) {
18
46
  const root = path.resolve(repoRoot);
19
47
  const file = path.resolve(policyPath);
20
48
  const rawBytes = await readFile(file);
@@ -29,9 +57,16 @@ export async function refreshProcessPolicy({ repoRoot, policyPath }) {
29
57
  if (!samePath(policy.native_host.path, expectedHost)) {
30
58
  throw new Error('process policy native_host path must equal candidate bin/deadbyte-process-host.exe');
31
59
  }
32
- const hostBytes = await readFile(expectedHost);
33
- const hostSha = sha256(hostBytes);
60
+ const hostSha = await regularPinnedExecutable(expectedHost, 'process native host');
34
61
  policy.native_host.sha256 = hostSha;
62
+
63
+ const windowsRoot = resolveSystemRoot(systemRoot);
64
+ const cmdPath = path.join(windowsRoot, 'System32', 'cmd.exe');
65
+ const powershellPath = path.join(windowsRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
66
+ const cmdSha = await refreshSystemProfile(policy, 'ping_hold', cmdPath);
67
+ const powershellSha = await refreshSystemProfile(policy, 'powershell', powershellPath);
68
+ const powershellReplSha = await refreshSystemProfile(policy, 'powershell_repl', powershellPath);
69
+
35
70
  const output = Buffer.from(`${JSON.stringify(policy, null, 2)}\n`, 'utf8');
36
71
  const temp = `${file}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
37
72
  const handle = await open(temp, 'wx');
@@ -47,8 +82,30 @@ export async function refreshProcessPolicy({ repoRoot, policyPath }) {
47
82
  await rm(temp, { force: true }).catch(() => {});
48
83
  throw error;
49
84
  }
50
- if (sha256(await readFile(expectedHost)) !== hostSha) throw new Error('process native host changed during policy refresh');
51
- return { policy_path: file, native_host_path: expectedHost, native_host_sha256: hostSha };
85
+
86
+ if (await regularPinnedExecutable(expectedHost, 'process native host') !== hostSha) {
87
+ throw new Error('process native host changed during policy refresh');
88
+ }
89
+ if (await regularPinnedExecutable(cmdPath, "process profile 'ping_hold'") !== cmdSha) {
90
+ throw new Error("process profile 'ping_hold' changed during policy refresh");
91
+ }
92
+ if (await regularPinnedExecutable(powershellPath, "process profile 'powershell'") !== powershellSha) {
93
+ throw new Error("process profile 'powershell' changed during policy refresh");
94
+ }
95
+ if (await regularPinnedExecutable(powershellPath, "process profile 'powershell_repl'") !== powershellReplSha) {
96
+ throw new Error("process profile 'powershell_repl' changed during policy refresh");
97
+ }
98
+
99
+ return {
100
+ policy_path: file,
101
+ native_host_path: expectedHost,
102
+ native_host_sha256: hostSha,
103
+ cmd_executable: cmdPath,
104
+ cmd_executable_sha256: cmdSha,
105
+ powershell_executable: powershellPath,
106
+ powershell_executable_sha256: powershellSha,
107
+ powershell_repl_executable_sha256: powershellReplSha
108
+ };
52
109
  }
53
110
 
54
111
  const invoked = process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url;
@@ -60,7 +117,7 @@ if (invoked) {
60
117
  : path.join(root, 'controller', 'deadbyte-process-policy.json');
61
118
  try {
62
119
  const result = await refreshProcessPolicy({ repoRoot: root, policyPath });
63
- console.log(`PROCESS_POLICY_REFRESH_OK sha256=${result.native_host_sha256}`);
120
+ console.log(`PROCESS_POLICY_REFRESH_OK native=${result.native_host_sha256} cmd=${result.cmd_executable_sha256} powershell=${result.powershell_executable_sha256} powershell_repl=${result.powershell_repl_executable_sha256}`);
64
121
  } catch (error) {
65
122
  console.error(`PROCESS_POLICY_REFRESH_FAIL ${error?.message ?? error}`);
66
123
  process.exitCode = 1;
@@ -0,0 +1,187 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { verifyGenerationBundle } from '../src/release-generation.mjs';
7
+ import { loadProcessPolicy, processSessionStatus } from '../src/process-policy.mjs';
8
+ import { createProcessRuntime } from '../src/process-runtime.mjs';
9
+
10
+ const here=path.dirname(fileURLToPath(import.meta.url));
11
+ const packageRoot=path.resolve(here,'..');
12
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
13
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
14
+ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
15
+ function insist(ok,message){if(!ok)throw new Error(message);}
16
+ function samePath(a,b){return path.resolve(String(a)).toLowerCase()===path.resolve(String(b)).toLowerCase();}
17
+
18
+ async function waitForExit(runtime,sessionId,timeoutMs=10000){
19
+ const deadline=Date.now()+timeoutMs;
20
+ let page=null,text='';
21
+ while(Date.now()<deadline){
22
+ page=await runtime.read({sessionId,offset:0,maxBytes:65536});
23
+ text=Buffer.from(page.data_base64,'base64').toString('utf8');
24
+ if(page.state==='exited'||page.state==='terminated') return {page,text};
25
+ await sleep(50);
26
+ }
27
+ throw new Error('R46 PowerShell parity timed out');
28
+ }
29
+
30
+ export async function runReleaseParity({
31
+ root=packageRoot,
32
+ runtimeStateRoot=stateRoot,
33
+ clock=()=>new Date()
34
+ }={}){
35
+ const candidate=path.resolve(root);
36
+ const state=path.resolve(runtimeStateRoot);
37
+ const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
38
+ const manifestSha256=sha256(manifestBytes);
39
+ const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
40
+ insist(pkg.version==='0.15.2','R46 parity requires package version 0.15.2');
41
+
42
+ const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
43
+ insist(pointer.version==='0.15.2'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer candidate mismatch');
44
+ insist(pointer.release_id===`v0.15.2-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
45
+ const releaseManifest=await readFile(path.join(pointer.release_root,'MANIFEST.SHA256'));
46
+ insist(sha256(releaseManifest)===manifestSha256,'R46 immutable release manifest mismatch');
47
+
48
+ const generation=await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
49
+ const processPolicyPath=path.join(pointer.generation_root,'process-policy.json');
50
+ const policy=await loadProcessPolicy(processPolicyPath);
51
+ const status=await processSessionStatus(policy);
52
+ insist(status.armed===true&&status.reason==='standing-grant','R46 parity requires restored Process standing grant');
53
+ insist(status.profile_ids.includes('powershell')&&status.root_ids.includes('core'),'R46 parity Process grant missing powershell/core');
54
+
55
+ const systemRoot=process.env.SystemRoot||process.env.WINDIR;
56
+ insist(typeof systemRoot==='string'&&systemRoot.length>0,'R46 parity requires SystemRoot/WINDIR');
57
+ const expectedPowerShell=path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe');
58
+ const expectedCmd=path.join(systemRoot,'System32','cmd.exe');
59
+ insist(samePath(policy.profiles?.powershell?.executable,expectedPowerShell),'R46 generated PowerShell path is not target-bound');
60
+ insist(samePath(policy.profiles?.ping_hold?.executable,expectedCmd),'R46 generated cmd path is not target-bound');
61
+ insist(policy.profiles.powershell.executable_sha256===sha256(await readFile(expectedPowerShell)),'R46 generated PowerShell digest mismatch');
62
+ insist(policy.profiles.ping_hold.executable_sha256===sha256(await readFile(expectedCmd)),'R46 generated cmd digest mismatch');
63
+ insist(JSON.stringify(policy.profiles.powershell.prefix_args)===JSON.stringify(['-NoLogo','-NoProfile','-NonInteractive','-Command']),'R46 PowerShell prefix contract mismatch');
64
+ insist(policy.profiles.powershell.max_args===1&&policy.profiles.powershell.max_arg_bytes===8192,'R46 PowerShell argument bounds mismatch');
65
+
66
+ const runtime=createProcessRuntime({
67
+ policy,
68
+ signingKeyPath:path.join(state,'trust','ed25519-private.pem'),
69
+ verifyKeyPath:path.join(state,'trust','ed25519-public.pem')
70
+ });
71
+ const source="$ErrorActionPreference='Stop';$before=@(Get-ChildItem -LiteralPath $env:TEMP -Filter '*.ps1' -File -ErrorAction SilentlyContinue|ForEach-Object{$_.FullName});[Console]::WriteLine('DB_R46_FILELESS_PARITY');[Console]::WriteLine('PSCommandPath=' + [string]$PSCommandPath);[Console]::WriteLine('InvocationPath=' + [string]$MyInvocation.MyCommand.Path);$after=@(Get-ChildItem -LiteralPath $env:TEMP -Filter '*.ps1' -File -ErrorAction SilentlyContinue|ForEach-Object{$_.FullName});$new=@($after|Where-Object{$_ -notin $before});[Console]::WriteLine('NEW_TEMP_PS1_COUNT=' + $new.Count)";
72
+ const started=await runtime.start({profileId:'powershell',rootId:'core',cwd:'.',args:[source]});
73
+ const observed=await waitForExit(runtime,started.session_id);
74
+ insist(observed.page.state==='exited'&&observed.page.exit_code===0,'R46 PowerShell parity process did not exit cleanly');
75
+ insist(/DB_R46_FILELESS_PARITY/.test(observed.text),'R46 PowerShell parity marker missing');
76
+ insist(/PSCommandPath=\r?\n/.test(observed.text),'R46 PowerShell unexpectedly materialized PSCommandPath');
77
+ insist(/InvocationPath=\r?\n/.test(observed.text),'R46 PowerShell invocation path is not fileless');
78
+ insist(/NEW_TEMP_PS1_COUNT=0/.test(observed.text),'R46 PowerShell parity created a temp ps1');
79
+ const journal=await runtime.verifyEventJournal({sessionId:started.session_id});
80
+ insist(journal.ok===true,'R46 PowerShell process journal verification failed');
81
+
82
+
83
+ insist(status.profile_ids.includes('powershell_repl'),'R46 parity Process grant missing powershell_repl');
84
+ insist(samePath(policy.profiles?.powershell_repl?.executable,expectedPowerShell),'R46 generated PowerShell REPL path is not target-bound');
85
+ insist(policy.profiles.powershell_repl.executable_sha256===sha256(await readFile(expectedPowerShell)),'R46 generated PowerShell REPL digest mismatch');
86
+ insist(policy.profiles.powershell_repl.allow_raw_input===true&&policy.profiles.powershell_repl.transport==='pipe','R46 PowerShell REPL raw-input contract mismatch');
87
+
88
+ const repl=await runtime.start({profileId:'powershell_repl',rootId:'core',cwd:'.',args:[]});
89
+ const firstWrite=await runtime.write({sessionId:repl.session_id,input:"$x=41;[Console]::WriteLine('DB_R46_REPL_SET')",appendNewline:true});
90
+ insist(firstWrite.session.state==='running','R46 PowerShell REPL did not remain running after first write');
91
+ const firstRead=await runtime.read({sessionId:repl.session_id,offset:0,maxBytes:65536,waitMs:5000,waitFor:'DB_R46_REPL_SET'});
92
+ insist(firstRead.wait_satisfied===true,'R46 PowerShell REPL first wait was not satisfied');
93
+ const beforeSecond=(await runtime.status({sessionId:repl.session_id})).stream_head;
94
+ await runtime.write({sessionId:repl.session_id,input:"[Console]::WriteLine('DB_R46_REPL_VALUE=' + ($x+1))",appendNewline:true});
95
+ const secondRead=await runtime.read({sessionId:repl.session_id,offset:beforeSecond,maxBytes:65536,waitMs:5000,waitFor:'DB_R46_REPL_VALUE=42'});
96
+ const secondText=Buffer.from(secondRead.data_base64,'base64').toString('utf8');
97
+ insist(secondRead.wait_satisfied===true&&/DB_R46_REPL_VALUE=42/.test(secondText),'R46 PowerShell REPL state did not persist across writes');
98
+ const tailRead=await runtime.read({sessionId:repl.session_id,offset:-4096,maxBytes:4096});
99
+ insist(/DB_R46_REPL_VALUE=42/.test(Buffer.from(tailRead.data_base64,'base64').toString('utf8')),'R46 PowerShell REPL tail read missing state proof');
100
+ await runtime.terminate({sessionId:repl.session_id});
101
+ const replJournal=await runtime.verifyEventJournal({sessionId:repl.session_id});
102
+ insist(replJournal.ok===true,'R46 PowerShell REPL journal verification failed');
103
+ const replJournalText=await readFile(path.join(policy.state_dir,`${repl.session_id}.jsonl`),'utf8');
104
+ insist(!replJournalText.includes('$x=41')&&!replJournalText.includes('DB_R46_REPL_VALUE'),'R46 raw process input leaked into signed journal');
105
+
106
+ const ringSource="[Console]::Write(('A'*2097152));[Console]::WriteLine('DB_R46_RING_TAIL')";
107
+ const ring=await runtime.start({profileId:'powershell',rootId:'core',cwd:'.',args:[ringSource]});
108
+ const ringObserved=await waitForExit(runtime,ring.session_id,15000);
109
+ const ringStatus=await runtime.status({sessionId:ring.session_id});
110
+ insist(ringObserved.page.exit_code===0&&ringStatus.earliest_offset>0,'R46 retained-output ring did not evict under heavy output');
111
+ const ringTail=await runtime.read({sessionId:ring.session_id,offset:-4096,maxBytes:4096});
112
+ insist(/DB_R46_RING_TAIL/.test(Buffer.from(ringTail.data_base64,'base64').toString('utf8')),'R46 retained-output ring lost tail marker');
113
+
114
+ const completed=clock();
115
+ insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'R46 parity clock invalid');
116
+ const evidence={
117
+ schema:'deadbyte.r46-parity.v1',
118
+ status:'passed',
119
+ version:'0.15.2',
120
+ completed_at_utc:completed.toISOString(),
121
+ candidate:{
122
+ release_id:pointer.release_id,
123
+ manifest_sha256:pointer.manifest_sha256,
124
+ generation_manifest_sha256:pointer.generation_manifest_sha256
125
+ },
126
+ generation:{files:generation.files,generation_manifest_sha256:generation.generation_manifest_sha256},
127
+ process_policy:{
128
+ sha256:policy.policy_sha256,
129
+ profile_ids:Object.keys(policy.profiles).sort(),
130
+ powershell_executable:policy.profiles.powershell.executable,
131
+ powershell_executable_sha256:policy.profiles.powershell.executable_sha256,
132
+ cmd_executable:policy.profiles.ping_hold.executable,
133
+ cmd_executable_sha256:policy.profiles.ping_hold.executable_sha256,
134
+ target_repin_verified:true
135
+ },
136
+ fileless_powershell:{
137
+ session_id:started.session_id,
138
+ child_pid:started.child_pid,
139
+ exit_code:observed.page.exit_code,
140
+ stream_sha256:observed.page.stream_sha256,
141
+ pscommandpath_empty:true,
142
+ invocation_path_empty:true,
143
+ new_temp_ps1_count:0,
144
+ journal_head_sha256:journal.head_sha256,
145
+ journal_event_count:journal.event_count
146
+ },
147
+ persistent_powershell_repl:{
148
+ session_id:repl.session_id,
149
+ child_pid:repl.child_pid,
150
+ state_persisted:true,
151
+ observed_value:42,
152
+ wait_for_verified:true,
153
+ tail_read_verified:true,
154
+ raw_input_not_journaled:true,
155
+ journal_head_sha256:replJournal.head_sha256,
156
+ journal_event_count:replJournal.event_count
157
+ },
158
+ retained_output_ring:{
159
+ session_id:ring.session_id,
160
+ stream_head:ringStatus.stream_head,
161
+ earliest_offset:ringStatus.earliest_offset,
162
+ eviction_verified:true,
163
+ tail_marker_verified:true
164
+ },
165
+ execution_provider:'deadbyte_mcp',
166
+ rdc_operation_count:0
167
+ };
168
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
169
+ const dir=path.join(state,'evidence','release');
170
+ await mkdir(dir,{recursive:true});
171
+ const file=path.join(dir,`r46-parity-${manifestSha256.slice(0,16)}.json`);
172
+ try{
173
+ const handle=await open(file,'wx',0o600);
174
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
175
+ return {status:'written',evidence_path:file,evidence_sha256:sha256(bytes),evidence};
176
+ }catch(error){
177
+ if(error?.code!=='EEXIST')throw error;
178
+ const existingBytes=await readFile(file);
179
+ const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
180
+ insist(existing.schema===evidence.schema&&existing.status==='passed','existing R46 parity evidence invalid');
181
+ insist(existing.candidate?.manifest_sha256===manifestSha256,'existing R46 parity candidate mismatch');
182
+ return {status:'existing',evidence_path:file,evidence_sha256:sha256(existingBytes),evidence:existing};
183
+ }
184
+ }
185
+
186
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
187
+ if(invoked)runReleaseParity().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
@@ -0,0 +1,110 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { doctor } from '../src/doctor-r45.mjs';
7
+
8
+ const here=path.dirname(fileURLToPath(import.meta.url));
9
+ const root=path.resolve(here,'..');
10
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const marker='DEADBYTE V0.15.2 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS';
12
+ const PARENT_RELEASE_ID='v0.15.1-d55f00b5144dc31a';
13
+ const PARENT_MANIFEST='d55f00b5144dc31a7fe39e4dc60d640a94780da6aaeca0918e87636b816d7854';
14
+ const PARENT_GENERATION='00cde7bf540cc2804d679a39040d5b07193c5f8a0d49cac3b882402f8f4e6ad8';
15
+ const REOPENED_RUNTIME_RELEASE_ID='v0.15.2-0fb46542f6a4345f';
16
+ const REOPENED_RUNTIME_MANIFEST='0fb46542f6a4345f1c527565d06bf6e9b8e283260e47bf684fb70a60308fcf34';
17
+ const REOPENED_RUNTIME_GENERATION='4717e0eb9c515ce4b1e90b8496c49c68abde765a6de7490176b803912385afe4';
18
+ function insist(ok,message){if(!ok)throw new Error(message);}
19
+
20
+ export async function writeWindowsGateEvidence({
21
+ candidateRoot=root,
22
+ stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
23
+ clock=()=>new Date()
24
+ }={}){
25
+ const candidate=path.resolve(candidateRoot);
26
+ const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
27
+ const manifestSha256=sha256(manifestBytes);
28
+ const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
29
+ insist(pkg.version==='0.15.2',`R46 Windows gate evidence requires V0.15.2, found ${pkg.version}`);
30
+ const release=JSON.parse(await readFile(path.join(candidate,'RELEASE.json'),'utf8'));
31
+ insist(release.version==='0.15.2'&&release.train==='R46','R46 release metadata mismatch');
32
+ const active=JSON.parse(await readFile(path.join(path.resolve(stateRoot),'active-release.json'),'utf8'));
33
+ const originalParent=active.release_id===PARENT_RELEASE_ID&&active.manifest_sha256===PARENT_MANIFEST&&active.generation_manifest_sha256===PARENT_GENERATION;
34
+ const reopenedPredecessor=active.release_id===REOPENED_RUNTIME_RELEASE_ID&&active.manifest_sha256===REOPENED_RUNTIME_MANIFEST&&active.generation_manifest_sha256===REOPENED_RUNTIME_GENERATION;
35
+ insist(originalParent||reopenedPredecessor,'R46 runtime predecessor identity mismatch');
36
+ const parent={release_id:PARENT_RELEASE_ID,version:'0.15.1',manifest_sha256:PARENT_MANIFEST,generation_manifest_sha256:PARENT_GENERATION};
37
+
38
+ const doctorResult=await doctor();
39
+ insist(doctorResult?.status==='ok'&&doctorResult?.package_version==='0.15.2'&&doctorResult?.manifest?.verified===true,'R46 doctor evidence invalid');
40
+ insist(doctorResult?.policy?.host_v2_ready===true,'R46 doctor did not verify Host policy v2');
41
+
42
+ const completed=clock();
43
+ insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'R46 Windows gate evidence clock invalid');
44
+ const evidence={
45
+ schema:'deadbyte.r46-windows-gate.v1',
46
+ status:'passed',
47
+ exit_code:0,
48
+ version:'0.15.2',
49
+ train:'R46',
50
+ marker,
51
+ completed_at_utc:completed.toISOString(),
52
+ manifest_sha256:manifestSha256,
53
+ doctor:{
54
+ status:doctorResult.status,
55
+ manifest_sha256:doctorResult.manifest.sha256,
56
+ verified_entries:doctorResult.manifest.verified_entries,
57
+ host_v2_ready:doctorResult.policy.host_v2_ready
58
+ },
59
+ parent,
60
+ runtime_predecessor:{
61
+ release_id:active.release_id,
62
+ version:active.version,
63
+ manifest_sha256:active.manifest_sha256,
64
+ generation_manifest_sha256:active.generation_manifest_sha256,
65
+ mode:reopenedPredecessor?'reopened-unpublished-0.15.2':'sealed-0.15.1-parent'
66
+ },
67
+ features:{
68
+ fileless_powershell:true,
69
+ powershell_source_via_process_argv:true,
70
+ no_ps1_materialization_regression:true,
71
+ target_machine_process_profile_repin:true,
72
+ target_machine_cmd_repin:true,
73
+ target_machine_powershell_repin:true,
74
+ powershell_persistent_repl:true,
75
+ guarded_process_raw_input:true,
76
+ process_tail_reads:true,
77
+ process_wait_reads:true,
78
+ process_chunked_output_ring:true,
79
+ remote_reachability_ttl:true,
80
+ remote_probe_timeout_retry:true,
81
+ remote_dispatch_timeout_no_retry:true,
82
+ observation_search_terminal_ttl:true,
83
+ observation_search_hard_cap:128,
84
+ full_regression_required:true,
85
+ compact_tool_count:43,
86
+ full_tool_count:83
87
+ }
88
+ };
89
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
90
+ const dir=path.join(path.resolve(stateRoot),'evidence','release');
91
+ await mkdir(dir,{recursive:true});
92
+ const file=path.join(dir,`r46-windows-gate-${manifestSha256.slice(0,16)}.json`);
93
+ try{
94
+ const handle=await open(file,'wx',0o600);
95
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
96
+ return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256,parent:evidence.parent};
97
+ }catch(error){
98
+ if(error?.code!=='EEXIST')throw error;
99
+ const existingBytes=await readFile(file);
100
+ const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
101
+ insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R46 Windows gate evidence invalid');
102
+ insist(existing.version==='0.15.2'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R46 Windows gate identity mismatch');
103
+ insist(existing.parent?.release_id===parent.release_id&&existing.parent?.manifest_sha256===parent.manifest_sha256,'existing R46 parent evidence mismatch');
104
+ insist(existing.runtime_predecessor?.release_id===active.release_id&&existing.runtime_predecessor?.manifest_sha256===active.manifest_sha256,'existing R46 runtime predecessor evidence mismatch');
105
+ return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,parent:existing.parent};
106
+ }
107
+ }
108
+
109
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
110
+ if(invoked)writeWindowsGateEvidence().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
@@ -89,6 +89,7 @@ export const R36_CAPABILITY_ENTRIES=Object.freeze([
89
89
 
90
90
  gateway('process.start','process_session_start','process_session','start','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
91
91
  gateway('process.read','process_session_read','process_session','read','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
92
+ gateway('process.write','process_session_write','process_session','write','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
92
93
  gateway('process.status','process_session_status','process_session','status','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
93
94
  gateway('process.list','process_session_list','process_session','list','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
94
95
  gateway('process.action','process_session_action','process_session','action','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
@@ -26,7 +26,7 @@ export const codingInspectInputSchema=z.discriminatedUnion('op',[
26
26
  export const machineFsGatewayInputSchema=z.discriminatedUnion('op',[
27
27
  z.strictObject({op:z.literal('stat'),root_id:id,path:rel.default('.'),hash:z.boolean().default(false)}),
28
28
  z.strictObject({op:z.literal('list'),root_id:id,path:rel.default('.'),cursor,limit:z.number().int().min(1).max(1000).default(100),hash_files:z.boolean().default(false)}),
29
- z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false)}),
29
+ z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),
30
30
  z.strictObject({op:z.literal('write'),root_id:id,path:rel,content:z.string().max(12*1024*1024),encoding:z.enum(['utf8','base64']).default('utf8'),expected_sha256:z.string().regex(/^(?:absent|[0-9a-f]{64})$/)}),
31
31
  z.strictObject({op:z.literal('edit_exact'),root_id:id,path:rel,expected_sha256:hex64,old_string:z.string().min(1).max(262144),new_string:z.string().max(524288),expected_replacements:z.number().int().min(1).max(64).default(1)}),
32
32
  z.strictObject({op:z.literal('mkdir'),root_id:id,path:rel}),
@@ -36,7 +36,8 @@ export const machineFsGatewayInputSchema=z.discriminatedUnion('op',[
36
36
 
37
37
  export const processSessionGatewayInputSchema=z.discriminatedUnion('op',[
38
38
  z.strictObject({op:z.literal('start'),profile_id:id,root_id:id,cwd:rel.default('.'),args:z.array(z.string().max(8192)).max(64).default([])}),
39
- z.strictObject({op:z.literal('read'),session_id:hex64,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(262144).default(65536)}),
39
+ z.strictObject({op:z.literal('read'),session_id:hex64,offset:z.number().int().min(-(16*1024*1024)).default(0),max_bytes:z.number().int().min(1).max(262144).default(65536),wait_ms:z.number().int().min(0).max(30000).default(0),wait_for:z.string().min(1).max(256).nullable().default(null)}),
40
+ z.strictObject({op:z.literal('write'),session_id:hex64,input:z.string().min(1).max(1024*1024),append_newline:z.boolean().default(true)}),
40
41
  z.strictObject({op:z.literal('status'),session_id:hex64}),
41
42
  z.strictObject({op:z.literal('list')}),
42
43
  z.strictObject({op:z.literal('action'),session_id:hex64,action_id:id}),
@@ -83,7 +84,7 @@ async function machineFs(args,runtime){
83
84
  const map={
84
85
  stat:r=>r.stat({rootId:args.root_id,relativePath:args.path,hash:args.hash}),
85
86
  list:r=>r.list({rootId:args.root_id,relativePath:args.path,cursor:args.cursor,limit:args.limit,hashFiles:args.hash_files}),
86
- read:r=>r.read({rootId:args.root_id,relativePath:args.path,offset:args.offset,maxBytes:args.max_bytes,hashFile:args.hash_file}),
87
+ read:r=>r.read({rootId:args.root_id,relativePath:args.path,offset:args.offset,maxBytes:args.max_bytes,hashFile:args.hash_file,timeoutMs:args.timeout_ms}),
87
88
  write:r=>r.write({rootId:args.root_id,relativePath:args.path,content:args.content,encoding:args.encoding,expectedSha256:args.expected_sha256}),
88
89
  edit_exact:r=>r.editExact({rootId:args.root_id,relativePath:args.path,expectedSha256:args.expected_sha256,oldString:args.old_string,newString:args.new_string,expectedReplacements:args.expected_replacements}),
89
90
  mkdir:r=>r.mkdir({rootId:args.root_id,relativePath:args.path}),
@@ -96,7 +97,8 @@ async function machineFs(args,runtime){
96
97
  async function processSession(args,runtime){
97
98
  const map={
98
99
  start:r=>r.start({profileId:args.profile_id,rootId:args.root_id,cwd:args.cwd,args:args.args}),
99
- read:r=>r.read({sessionId:args.session_id,offset:args.offset,maxBytes:args.max_bytes}),
100
+ read:r=>r.read({sessionId:args.session_id,offset:args.offset,maxBytes:args.max_bytes,waitMs:args.wait_ms,waitFor:args.wait_for}),
101
+ write:r=>r.write({sessionId:args.session_id,input:args.input,appendNewline:args.append_newline}),
100
102
  status:r=>r.status({sessionId:args.session_id}),
101
103
  list:r=>r.list(),
102
104
  action:r=>r.action({sessionId:args.session_id,actionId:args.action_id}),
@@ -136,7 +138,7 @@ export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime
136
138
  });
137
139
  server.registerTool('coding_inspect',{description:'Typed compact coding inspection and semantic navigation gateway. No arbitrary tool dispatch.',inputSchema:codingInspectInputSchema,outputSchema:compactGatewayOutputSchema},args=>codingInspect(args,codingRuntime,semanticRuntime));
138
140
  server.registerTool('machine_fs',{description:'Typed compact machine filesystem gateway preserving root policy and mutation preconditions.',inputSchema:machineFsGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineFs(args,machineFsRuntime));
139
- server.registerTool('process_session',{description:'Typed compact DEADBYTE-owned process session gateway. No raw stdin or PID authority.',inputSchema:processSessionGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>processSession(args,processRuntime));
141
+ server.registerTool('process_session',{description:'Typed compact DEADBYTE-owned process session gateway. Raw input is available only for locally opt-in profiles and remains grant-bounded; numeric PID authority is never exposed.',inputSchema:processSessionGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>processSession(args,processRuntime));
140
142
  server.registerTool('machine_observe',{description:'Typed compact machine identity and read-only observation gateway. Revoke is intentionally excluded.',inputSchema:machineObserveInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineObserve(args,machineRuntime,observationRuntime));
141
143
  server.registerTool('desktop_control',{description:'Typed Desktop observe, target, status and guarded action gateway. No arbitrary dispatch; mutation still requires separate Desktop authority.',inputSchema:desktopControlInputSchema,outputSchema:compactGatewayOutputSchema},args=>invokeDesktopOperation(args,desktopRuntime));
142
144
  }
@@ -87,6 +87,9 @@ function controllerRuntimeReady(text){
87
87
  /^Local ready\s*:\s*True\s*$/m.test(value)&&
88
88
  /^Loop supervisor:\s*RUNNING\s+\[verified\]\s*$/m.test(value);
89
89
  }
90
+ function controllerRuntimeDegraded(text){
91
+ return /^DEADBYTE MCP\s*=\s*DEGRADED\s*$/m.test(String(text||''));
92
+ }
90
93
  async function listLogFiles(root){
91
94
  const files=[];
92
95
  const audit=path.join(root,'audit');
@@ -319,10 +322,11 @@ async function runRemoteSession({resumeCursor=null,verbose=false}={}){
319
322
  if(preflight.status===0&&controllerRuntimeReady(preflight.stdout)){
320
323
  process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
321
324
  }else{
322
- process.stdout.write('[BOOT] Starting bridge/runtime...\n');
323
- invokeController(ctx,'start',{discardOutput:true,timeoutMs:REMOTE_START_TIMEOUT_MS});
325
+ const recovery=preflight.status===0&&controllerRuntimeDegraded(preflight.stdout);
326
+ process.stdout.write(recovery?'[RECOVERY] Runtime DEGRADED; restarting verified owned components...\n':'[BOOT] Starting bridge/runtime...\n');
327
+ invokeController(ctx,recovery?'restart':'start',{discardOutput:true,timeoutMs:REMOTE_START_TIMEOUT_MS});
324
328
  const started=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
325
- if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error('remote controller start returned without a verified ONLINE runtime');
329
+ if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error(`remote controller ${recovery?'restart':'start'} returned without a verified ONLINE runtime`);
326
330
  startText=started.stdout||'';
327
331
  }
328
332
  process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
@@ -32,10 +32,10 @@ export function registerMachineFsTools(server, { runtime }) {
32
32
  });
33
33
  server.registerTool('machine_fs_read', {
34
34
  description:'Read one bounded byte range from a root-relative regular file.',
35
- inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().nonnegative().default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false) }),
35
+ inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().nonnegative().default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
36
36
  outputSchema:machineFsReadOutputSchema
37
- }, async ({root_id,path,offset,max_bytes,hash_file}) => {
38
- try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file})); }
37
+ }, async ({root_id,path,offset,max_bytes,hash_file,timeout_ms}) => {
38
+ try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file,timeoutMs:timeout_ms})); }
39
39
  catch (error) { return fail(machineFsReadOutputSchema,error); }
40
40
  });
41
41
  server.registerTool('machine_fs_write', {
@@ -33,7 +33,7 @@ export const machineFsReadOutputSchema = z.strictObject({
33
33
  offset: z.number().int().nonnegative().optional(), next_offset: z.number().int().nonnegative().optional(),
34
34
  file_size: z.number().int().nonnegative().optional(), eof: z.boolean().optional(),
35
35
  data_base64: z.string().max(12 * 1024 * 1024).optional(), data_sha256: hex64.optional(),
36
- file_sha256: hex64.nullable().optional()
36
+ file_sha256: hex64.nullable().optional(), timeout_ms: z.number().int().min(100).max(180000).optional()
37
37
  });
38
38
 
39
39
  const commitBase = {
@@ -0,0 +1,46 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createReadStream, promises as fs } from 'node:fs';
3
+
4
+ const [file, offsetText, lengthText, hashText] = process.argv.slice(2);
5
+ const offset = Number(offsetText);
6
+ const length = Number(lengthText);
7
+ if (typeof file !== 'string' || !Number.isSafeInteger(offset) || offset < 0 ||
8
+ !Number.isSafeInteger(length) || length < 0 || length > 4 * 1024 * 1024 ||
9
+ !['0','1'].includes(hashText ?? '0')) {
10
+ process.stderr.write('invalid machine-fs read worker arguments\n');
11
+ process.exit(64);
12
+ }
13
+
14
+ try {
15
+ const handle = await fs.open(file, 'r');
16
+ let bytes;
17
+ try {
18
+ const buffer = Buffer.alloc(length);
19
+ const result = length ? await handle.read(buffer, 0, length, offset) : { bytesRead: 0 };
20
+ bytes = buffer.subarray(0, result.bytesRead);
21
+ } finally {
22
+ await handle.close();
23
+ }
24
+
25
+ let fileSha256 = null;
26
+ if (hashText === '1') {
27
+ const hash = createHash('sha256');
28
+ await new Promise((resolve, reject) => {
29
+ const stream = createReadStream(file);
30
+ stream.on('data', chunk => hash.update(chunk));
31
+ stream.once('error', reject);
32
+ stream.once('end', resolve);
33
+ });
34
+ fileSha256 = hash.digest('hex');
35
+ }
36
+
37
+ process.stdout.write(JSON.stringify({
38
+ schema: 'deadbyte.machine-fs-read-worker.v1',
39
+ bytes_read: bytes.length,
40
+ data_base64: bytes.toString('base64'),
41
+ file_sha256: fileSha256
42
+ }));
43
+ } catch (error) {
44
+ process.stderr.write(String(error?.stack ?? error));
45
+ process.exit(1);
46
+ }