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.
Files changed (112) hide show
  1. package/MANIFEST.SHA256 +111 -48
  2. package/PROVENANCE.json +1 -1
  3. package/README.txt +18 -18
  4. package/RELEASE.json +3 -3
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bench/r121-epoch-readhead.mjs +54 -0
  8. package/bench/r124-epoch-predecessor.mjs +53 -0
  9. package/bin/deadbyte-epoch-lock-r85.node +0 -0
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
  12. package/docs/R128-RELEASE-CANDIDATE.md +26 -0
  13. package/docs/R75-RECOVERY-HANDOFF.md +16 -0
  14. package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
  15. package/native/deadbyte-epoch-lock-r85.cpp +241 -0
  16. package/package.json +1 -1
  17. package/scripts/build-epoch-lock-r85.ps1 +34 -0
  18. package/scripts/deploy-r48.mjs +3 -1
  19. package/scripts/npm-release-r48.mjs +18 -4
  20. package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
  21. package/scripts/release-manifest.mjs +1 -1
  22. package/scripts/verify-authority-epoch-history.mjs +38 -0
  23. package/scripts/verify-r70-full-regression.ps1 +12 -2
  24. package/src/activation-authority-proof-r75.mjs +67 -0
  25. package/src/activation-continuity-engine-r65.mjs +17 -10
  26. package/src/activation-preflight-r65.mjs +13 -0
  27. package/src/agent-eval-r42.mjs +30 -0
  28. package/src/authority-continuity-r65.mjs +442 -29
  29. package/src/autonomous-local-supervisor.mjs +77 -47
  30. package/src/deadbyte-cli.mjs +73 -46
  31. package/src/epoch-lock-r85.mjs +48 -0
  32. package/src/foreground-marker-cas-win32.mjs +284 -0
  33. package/src/foreground-process-identity.mjs +3 -3
  34. package/src/privileged-install-trust-r89.mjs +196 -0
  35. package/src/remote-live-log.mjs +15 -8
  36. package/src/remote-log-decode-r60.mjs +65 -0
  37. package/src/remote-session-guardian.mjs +13 -12
  38. package/src/runtime-update.mjs +5 -22
  39. package/src/version.mjs +1 -1
  40. package/src/windows-powershell-environment.mjs +24 -0
  41. package/test/autonomous-local-supervisor.test.mjs +1 -1
  42. package/test/cli-entrypoint.test.mjs +1 -1
  43. package/test/harness2-eval-context-isolation.test.mjs +90 -0
  44. package/test/helpers/r75-authority-proof.mjs +9 -0
  45. package/test/r100-posix-owned-lock-race.test.mjs +72 -0
  46. package/test/r101-acl-probe-environment.test.mjs +51 -0
  47. package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
  48. package/test/r107-updater-powershell-path.test.mjs +77 -0
  49. package/test/r108-remote-powershell-path.test.mjs +59 -0
  50. package/test/r110-supervisor-powershell-path.test.mjs +28 -0
  51. package/test/r111-supervisor-marker-cas.test.mjs +116 -0
  52. package/test/r112-supervisor-claim-cas.test.mjs +63 -0
  53. package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
  54. package/test/r114-supervisor-absence-proof.test.mjs +45 -0
  55. package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
  56. package/test/r116-authority-crash-matrix.test.mjs +83 -0
  57. package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
  58. package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
  59. package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
  60. package/test/r120-authority-history-audit.test.mjs +103 -0
  61. package/test/r121-signed-transition-invariant.test.mjs +132 -0
  62. package/test/r122-epoch-bounded-read.test.mjs +58 -0
  63. package/test/r123-recovery-bounded-read.test.mjs +64 -0
  64. package/test/r124-live-log-integrity.test.mjs +66 -0
  65. package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
  66. package/test/r124-signed-predecessor-shape.test.mjs +66 -0
  67. package/test/r125-restore-binding-types.test.mjs +63 -0
  68. package/test/r126-restore-policy-envelope.test.mjs +89 -0
  69. package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
  70. package/test/r33-closeout-regression.test.mjs +2 -1
  71. package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
  72. package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
  73. package/test/r56-rollback-launcher-restore.test.mjs +2 -2
  74. package/test/r57-core-startup-race.test.mjs +2 -2
  75. package/test/r58-concurrent-recovery.test.mjs +10 -7
  76. package/test/r58-stale-owner.test.mjs +5 -6
  77. package/test/r59-tool-surface-compat.test.mjs +1 -1
  78. package/test/r65-activation-orchestration.test.mjs +2 -1
  79. package/test/r65-activation-preflight.test.mjs +7 -6
  80. package/test/r65-authority-epoch-recovery.test.mjs +3 -1
  81. package/test/r65-authority-epoch-store.test.mjs +9 -6
  82. package/test/r65-authority-restored-safe.test.mjs +2 -1
  83. package/test/r65-deploy-preflight.test.mjs +13 -14
  84. package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
  85. package/test/r65-legacy-authority-migration.test.mjs +2 -1
  86. package/test/r67-npm-cross-volume.test.mjs +9 -2
  87. package/test/r71-full-verifier-artifact.test.mjs +21 -15
  88. package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
  89. package/test/r71-release-identity.test.mjs +3 -1
  90. package/test/r74-kernel-lock.test.mjs +12 -6
  91. package/test/r74-prepublish-contract.test.mjs +3 -1
  92. package/test/r75-activation-authority-proof.test.mjs +46 -0
  93. package/test/r75-authority-evidence.test.mjs +77 -0
  94. package/test/r76-pinned-marker-cas.test.mjs +78 -0
  95. package/test/r77-epoch-grant-cas.test.mjs +51 -0
  96. package/test/r78-parent-junction-cas.test.mjs +62 -0
  97. package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
  98. package/test/r79-windows-fsync-regression.test.mjs +45 -0
  99. package/test/r80-durable-epoch-commit.test.mjs +76 -0
  100. package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
  101. package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
  102. package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
  103. package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
  104. package/test/r85-epoch-repeat-append.test.mjs +30 -0
  105. package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
  106. package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
  107. package/test/r88-native-disposition-failure.test.mjs +35 -0
  108. package/test/r89-privileged-install-boundary.test.mjs +123 -0
  109. package/test/r92-acl-ace-applicability.test.mjs +45 -0
  110. package/test/r94-win32-propagation-enum.test.mjs +51 -0
  111. package/test/r99-privileged-import-closure.test.mjs +45 -0
  112. 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, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
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 systemRoot=process.env.SystemRoot || process.env.WINDIR;
37
- if(systemRoot){
38
- const candidate=path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe');
39
- try { await access(candidate); return candidate; } catch {}
40
- }
41
- return 'powershell.exe';
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
- try { raw=await readFile(markerPath); }
70
- catch(error){ if(error?.code==='ENOENT') return {exists:false}; throw error; }
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
- if(!stored.valid) return {status:'unverified',live:false,reason:'marker_invalid',marker:stored.marker,marker_sha256:stored.raw_sha256};
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) return {status:'stale',live:false,reason:'process_unobservable',marker:stored.marker,marker_sha256:stored.raw_sha256};
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,marker_sha256:stored.raw_sha256};
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:'stale',live:false,reason:'identity_mismatch',marker:stored.marker,observation:observed,marker_sha256:stored.raw_sha256};
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,marker_sha256:stored.raw_sha256};
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
- await mkdir(path.dirname(markerPath),{recursive:true});
110
- try {
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
- const marker=await currentMarker();
125
- const before=await readMarker(markerPath);
126
- if(!before.exists||before.raw_sha256!==prior.marker_sha256) throw new Error('supervisor ownership changed during takeover');
127
- const temp=path.join(path.dirname(markerPath),`.${path.basename(markerPath)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
128
- try {
129
- await writeFile(temp,`${JSON.stringify(marker)}\n`,{flag:'wx',mode:0o600});
130
- const check=await readMarker(markerPath);
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
- const check=await readMarker(markerPath);
151
- if(!check.exists||check.raw_sha256!==observed.marker_sha256) throw new Error('supervisor ownership changed before release');
152
- await rm(markerPath,{force:false});
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}).catch(()=>{});
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 !stopped&&supervisor.running; },
238
- async scanNow(){ if(stopped) throw new Error('local supervisor host stopped'); return supervisor.scanNow(); },
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
- stopped=true;
242
- await supervisor.stop();
243
- await releaseSupervisor({markerPath,instanceNonce:claim.instance_nonce});
244
- return {status:'stopped'};
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
  }
@@ -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, rm, stat, writeFile } from 'node:fs/promises';
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, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
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 systemRoot=process.env.SystemRoot||process.env.WINDIR;if(!systemRoot) throw new Error('SystemRoot/WINDIR missing');
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(probeMode?{...process.env,DEADBYTE_REMOTE_PROBE_MODE:probeMode}:process.env)};
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,offset){
312
- let info;try{info=await stat(file);}catch(error){if(error?.code==='ENOENT') return {offset:0,text:''};throw error;}
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
- try{offsets.set(file,(await stat(file)).size);}catch{}
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):0;
356
+ const prior=activeOffsets.has(file)?activeOffsets.get(file):null;
371
357
  const next=await readAppend(file,prior);
372
- activeOffsets.set(file,next.offset);
373
- if(next.reset)partialLines.delete(file);
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(`remote session already active pid=${ownerPid}`);
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
- writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Stale remote session pid=${ownerPid}; revoking authority before restart.\n`,displayMode);
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
- await rm(markerPath,{force:true});
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 current=null;try{current=await readRemoteSessionMarker(ctx.root);}catch(error){writeRemote(process.stderr,`${remoteTag('warn',displayMode)} remote session marker cleanup: ${String(error?.message||error)}\n`,displayMode);return;}
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'&&current.owner_pid===marker.owner_pid&&current.session_nonce===marker.session_nonce;
515
- if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
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(error){
535
- await cleanupRemoteSession(ctx,{displayMode});
536
- await releaseRemoteSession(ctx,marker,{displayMode});
537
- throw error;
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
- }finally{
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
- await releaseRemoteSession(ctx,marker,{displayMode});
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
+ }