deadbyte-mcp 0.15.47 → 0.15.49
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 +111 -48
- package/PROVENANCE.json +1 -1
- package/README.txt +18 -18
- package/RELEASE.json +3 -3
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bench/r121-epoch-readhead.mjs +54 -0
- package/bench/r124-epoch-predecessor.mjs +53 -0
- package/bin/deadbyte-epoch-lock-r85.node +0 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
- package/docs/R128-RELEASE-CANDIDATE.md +26 -0
- package/docs/R75-RECOVERY-HANDOFF.md +16 -0
- package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
- package/native/deadbyte-epoch-lock-r85.cpp +241 -0
- package/package.json +1 -1
- package/scripts/build-epoch-lock-r85.ps1 +34 -0
- package/scripts/deploy-r48.mjs +3 -1
- package/scripts/npm-release-r48.mjs +18 -4
- package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/verify-authority-epoch-history.mjs +38 -0
- package/scripts/verify-r70-full-regression.ps1 +12 -2
- package/src/activation-authority-proof-r75.mjs +67 -0
- package/src/activation-continuity-engine-r65.mjs +17 -10
- package/src/activation-preflight-r65.mjs +13 -0
- package/src/agent-eval-r42.mjs +30 -0
- package/src/authority-continuity-r65.mjs +442 -29
- package/src/autonomous-local-supervisor.mjs +77 -47
- package/src/deadbyte-cli.mjs +73 -46
- package/src/epoch-lock-r85.mjs +48 -0
- package/src/foreground-marker-cas-win32.mjs +284 -0
- package/src/foreground-process-identity.mjs +3 -3
- package/src/privileged-install-trust-r89.mjs +196 -0
- package/src/remote-live-log.mjs +15 -8
- package/src/remote-log-decode-r60.mjs +65 -0
- package/src/remote-session-guardian.mjs +13 -12
- package/src/runtime-update.mjs +5 -22
- package/src/version.mjs +1 -1
- package/src/windows-powershell-environment.mjs +24 -0
- package/test/autonomous-local-supervisor.test.mjs +1 -1
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/harness2-eval-context-isolation.test.mjs +90 -0
- package/test/helpers/r75-authority-proof.mjs +9 -0
- package/test/r100-posix-owned-lock-race.test.mjs +72 -0
- package/test/r101-acl-probe-environment.test.mjs +51 -0
- package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
- package/test/r107-updater-powershell-path.test.mjs +77 -0
- package/test/r108-remote-powershell-path.test.mjs +59 -0
- package/test/r110-supervisor-powershell-path.test.mjs +28 -0
- package/test/r111-supervisor-marker-cas.test.mjs +116 -0
- package/test/r112-supervisor-claim-cas.test.mjs +63 -0
- package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
- package/test/r114-supervisor-absence-proof.test.mjs +45 -0
- package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
- package/test/r116-authority-crash-matrix.test.mjs +83 -0
- package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
- package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
- package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
- package/test/r120-authority-history-audit.test.mjs +103 -0
- package/test/r121-signed-transition-invariant.test.mjs +132 -0
- package/test/r122-epoch-bounded-read.test.mjs +58 -0
- package/test/r123-recovery-bounded-read.test.mjs +64 -0
- package/test/r124-live-log-integrity.test.mjs +66 -0
- package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
- package/test/r124-signed-predecessor-shape.test.mjs +66 -0
- package/test/r125-restore-binding-types.test.mjs +63 -0
- package/test/r126-restore-policy-envelope.test.mjs +89 -0
- package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
- package/test/r33-closeout-regression.test.mjs +2 -1
- package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
- package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
- package/test/r56-rollback-launcher-restore.test.mjs +2 -2
- package/test/r57-core-startup-race.test.mjs +2 -2
- package/test/r58-concurrent-recovery.test.mjs +10 -7
- package/test/r58-stale-owner.test.mjs +5 -6
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r65-activation-orchestration.test.mjs +2 -1
- package/test/r65-activation-preflight.test.mjs +7 -6
- package/test/r65-authority-epoch-recovery.test.mjs +3 -1
- package/test/r65-authority-epoch-store.test.mjs +9 -6
- package/test/r65-authority-restored-safe.test.mjs +2 -1
- package/test/r65-deploy-preflight.test.mjs +13 -14
- package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
- package/test/r65-legacy-authority-migration.test.mjs +2 -1
- package/test/r67-npm-cross-volume.test.mjs +9 -2
- package/test/r71-full-verifier-artifact.test.mjs +21 -15
- package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
- package/test/r71-release-identity.test.mjs +3 -1
- package/test/r74-kernel-lock.test.mjs +12 -6
- package/test/r74-prepublish-contract.test.mjs +3 -1
- package/test/r75-activation-authority-proof.test.mjs +46 -0
- package/test/r75-authority-evidence.test.mjs +77 -0
- package/test/r76-pinned-marker-cas.test.mjs +78 -0
- package/test/r77-epoch-grant-cas.test.mjs +51 -0
- package/test/r78-parent-junction-cas.test.mjs +62 -0
- package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
- package/test/r79-windows-fsync-regression.test.mjs +45 -0
- package/test/r80-durable-epoch-commit.test.mjs +76 -0
- package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
- package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
- package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
- package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
- package/test/r85-epoch-repeat-append.test.mjs +30 -0
- package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
- package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
- package/test/r88-native-disposition-failure.test.mjs +35 -0
- package/test/r89-privileged-install-boundary.test.mjs +123 -0
- package/test/r92-acl-ace-applicability.test.mjs +45 -0
- package/test/r94-win32-propagation-enum.test.mjs +51 -0
- package/test/r99-privileged-import-closure.test.mjs +45 -0
- package/test/remote-session-cli.test.mjs +69 -9
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
|
-
import { access,
|
|
4
|
+
import { access, readFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { startAutonomousSupervisor } from './autonomous-supervisor.mjs';
|
|
7
|
+
import { windowsPowerShellEnvironment } from './windows-powershell-environment.mjs';
|
|
8
|
+
import { readPinnedEpochLockSnapshot, createPinnedEpochLock, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
|
|
9
|
+
import { runNativeInspect } from './native-inspector-r67.mjs';
|
|
7
10
|
|
|
8
11
|
const execFileAsync=promisify(execFile);
|
|
12
|
+
const TRUSTED_WINDOWS_ROOT='C:\\Windows';
|
|
9
13
|
export const AUTONOMOUS_SUPERVISOR_OWNER_SCHEMA='deadbyte.autonomous-supervisor-owner.v1';
|
|
10
14
|
const HEX64=/^[0-9a-f]{64}$/;
|
|
11
15
|
const RECOVERY_OPERATIONS=Object.freeze(['status','resume','park','disarmautonomous','disarmcoding','disarmprocess','disarmhost']);
|
|
@@ -33,12 +37,12 @@ function validateMarker(marker){
|
|
|
33
37
|
}
|
|
34
38
|
async function powershellPath(){
|
|
35
39
|
if(process.platform!=='win32') return null;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return
|
|
40
|
+
const candidate=path.join(TRUSTED_WINDOWS_ROOT,'System32','WindowsPowerShell','v1.0','powershell.exe');
|
|
41
|
+
try { await access(candidate); return candidate; } catch { return null; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function trustedPowerShellEnvironment(){
|
|
45
|
+
return windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT});
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export async function observeCurrentProcessIdentity(pid=process.pid){
|
|
@@ -49,7 +53,7 @@ export async function observeCurrentProcessIdentity(pid=process.pid){
|
|
|
49
53
|
let stdout;
|
|
50
54
|
try {
|
|
51
55
|
({stdout}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-Command',script],{
|
|
52
|
-
windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8'
|
|
56
|
+
windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
|
|
53
57
|
}));
|
|
54
58
|
} catch { return null; }
|
|
55
59
|
let parsed;
|
|
@@ -64,14 +68,36 @@ export async function observeCurrentProcessIdentity(pid=process.pid){
|
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
async function confirmProcessAbsent(pid){
|
|
72
|
+
const shell=await powershellPath();
|
|
73
|
+
if(!shell) return false;
|
|
74
|
+
const script=`$ErrorActionPreference='Stop'; $p=Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}' -ErrorAction Stop; if($null -eq $p){[Console]::Out.WriteLine('ABSENT')}else{[Console]::Out.WriteLine('PRESENT')}`;
|
|
75
|
+
try {
|
|
76
|
+
const {stdout}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-Command',script],{
|
|
77
|
+
windowsHide:true,timeout:10_000,maxBuffer:1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
|
|
78
|
+
});
|
|
79
|
+
if(String(stdout).trim()!=='ABSENT') return false;
|
|
80
|
+
const native=await runNativeInspect(['identity',String(pid)]);
|
|
81
|
+
return native.status==='failed'&&native.error_code==='INVALID_PARAMETER'&&
|
|
82
|
+
native.win32_error===87&&native.error==='OpenProcess identity win32=87';
|
|
83
|
+
} catch { return false; }
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
async function readMarker(markerPath){
|
|
68
|
-
let raw;
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
let raw, fileId=null;
|
|
88
|
+
if(process.platform==='win32'){
|
|
89
|
+
const snapshot=readPinnedEpochLockSnapshot(markerPath);
|
|
90
|
+
if(!snapshot) return {exists:false};
|
|
91
|
+
raw=snapshot.bytes;
|
|
92
|
+
fileId=snapshot.fileId;
|
|
93
|
+
} else {
|
|
94
|
+
try { raw=await readFile(markerPath); }
|
|
95
|
+
catch(error){ if(error?.code==='ENOENT') return {exists:false}; throw error; }
|
|
96
|
+
}
|
|
71
97
|
let marker;
|
|
72
98
|
try { marker=JSON.parse(raw.toString('utf8')); }
|
|
73
|
-
catch { return {exists:true,raw,raw_sha256:sha256Bytes(raw),valid:false,marker:null}; }
|
|
74
|
-
return {exists:true,raw,raw_sha256:sha256Bytes(raw),valid:validateMarker(marker),marker};
|
|
99
|
+
catch { return {exists:true,raw,raw_sha256:sha256Bytes(raw),file_id:fileId,valid:false,marker:null}; }
|
|
100
|
+
return {exists:true,raw,raw_sha256:sha256Bytes(raw),file_id:fileId,valid:validateMarker(marker),marker};
|
|
75
101
|
}
|
|
76
102
|
function markerFor(identity){
|
|
77
103
|
return {
|
|
@@ -91,28 +117,28 @@ export async function observeSupervisor(options={}){
|
|
|
91
117
|
const markerPath=assertOptions(options);
|
|
92
118
|
const stored=await readMarker(markerPath);
|
|
93
119
|
if(!stored.exists) return {status:'absent',live:false,reason:'no_owner',marker:null,marker_sha256:null};
|
|
94
|
-
|
|
120
|
+
const binding={marker_sha256:stored.raw_sha256,marker_file_id:stored.file_id};
|
|
121
|
+
if(!stored.valid) return {status:'unverified',live:false,reason:'marker_invalid',marker:stored.marker,...binding};
|
|
95
122
|
const observed=await observeCurrentProcessIdentity(stored.marker.pid);
|
|
96
|
-
if(!observed)
|
|
123
|
+
if(!observed){
|
|
124
|
+
if(await confirmProcessAbsent(stored.marker.pid))
|
|
125
|
+
return {status:'stale',live:false,reason:'process_absent',marker:stored.marker,...binding};
|
|
126
|
+
return {status:'unverified',live:false,reason:'process_unobservable',marker:stored.marker,...binding};
|
|
127
|
+
}
|
|
97
128
|
if(observed.creation_time_utc!==new Date(stored.marker.creation_time_utc).toISOString()) {
|
|
98
|
-
return {status:'stale',live:false,reason:'pid_reused',marker:stored.marker,observation:observed
|
|
129
|
+
return {status:'stale',live:false,reason:'pid_reused',marker:stored.marker,observation:observed,...binding};
|
|
99
130
|
}
|
|
100
131
|
if(!samePath(observed.image_path,stored.marker.image_path)||observed.image_sha256!==stored.marker.image_sha256) {
|
|
101
|
-
return {status:'
|
|
132
|
+
return {status:'unverified',live:false,reason:'identity_mismatch',marker:stored.marker,observation:observed,...binding};
|
|
102
133
|
}
|
|
103
|
-
return {status:'owned',live:true,reason:'live_verified_owner',marker:stored.marker,observation:observed
|
|
134
|
+
return {status:'owned',live:true,reason:'live_verified_owner',marker:stored.marker,observation:observed,...binding};
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
export async function claimSupervisor(options={}){
|
|
107
138
|
const markerPath=assertOptions(options);
|
|
108
139
|
const marker=await currentMarker();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await writeFile(markerPath,`${JSON.stringify(marker)}\n`,{flag:'wx',mode:0o600});
|
|
112
|
-
} catch(error){
|
|
113
|
-
if(error?.code==='EEXIST') throw new Error('supervisor owner marker already exists; takeover required');
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
140
|
+
const created=createPinnedEpochLock(markerPath,Buffer.from(`${JSON.stringify(marker)}\n`));
|
|
141
|
+
if(!created) throw new Error('supervisor owner marker already exists; takeover required');
|
|
116
142
|
return {status:'claimed',instance_nonce:marker.instance_nonce,marker};
|
|
117
143
|
}
|
|
118
144
|
|
|
@@ -121,17 +147,13 @@ export async function takeoverSupervisor(options={}){
|
|
|
121
147
|
const prior=await observeSupervisor({markerPath});
|
|
122
148
|
if(prior.live&&prior.reason==='live_verified_owner') throw new Error('supervisor takeover refused: live verified owner');
|
|
123
149
|
if(prior.reason==='no_owner') return claimSupervisor({markerPath});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if(!
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if(!check.exists||check.raw_sha256!==prior.marker_sha256) throw new Error('supervisor ownership changed during takeover');
|
|
132
|
-
await rename(temp,markerPath);
|
|
133
|
-
} finally { await rm(temp,{force:true}).catch(()=>{}); }
|
|
134
|
-
return {status:'claimed',instance_nonce:marker.instance_nonce,marker,taken_over_reason:prior.reason};
|
|
150
|
+
if(!['process_absent','pid_reused'].includes(prior.reason))
|
|
151
|
+
throw new Error(`supervisor takeover refused: ownership unverified (${prior.reason})`);
|
|
152
|
+
if(!prior.marker_file_id) throw new Error('supervisor ownership file identity unavailable for takeover');
|
|
153
|
+
const removed=deletePinnedRemoteMarker(markerPath,prior.marker_sha256,{expectedFileId:prior.marker_file_id});
|
|
154
|
+
if(removed!=='REMOVED') throw new Error(`supervisor ownership changed during takeover: ${removed}`);
|
|
155
|
+
const claimed=await claimSupervisor({markerPath});
|
|
156
|
+
return {...claimed,taken_over_reason:prior.reason};
|
|
135
157
|
}
|
|
136
158
|
|
|
137
159
|
export async function releaseSupervisor(options={}){
|
|
@@ -147,9 +169,9 @@ export async function releaseSupervisor(options={}){
|
|
|
147
169
|
!samePath(current.image_path,observed.observation.image_path)||current.image_sha256!==observed.observation.image_sha256) {
|
|
148
170
|
throw new Error('supervisor ownership current process identity mismatch');
|
|
149
171
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
172
|
+
if(!observed.marker_file_id) throw new Error('supervisor ownership file identity unavailable for release');
|
|
173
|
+
const removed=deletePinnedRemoteMarker(markerPath,observed.marker_sha256,{expectedFileId:observed.marker_file_id});
|
|
174
|
+
if(removed!=='REMOVED') throw new Error(`supervisor ownership changed before release: ${removed}`);
|
|
153
175
|
return {status:'released'};
|
|
154
176
|
}
|
|
155
177
|
|
|
@@ -180,7 +202,7 @@ export async function runPinnedControllerOperation(options={}){
|
|
|
180
202
|
if(!shell) throw new Error('supervisor controller requires Windows PowerShell');
|
|
181
203
|
try {
|
|
182
204
|
const {stdout,stderr}=await execFileAsync(shell,['-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',controllerPath,operation],{
|
|
183
|
-
windowsHide:true,timeout:120_000,maxBuffer:4*1024*1024,encoding:'utf8'
|
|
205
|
+
windowsHide:true,timeout:120_000,maxBuffer:4*1024*1024,encoding:'utf8',env:trustedPowerShellEnvironment()
|
|
184
206
|
});
|
|
185
207
|
return {exit_code:0,stdout:String(stdout??''),stderr:String(stderr??'')};
|
|
186
208
|
} catch(error) {
|
|
@@ -228,20 +250,28 @@ export async function startLocalSupervisorHost({runtime,policy=runtime?.policy,m
|
|
|
228
250
|
try {
|
|
229
251
|
supervisor=startAutonomousSupervisor({runtime,policy,idleDelayMs,maxCycles,logger,continuous});
|
|
230
252
|
} catch(error) {
|
|
231
|
-
await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce})
|
|
253
|
+
try { await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce}); }
|
|
254
|
+
catch(cleanupError) {
|
|
255
|
+
throw new AggregateError([error,cleanupError],
|
|
256
|
+
'supervisor startup and ownership cleanup both failed',{cause:error});
|
|
257
|
+
}
|
|
232
258
|
throw error;
|
|
233
259
|
}
|
|
234
260
|
let stopped=false;
|
|
261
|
+
let stopPromise=null;
|
|
235
262
|
return Object.freeze({
|
|
236
263
|
marker_path:path.resolve(markerPath),instance_nonce:claim.instance_nonce,supervisor,
|
|
237
|
-
get running(){ return !
|
|
238
|
-
async scanNow(){ if(
|
|
264
|
+
get running(){ return !stopPromise&&supervisor.running; },
|
|
265
|
+
async scanNow(){ if(stopPromise) throw new Error('local supervisor host stopping or stopped'); return supervisor.scanNow(); },
|
|
239
266
|
async stop(){
|
|
240
267
|
if(stopped) return {status:'stopped'};
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
if(!stopPromise) stopPromise=(async()=>{
|
|
269
|
+
await supervisor.stop();
|
|
270
|
+
await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce});
|
|
271
|
+
stopped=true;
|
|
272
|
+
return {status:'stopped'};
|
|
273
|
+
})();
|
|
274
|
+
return stopPromise;
|
|
245
275
|
}
|
|
246
276
|
});
|
|
247
277
|
}
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { closeSync, openSync } from 'node:fs';
|
|
3
|
-
import { lstat, mkdir, open, readFile, readdir,
|
|
3
|
+
import { lstat, mkdir, open, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { spawn, spawnSync } from 'node:child_process';
|
|
5
5
|
import { createHash, randomBytes } from 'node:crypto';
|
|
6
6
|
import os from 'node:os';
|
|
@@ -22,9 +22,10 @@ import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
|
|
|
22
22
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
23
23
|
import { resolveStateRoot } from './state-root.mjs';
|
|
24
24
|
import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
|
|
25
|
+
import {readRemoteMarkerSnapshot,deletePinnedRemoteMarker} from './foreground-marker-cas-win32.mjs';
|
|
25
26
|
import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
|
|
26
27
|
import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
|
|
27
|
-
import { decodeRemoteLogBytes, boundedRemoteDiagnostic,
|
|
28
|
+
import { decodeRemoteLogBytes, boundedRemoteDiagnostic, readRemoteLogAppend } from './remote-log-decode-r60.mjs';
|
|
28
29
|
|
|
29
30
|
const REMOTE_COMMANDS=new Set([
|
|
30
31
|
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
@@ -42,6 +43,7 @@ const CURRENT_PACKAGE_ROOT=path.resolve(fileURLToPath(new URL('..',import.meta.u
|
|
|
42
43
|
const CURRENT_DEPLOY_SCRIPT=path.join(CURRENT_PACKAGE_ROOT,'scripts','deploy-r48.mjs');
|
|
43
44
|
const REMOTE_UPDATE_TIMEOUT_MS=900000;
|
|
44
45
|
const LIVE_LOG_DISCOVERY_INTERVAL_MS=1000;
|
|
46
|
+
const TRUSTED_WINDOWS_ROOT='C:\\Windows';
|
|
45
47
|
|
|
46
48
|
function stateRoot(){return resolveStateRoot();}
|
|
47
49
|
async function requireRegular(file,label){const info=await lstat(file);if(!info.isFile()||info.isSymbolicLink()) throw new Error(`${label} must be a regular non-symlink file`);return file;}
|
|
@@ -78,8 +80,7 @@ async function remoteContext(){
|
|
|
78
80
|
if(process.platform!=='win32') throw new Error('remote controller is currently Windows-only');
|
|
79
81
|
const root=stateRoot();
|
|
80
82
|
const launcher=await requireRegular(path.join(root,'bin','DEADBYTE-MCP.ps1'),'stable DEADBYTE launcher');
|
|
81
|
-
const
|
|
82
|
-
const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
|
|
83
|
+
const powershell=await requireRegular(path.join(TRUSTED_WINDOWS_ROOT,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
|
|
83
84
|
return {root,launcher,powershell};
|
|
84
85
|
}
|
|
85
86
|
function boundedSingleLine(value,maxChars=2048){
|
|
@@ -202,7 +203,8 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
|
|
|
202
203
|
stdio=['ignore',stdoutFd,stderrFd];
|
|
203
204
|
}else stdio=capture?['ignore','pipe','pipe']:'inherit';
|
|
204
205
|
const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
|
|
205
|
-
env:windowsPowerShellEnvironment(
|
|
206
|
+
env:windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT,
|
|
207
|
+
...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})})};
|
|
206
208
|
if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
|
|
207
209
|
result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command,...(json?['-Json']:[])],options);
|
|
208
210
|
}finally{
|
|
@@ -308,32 +310,15 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
|
|
|
308
310
|
if(!page.has_more) return next;
|
|
309
311
|
}
|
|
310
312
|
}
|
|
311
|
-
async function readAppend(file,
|
|
312
|
-
|
|
313
|
-
let start=offset;
|
|
314
|
-
const reset=info.size<start;
|
|
315
|
-
if(reset) start=0;
|
|
316
|
-
if(info.size===start) return {offset:start,text:''};
|
|
317
|
-
const handle=await open(file,'r');
|
|
318
|
-
let encoding=null,buffer,bytesRead=0;
|
|
319
|
-
try{
|
|
320
|
-
const header=Buffer.alloc(2);await handle.read(header,0,2,0);
|
|
321
|
-
if(header[0]===0xff&&header[1]===0xfe&&/\.log$/i.test(file)){
|
|
322
|
-
encoding='utf16le';if(start%2!==0)start=Math.max(0,start-1);
|
|
323
|
-
}
|
|
324
|
-
const length=Math.min(1024*1024,info.size-start);
|
|
325
|
-
if(length===0)return {offset:start,text:''};
|
|
326
|
-
buffer=Buffer.alloc(length);
|
|
327
|
-
({bytesRead}=await handle.read(buffer,0,length,start));
|
|
328
|
-
}finally{await handle.close();}
|
|
329
|
-
const complete=safeRemoteLogPrefixLength(buffer.subarray(0,bytesRead),encoding??'utf8');
|
|
330
|
-
return {offset:start+complete,text:decodeRemoteLogBytes(file,buffer.subarray(0,complete),{encoding}),reset};
|
|
313
|
+
async function readAppend(file,cursor){
|
|
314
|
+
return readRemoteLogAppend(file,cursor);
|
|
331
315
|
}
|
|
332
316
|
async function snapshotRemoteLogOffsets(root){
|
|
333
317
|
const offsets=new Map();
|
|
334
318
|
const files=await listLogFiles(root);
|
|
335
319
|
for(const file of files){
|
|
336
|
-
|
|
320
|
+
const snapshot=await readRemoteLogAppend(file,null,{startAtEnd:true});
|
|
321
|
+
if(snapshot.cursor)offsets.set(file,snapshot.cursor);
|
|
337
322
|
}
|
|
338
323
|
return offsets;
|
|
339
324
|
}
|
|
@@ -345,6 +330,7 @@ function timelineFilterEvent(timeline){
|
|
|
345
330
|
return {
|
|
346
331
|
event_type:'tool.result',request_id:timeline.request_id,tool:timeline.tool,
|
|
347
332
|
outcome:{
|
|
333
|
+
status:timeline.side_effect_state==='unknown'?'interrupted':timeline.status,
|
|
348
334
|
is_error:timeline.status==='error',
|
|
349
335
|
...(Number.isFinite(Number(timeline.duration_ms))?{duration_ms:Number(timeline.duration_ms)}:{}),
|
|
350
336
|
...(timeline.side_effect_state?{side_effect_state:timeline.side_effect_state}:{}),
|
|
@@ -367,10 +353,13 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
367
353
|
nextDiscoveryAt=now+LIVE_LOG_DISCOVERY_INTERVAL_MS;
|
|
368
354
|
}
|
|
369
355
|
for(const file of liveFiles){
|
|
370
|
-
const prior=activeOffsets.has(file)?activeOffsets.get(file):
|
|
356
|
+
const prior=activeOffsets.has(file)?activeOffsets.get(file):null;
|
|
371
357
|
const next=await readAppend(file,prior);
|
|
372
|
-
activeOffsets.set(file,next.
|
|
373
|
-
if(next.reset)
|
|
358
|
+
activeOffsets.set(file,next.cursor);
|
|
359
|
+
if(next.reset){
|
|
360
|
+
partialLines.delete(file);
|
|
361
|
+
process.stderr.write('[LIVELOG] STREAM_RESET '+path.basename(file)+' — '+next.resetReason+'; check audit continuity before trusting coverage\n');
|
|
362
|
+
}
|
|
374
363
|
if(!next.text)continue;
|
|
375
364
|
const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
|
|
376
365
|
partialLines.set(file,lines.pop()??'');
|
|
@@ -378,6 +367,8 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
378
367
|
for(const line of lines){
|
|
379
368
|
if(Buffer.byteLength(line,'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
|
|
380
369
|
const auditEvent=parseAuditLogLine(file,line);
|
|
370
|
+
if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))&&!auditEvent)
|
|
371
|
+
throw new Error('LIVELOG_AUDIT_RECORD_INVALID '+path.basename(file)+'; refusing to silently skip audit bytes');
|
|
381
372
|
const timeline=auditTimelineRecord(file,line,auditEvent);
|
|
382
373
|
let recorded=null;
|
|
383
374
|
if(timeline&&journal) recorded=timeline.event_type==='tool.call'
|
|
@@ -456,8 +447,9 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
456
447
|
const ownerPid=remoteOwnerPid(prior);
|
|
457
448
|
if(pidAlive(ownerPid)){
|
|
458
449
|
if(!prior.owner_identity)throw new Error(`remote session ownership unverified for legacy pid=${ownerPid}; marker retained`);
|
|
459
|
-
if(prior.owner_identity.pid!==ownerPid)throw new Error('remote session owner identity PID binding mismatch');
|
|
460
|
-
if(foregroundIdentityMatches(prior.owner_identity,queryForegroundIdentity(ownerPid)))throw new Error(
|
|
450
|
+
if(prior.owner_identity.pid!==ownerPid)throw new Error('remote session owner identity PID binding mismatch; marker retained');
|
|
451
|
+
if(!foregroundIdentityMatches(prior.owner_identity,queryForegroundIdentity(ownerPid)))throw new Error('remote session owner identity mismatch; marker retained');
|
|
452
|
+
throw new Error(`remote session already active pid=${ownerPid}`);
|
|
461
453
|
}
|
|
462
454
|
let guardianAlive=prior.schema==='deadbyte.remote-session.v2'&&pidAlive(prior.guardian_pid);
|
|
463
455
|
if(guardianAlive){
|
|
@@ -475,9 +467,7 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
475
467
|
}
|
|
476
468
|
throw new Error(`remote session guardian cleanup still active pid=${prior.guardian_pid}`);
|
|
477
469
|
}
|
|
478
|
-
|
|
479
|
-
await cleanupRemoteSession(ctx,{displayMode});
|
|
480
|
-
await rm(remoteSessionMarker(ctx.root),{force:true});
|
|
470
|
+
throw new Error(`remote session ownership unverified for stale pid=${ownerPid}; marker retained`);
|
|
481
471
|
}
|
|
482
472
|
async function claimRemoteSession(ctx){
|
|
483
473
|
const dir=path.join(ctx.root,'runtime');await mkdir(dir,{recursive:true});
|
|
@@ -505,14 +495,28 @@ async function claimRemoteSession(ctx){
|
|
|
505
495
|
return marker;
|
|
506
496
|
}catch(error){
|
|
507
497
|
try{guardian?.kill();}catch{}
|
|
508
|
-
|
|
498
|
+
try{
|
|
499
|
+
const snapshot=await readRemoteMarkerSnapshot(markerPath);
|
|
500
|
+
if(!snapshot||snapshot.marker?.owner_pid!==marker.owner_pid||snapshot.marker?.session_nonce!==marker.session_nonce)
|
|
501
|
+
throw new Error('claim marker ownership changed or missing');
|
|
502
|
+
if(deletePinnedRemoteMarker(markerPath,snapshot.sha256)!=='REMOVED')
|
|
503
|
+
throw new Error('claim marker changed before native deletion');
|
|
504
|
+
}catch(cleanupError){
|
|
505
|
+
throw new AggregateError([error,cleanupError],
|
|
506
|
+
'remote session claim failed; marker cleanup unverified and marker retained');
|
|
507
|
+
}
|
|
509
508
|
throw error;
|
|
510
509
|
}
|
|
511
510
|
}
|
|
512
511
|
async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
|
|
513
|
-
let
|
|
512
|
+
let snapshot;
|
|
513
|
+
try{snapshot=await readRemoteMarkerSnapshot(remoteSessionMarker(ctx.root));}
|
|
514
|
+
catch(error){writeRemote(process.stderr,`${remoteTag('warn',displayMode)} remote session marker cleanup: ${String(error?.message||error)}\n`,displayMode);throw new Error(`remote session ownership marker retained: ${String(error?.message||error)}`);}
|
|
515
|
+
const current=snapshot?.marker;
|
|
514
516
|
const same=current?.schema==='deadbyte.remote-session.v2'&¤t.owner_pid===marker.owner_pid&¤t.session_nonce===marker.session_nonce;
|
|
515
|
-
if(same)
|
|
517
|
+
if(!same)throw new Error('remote session ownership marker changed or missing; marker retained');
|
|
518
|
+
if(deletePinnedRemoteMarker(remoteSessionMarker(ctx.root),snapshot.sha256)!=='REMOVED')
|
|
519
|
+
throw new Error('remote session ownership marker replaced before native deletion; marker retained');
|
|
516
520
|
}
|
|
517
521
|
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default'}={}){
|
|
518
522
|
const display=prepareRemoteConsole();
|
|
@@ -531,10 +535,21 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
531
535
|
}finally{await startupLock.release();}
|
|
532
536
|
try{
|
|
533
537
|
await synchronizeForegroundRuntime(ctx,{displayMode});
|
|
534
|
-
}catch(
|
|
535
|
-
|
|
536
|
-
await
|
|
537
|
-
|
|
538
|
+
}catch(primaryError){
|
|
539
|
+
let cleanupError=null;
|
|
540
|
+
try{await cleanupRemoteSession(ctx,{displayMode});}
|
|
541
|
+
catch(error){cleanupError=error;}
|
|
542
|
+
if(!cleanupError){
|
|
543
|
+
try{await releaseRemoteSession(ctx,marker,{displayMode});}
|
|
544
|
+
catch(error){cleanupError=error;}
|
|
545
|
+
}
|
|
546
|
+
if(cleanupError){
|
|
547
|
+
const primaryDetail=String(primaryError?.message||primaryError);
|
|
548
|
+
const cleanupDetail=String(cleanupError?.message||cleanupError);
|
|
549
|
+
throw new AggregateError([primaryError,cleanupError],
|
|
550
|
+
`remote activation failed: ${primaryDetail}; cleanup failed, ownership marker retained: ${cleanupDetail}`);
|
|
551
|
+
}
|
|
552
|
+
throw primaryError;
|
|
538
553
|
}
|
|
539
554
|
const resultJournal=await openRemoteResultJournal({
|
|
540
555
|
filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
|
|
@@ -550,6 +565,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
550
565
|
if(watchStdin){process.stdin.once('end',abort);process.stdin.once('close',abort);process.stdin.resume();}
|
|
551
566
|
let ready=false;
|
|
552
567
|
let logTask=null;
|
|
568
|
+
let sessionError=null;
|
|
553
569
|
try{
|
|
554
570
|
process.stdout.write('DEADBYTE MCP\n');
|
|
555
571
|
process.stdout.write(`${formatRemoteLifecycle('device_start',{displayMode})}\n`);
|
|
@@ -609,8 +625,9 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
609
625
|
process.stdout.write('[OK] ONLINE\n');
|
|
610
626
|
process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
|
|
611
627
|
if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
|
|
612
|
-
await logTask;
|
|
613
|
-
}
|
|
628
|
+
await logTask;
|
|
629
|
+
}catch(error){sessionError=error;throw error;}
|
|
630
|
+
finally{
|
|
614
631
|
const wasAborted=aborter.signal.aborted;
|
|
615
632
|
aborter.abort();
|
|
616
633
|
if(logTask) await logTask.catch(()=>{});
|
|
@@ -623,8 +640,18 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
623
640
|
if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
|
|
624
641
|
process.stdout.write(`${formatRemoteLifecycle('cleanup',{displayMode})}\n`);
|
|
625
642
|
if(ready||!wasAborted) process.stdout.write('[INFO] Disconnecting; revoking authority...\n');
|
|
626
|
-
await cleanupRemoteSession(ctx,{displayMode});
|
|
627
|
-
|
|
643
|
+
try{await cleanupRemoteSession(ctx,{displayMode});}
|
|
644
|
+
catch(cleanupError){
|
|
645
|
+
if(sessionError) throw new AggregateError([sessionError,cleanupError],
|
|
646
|
+
`remote activation failed: ${String(sessionError?.message||sessionError)}; cleanup failed, ownership marker retained: ${String(cleanupError?.message||cleanupError)}`);
|
|
647
|
+
throw cleanupError;
|
|
648
|
+
}
|
|
649
|
+
try{await releaseRemoteSession(ctx,marker,{displayMode});}
|
|
650
|
+
catch(markerError){
|
|
651
|
+
if(sessionError)throw new AggregateError([sessionError,markerError],
|
|
652
|
+
`remote activation failed: ${String(sessionError?.message||sessionError)}; marker cleanup failed: ${String(markerError?.message||markerError)}`);
|
|
653
|
+
throw markerError;
|
|
654
|
+
}
|
|
628
655
|
}
|
|
629
656
|
}
|
|
630
657
|
async function runRemote(args){
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {createHash, randomBytes} from 'node:crypto';
|
|
2
|
+
import {lstat,readFile} from 'node:fs/promises';
|
|
3
|
+
import {createRequire} from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {inspectTrustedInstallation,privilegedEpochInstallFiles} from './privileged-install-trust-r89.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const binary=path.join(root,'bin','deadbyte-epoch-lock-r85.node');
|
|
10
|
+
const metadata=path.join(root,'docs','R85-EPOCH-LOCK-BINARY.json');
|
|
11
|
+
const require=createRequire(import.meta.url);
|
|
12
|
+
const hex64=/^[0-9a-f]{64}$/;
|
|
13
|
+
let addon=null;
|
|
14
|
+
|
|
15
|
+
async function loadVerifiedAddon(){
|
|
16
|
+
if(process.platform!=='win32'||process.arch!=='x64')throw new Error('epoch held-handle lock requires Windows x64');
|
|
17
|
+
await inspectTrustedInstallation(privilegedEpochInstallFiles(root,process.execPath));
|
|
18
|
+
const meta=JSON.parse(await readFile(metadata,'utf8'));
|
|
19
|
+
if(meta.schema!=='deadbyte.r85-epoch-lock-binary.v1'||meta.platform!=='win32-x64'||
|
|
20
|
+
meta.addon!=='bin/deadbyte-epoch-lock-r85.node'||meta.source!=='native/deadbyte-epoch-lock-r85.cpp'||
|
|
21
|
+
meta.node_api_version>Number(process.versions.napi)||!hex64.test(meta.addon_sha256)||!hex64.test(meta.source_sha256))
|
|
22
|
+
throw new Error('epoch held-handle binary metadata invalid');
|
|
23
|
+
const [st,bytes,sourceBytes]=await Promise.all([lstat(binary),readFile(binary),readFile(path.join(root,meta.source))]);
|
|
24
|
+
if(!st.isFile()||st.isSymbolicLink()||
|
|
25
|
+
createHash('sha256').update(bytes).digest('hex')!==meta.addon_sha256||
|
|
26
|
+
createHash('sha256').update(sourceBytes).digest('hex')!==meta.source_sha256)
|
|
27
|
+
throw new Error('epoch held-handle binary/source hash mismatch');
|
|
28
|
+
addon??=require(binary);
|
|
29
|
+
if(typeof addon.acquire!=='function'||typeof addon.release!=='function')
|
|
30
|
+
throw new Error('epoch held-handle exports invalid');
|
|
31
|
+
return addon;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function acquireHeldEpochLock(file,bytes){
|
|
35
|
+
if(!Buffer.isBuffer(bytes)||bytes.length<2||bytes.length>16384||typeof file!=='string'||!path.isAbsolute(file))
|
|
36
|
+
throw new Error('epoch held-handle lock arguments invalid');
|
|
37
|
+
const native=await loadVerifiedAddon();
|
|
38
|
+
const handle=native.acquire(file,bytes,randomBytes(16).toString('hex'));
|
|
39
|
+
if(handle===null)return null;
|
|
40
|
+
let released=false;
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
async release(){
|
|
43
|
+
if(released)throw new Error('epoch held-handle lock already released');
|
|
44
|
+
native.release(handle);
|
|
45
|
+
released=true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|