deadbyte-mcp 0.15.46 → 0.15.48

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 (98) hide show
  1. package/MANIFEST.SHA256 +97 -45
  2. package/PROVENANCE.json +22 -11
  3. package/README.txt +18 -18
  4. package/RELEASE.json +378 -369
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bin/deadbyte-epoch-lock-r85.node +0 -0
  8. package/bin/deadbyte-kernel-lock-r74.exe +0 -0
  9. package/bridge/package.json +1 -1
  10. package/controller/deadbyte-process-policy.json +7 -7
  11. package/docs/R73-CAPABILITY-GAP-MATRIX.json +17 -0
  12. package/docs/R73-EXECUTION-CONTRACT.md +11 -0
  13. package/docs/R73-IMPLEMENTATION-CHANGELOG.md +12 -0
  14. package/docs/R73-REMAINING-DEFECTS.md +9 -0
  15. package/docs/R74-KERNEL-LOCK-BINARY.json +11 -0
  16. package/docs/R75-RECOVERY-HANDOFF.md +16 -0
  17. package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
  18. package/native/deadbyte-epoch-lock-r85.cpp +241 -0
  19. package/native/deadbyte-kernel-lock-r74.cpp +65 -0
  20. package/package.json +1 -1
  21. package/scripts/build-epoch-lock-r85.ps1 +34 -0
  22. package/scripts/build-kernel-lock-r74.ps1 +28 -0
  23. package/scripts/deploy-r48.mjs +3 -1
  24. package/scripts/gate-windows-r72.ps1 +2 -2
  25. package/scripts/npm-prepublish-contract-r74.mjs +113 -0
  26. package/scripts/npm-release-r48.mjs +24 -4
  27. package/scripts/npm-release-readiness-r108.mjs +134 -0
  28. package/scripts/npm-release-readiness-r74.mjs +46 -0
  29. package/scripts/r73-atomic-benchmark.mjs +39 -0
  30. package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
  31. package/scripts/release-manifest.mjs +1 -1
  32. package/scripts/verify-r70-full-regression.ps1 +12 -2
  33. package/src/activation-authority-proof-r75.mjs +67 -0
  34. package/src/activation-continuity-engine-r65.mjs +17 -4
  35. package/src/activation-preflight-r65.mjs +13 -0
  36. package/src/atomic-mutation.mjs +99 -9
  37. package/src/authority-continuity-r65.mjs +265 -21
  38. package/src/deadbyte-cli.mjs +55 -18
  39. package/src/epoch-lock-r85.mjs +48 -0
  40. package/src/foreground-marker-cas-win32.mjs +284 -0
  41. package/src/foreground-process-identity.mjs +3 -3
  42. package/src/kernel-lock-r74.mjs +125 -0
  43. package/src/privileged-install-trust-r89.mjs +196 -0
  44. package/src/remote-session-guardian.mjs +9 -7
  45. package/src/runtime-update.mjs +5 -22
  46. package/src/version.mjs +1 -1
  47. package/src/windows-powershell-environment.mjs +24 -0
  48. package/test/atomic-mutation.test.mjs +68 -2
  49. package/test/cli-entrypoint.test.mjs +1 -1
  50. package/test/commander-schema-differential-r48.test.mjs +1 -1
  51. package/test/helpers/r75-authority-proof.mjs +9 -0
  52. package/test/r100-posix-owned-lock-race.test.mjs +72 -0
  53. package/test/r101-acl-probe-environment.test.mjs +51 -0
  54. package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
  55. package/test/r107-updater-powershell-path.test.mjs +77 -0
  56. package/test/r33-closeout-regression.test.mjs +2 -1
  57. package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
  58. package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
  59. package/test/r56-rollback-launcher-restore.test.mjs +2 -2
  60. package/test/r57-core-startup-race.test.mjs +2 -2
  61. package/test/r58-concurrent-recovery.test.mjs +10 -7
  62. package/test/r58-stale-owner.test.mjs +5 -6
  63. package/test/r59-tool-surface-compat.test.mjs +1 -1
  64. package/test/r65-activation-orchestration.test.mjs +2 -1
  65. package/test/r65-activation-preflight.test.mjs +7 -6
  66. package/test/r65-authority-epoch-recovery.test.mjs +3 -1
  67. package/test/r65-authority-restored-safe.test.mjs +2 -1
  68. package/test/r65-deploy-preflight.test.mjs +13 -14
  69. package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
  70. package/test/r65-legacy-authority-migration.test.mjs +2 -1
  71. package/test/r67-npm-cross-volume.test.mjs +9 -2
  72. package/test/r71-full-verifier-artifact.test.mjs +21 -15
  73. package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
  74. package/test/r71-release-identity.test.mjs +3 -1
  75. package/test/r73-package-boundary.test.mjs +22 -0
  76. package/test/r74-kernel-lock.test.mjs +156 -0
  77. package/test/r74-prepublish-contract.test.mjs +89 -0
  78. package/test/r75-activation-authority-proof.test.mjs +46 -0
  79. package/test/r75-authority-evidence.test.mjs +77 -0
  80. package/test/r76-pinned-marker-cas.test.mjs +78 -0
  81. package/test/r77-epoch-grant-cas.test.mjs +51 -0
  82. package/test/r78-parent-junction-cas.test.mjs +62 -0
  83. package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
  84. package/test/r79-windows-fsync-regression.test.mjs +45 -0
  85. package/test/r80-durable-epoch-commit.test.mjs +76 -0
  86. package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
  87. package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
  88. package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
  89. package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
  90. package/test/r85-epoch-repeat-append.test.mjs +30 -0
  91. package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
  92. package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
  93. package/test/r88-native-disposition-failure.test.mjs +35 -0
  94. package/test/r89-privileged-install-boundary.test.mjs +123 -0
  95. package/test/r92-acl-ace-applicability.test.mjs +45 -0
  96. package/test/r94-win32-propagation-enum.test.mjs +51 -0
  97. package/test/r99-privileged-import-closure.test.mjs +45 -0
  98. package/test/remote-session-cli.test.mjs +69 -9
@@ -0,0 +1,110 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {spawn} from 'node:child_process';
5
+ import {mkdtemp,mkdir,writeFile,readdir,access,rm} from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import {fileURLToPath,pathToFileURL} from 'node:url';
9
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
10
+
11
+ const repo=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
12
+ const exists=file=>access(file).then(()=>true,()=>false);
13
+ const awaitChildExit=(child,timeoutMs)=>new Promise((resolve,reject)=>{
14
+ if(child.exitCode!==null||child.signalCode!==null)return resolve({code:child.exitCode,signal:child.signalCode});
15
+ const timer=setTimeout(()=>reject(new Error('R87 child exit timeout')),timeoutMs);
16
+ child.once('exit',(code,signal)=>{clearTimeout(timer);resolve({code,signal});});
17
+ });
18
+
19
+ test('R87 Windows: exact signed orphan revoke recovers after owner crash, never restores grant',
20
+ {skip:process.platform!=='win32',timeout:40000},async()=>{
21
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r87-crashed-revoke-'));
22
+ let child;
23
+ try{
24
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
25
+ await mkdir(runtimeDir);await mkdir(trust);
26
+ const keys=generateKeyPairSync('ed25519');
27
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
28
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
29
+ const grantInstanceNonce=randomBytes(32).toString('hex');
30
+ const grantBytes=Buffer.from(JSON.stringify({instance_nonce:grantInstanceNonce}));
31
+ const store=createAuthorityEpochStore({runtimeDir});
32
+ const prior=await store.append({plane:'process',action:'grant',grantBytes,grantInstanceNonce});
33
+ const script=`import {createAuthorityEpochStore} from ${JSON.stringify(pathToFileURL(path.join(repo,'src','authority-continuity-r65.mjs')).href)};
34
+ const store=createAuthorityEpochStore({runtimeDir:${JSON.stringify(runtimeDir)},
35
+ onDurabilityStage:async stage=>{if(stage==='event_file_synced'){
36
+ process.stdout.write('R87_ORPHAN_READY\\n');
37
+ await new Promise(()=>setInterval(()=>{},1000));
38
+ }}});
39
+ await store.append({plane:'process',action:'user_revoke',
40
+ grantBytes:Buffer.from(${JSON.stringify(grantBytes.toString('base64'))},'base64'),
41
+ grantInstanceNonce:${JSON.stringify(grantInstanceNonce)}});`;
42
+ child=spawn(process.execPath,['--input-type=module','-e',script],
43
+ {env:{...process.env,NODE_TEST_CONTEXT:'child-v8'},windowsHide:true,stdio:['ignore','pipe','ignore']});
44
+ await new Promise((resolve,reject)=>{
45
+ let output='';
46
+ const timer=setTimeout(()=>{child.kill();reject(new Error('R87 orphan-ready child timeout'));},12000);
47
+ child.once('error',error=>{clearTimeout(timer);reject(error);});
48
+ child.once('exit',()=>{clearTimeout(timer);reject(new Error('R87 child exited before orphan-ready'));});
49
+ child.stdout.on('data',chunk=>{output=(output+String(chunk)).slice(-100);if(output.includes('R87_ORPHAN_READY')){
50
+ clearTimeout(timer);resolve();
51
+ }});
52
+ });
53
+ const dir=path.join(runtimeDir,'authority-epochs','process');
54
+ const names=await readdir(path.join(dir,'events'));
55
+ const orphan=names.find(name=>/^0000000000000002-[0-9a-f]{64}\.json$/.test(name));
56
+ assert.ok(orphan,'one immutable signed second event must remain');
57
+ const expectedOrphanSha256=orphan.slice(17,81);
58
+ assert.equal(await exists(path.join(dir,'.lock')),true,'live owner must hold its lock');
59
+ await assert.rejects(store.reconcileOrphanedRevocation({plane:'process',
60
+ expectedHeadSha256:prior.event_sha256,expectedOrphanSha256,operatorConfirmed:true}),
61
+ /native pinned file I\/O failed closed/);
62
+ assert.equal(await exists(path.join(dir,'.lock')),true,'live-owner lock must not be removed');
63
+ child.kill('SIGKILL');
64
+ const outcome=await awaitChildExit(child,12000);
65
+ assert.notEqual(outcome.code,0,'R87 child must not complete revoke after forced death');
66
+ assert.equal(await exists(path.join(dir,'.lock')),true,'crash leaves an owner-identity-bearing lock');
67
+ const before=await store.verifyGrant({plane:'process',grantBytes,grantInstanceNonce});
68
+ assert.equal(before.trusted,false);
69
+ assert.match(before.reason,/head replay or incomplete write/);
70
+ await assert.rejects(store.reconcileOrphanedRevocation({plane:'process',
71
+ expectedHeadSha256:prior.event_sha256,expectedOrphanSha256:'f'.repeat(64),operatorConfirmed:true}),
72
+ /orphan identity mismatch/);
73
+ assert.equal(await exists(path.join(dir,'.lock')),true,
74
+ 'wrong evidence must not reclaim a dead-owner lock before validation');
75
+ const recoveryScript=`import {createAuthorityEpochStore} from ${JSON.stringify(pathToFileURL(path.join(repo,'src','authority-continuity-r65.mjs')).href)};
76
+ const store=createAuthorityEpochStore({runtimeDir:${JSON.stringify(runtimeDir)}});
77
+ try {
78
+ const result=await store.reconcileOrphanedRevocation({plane:'process',
79
+ expectedHeadSha256:${JSON.stringify(prior.event_sha256)},
80
+ expectedOrphanSha256:${JSON.stringify(expectedOrphanSha256)},operatorConfirmed:true});
81
+ if(result.status!=='REVOKED_ONLY_RECONCILED')throw new Error('unexpected result');
82
+ } catch(error) { process.stderr.write(String(error.message).slice(0,500)); process.exitCode=2; }`;
83
+ const recover=()=>new Promise((resolve,reject)=>{
84
+ const worker=spawn(process.execPath,['--input-type=module','-e',recoveryScript],
85
+ {windowsHide:true,stdio:['ignore','ignore','pipe']});
86
+ let stderr='';
87
+ worker.stderr.on('data',chunk=>{stderr=(stderr+String(chunk)).slice(-1000);});
88
+ const timer=setTimeout(()=>{worker.kill();reject(new Error('R87 recovery child timeout'));},12000);
89
+ worker.once('error',error=>{clearTimeout(timer);reject(error);});
90
+ worker.once('exit',code=>{clearTimeout(timer);resolve({code,stderr});});
91
+ });
92
+ const contenders=await Promise.all([recover(),recover()]);
93
+ assert.equal(contenders.filter(item=>item.code===0).length,1,
94
+ `exactly one process may publish the recovered head: ${JSON.stringify(contenders)}`);
95
+ assert.equal(contenders.filter(item=>item.code===2).length,1);
96
+ assert.equal((await store.verifyGrant({plane:'process',grantBytes,grantInstanceNonce})).trusted,false);
97
+ assert.equal(await exists(path.join(dir,'.lock')),false);
98
+ await assert.rejects(store.reconcileOrphanedRevocation({plane:'process',
99
+ expectedHeadSha256:prior.event_sha256,expectedOrphanSha256,operatorConfirmed:true}));
100
+ assert.equal((await store.readHead({plane:'process'})).action,'user_revoke');
101
+ console.log('R87_CRASHED_ORPHAN_REVOKE_SINGLE_WINNER_WITHOUT_GRANT_RESTORE');
102
+ }finally{
103
+ try{
104
+ if(child){
105
+ if(child.exitCode===null&&child.signalCode===null)child.kill('SIGKILL');
106
+ await awaitChildExit(child,5000);
107
+ }
108
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
109
+ }
110
+ });
@@ -0,0 +1,35 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,chmod,access,rm} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import {acquireHeldEpochLock} from '../src/epoch-lock-r85.mjs';
7
+
8
+ const exists=async file=>access(file).then(()=>true,()=>false);
9
+
10
+ test('Windows denied native disposition retains owned lock and permits explicit retry',
11
+ {skip:process.platform!=='win32'},async()=>{
12
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r88-disposition-'));
13
+ const lock=path.join(root,'epoch.lock');
14
+ let held;
15
+ try{
16
+ held=await acquireHeldEpochLock(lock,Buffer.from('{}'));
17
+ assert.ok(held,'isolated lock must be acquired');
18
+ await chmod(lock,0o444);
19
+ await assert.rejects(held.release(),/epoch native lock disposition failed closed/);
20
+ assert.equal(await exists(lock),true,'failed disposition must retain the lock');
21
+ await chmod(lock,0o666);
22
+ await held.release();
23
+ held=null;
24
+ assert.equal(await exists(lock),false,'retry must dispose the exact held lock');
25
+ }finally{
26
+ try{
27
+ if(await exists(lock)){
28
+ await chmod(lock,0o666);
29
+ if(held)await held.release();
30
+ }
31
+ }finally{
32
+ await rm(root,{recursive:true,force:true,maxRetries:4,retryDelay:100});
33
+ }
34
+ }
35
+ });
@@ -0,0 +1,123 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {access,mkdtemp,writeFile,rm} from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import {
8
+ requiredAclPathChain,evaluatePrivilegedAclSnapshot,inspectTrustedInstallation,privilegedEpochInstallFiles
9
+ } from '../src/privileged-install-trust-r89.mjs';
10
+
11
+ const sys='S-1-5-18',admin='S-1-5-32-544',users='S-1-5-11';
12
+ const leaf='C:\\Program Files\\Deadbyte\\bin\\deadbyte-epoch-lock-r85.node';
13
+ const js='C:\\Program Files\\Deadbyte\\src\\epoch-lock-r85.mjs';
14
+ const files=[leaf,js];
15
+ function protectedFixture(){
16
+ return requiredAclPathChain(files).map(p=>({path:p,owner:admin,
17
+ directory:![leaf.toLowerCase(),js.toLowerCase()].includes(p.toLowerCase()),reparse:false,
18
+ aces:[{sid:sys,type:'Allow',mask:0x001f01ff,inheritance:0,propagation:0},
19
+ {sid:users,type:'Allow',mask:0x001200a9,inheritance:0,propagation:0}]}));
20
+ }
21
+ function mutate(records,predicate,fn){
22
+ const result=structuredClone(records);
23
+ const rec=result.find(predicate);
24
+ assert.ok(rec);
25
+ fn(rec);
26
+ return result;
27
+ }
28
+ test('R89 protected installation ACL snapshot grants only OS trusted writers',()=>{
29
+ assert.deepEqual(evaluatePrivilegedAclSnapshot(protectedFixture(),files),
30
+ {trusted:true,reason:'PROTECTED_INSTALLATION_ACL_SNAPSHOT'});
31
+ });
32
+ test('R89 Authenticated Users Modify on addon is rejected',()=>{
33
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()===leaf.toLowerCase(),
34
+ r=>r.aces.push({sid:users,type:'Allow',mask:0x000301bf,inheritance:0,propagation:0}));
35
+ const outcome=evaluatePrivilegedAclSnapshot(records,files);
36
+ assert.equal(outcome.trusted,false);assert.equal(outcome.reason,'UNTRUSTED_WRITE_ACE');
37
+ });
38
+ test('R89 Authenticated Users delete-child on any ancestor is rejected',()=>{
39
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()==='c:\\program files',
40
+ r=>r.aces.push({sid:users,type:'Allow',mask:0x40,inheritance:0,propagation:0}));
41
+ assert.equal(evaluatePrivilegedAclSnapshot(records,files).reason,'UNTRUSTED_WRITE_ACE');
42
+ });
43
+ test('R89 inherited CreateFiles alone on root is not mistaken for replacing existing child',()=>{
44
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()==='c:\\',
45
+ r=>r.aces.push({sid:users,type:'Allow',mask:0x02,inheritance:0,propagation:0}));
46
+ assert.equal(evaluatePrivilegedAclSnapshot(records,files).trusted,true);
47
+ });
48
+ test('R89 unsigned/untrusted owner cannot assert immutable installation',()=>{
49
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()===leaf.toLowerCase(),
50
+ r=>r.owner=users);
51
+ assert.equal(evaluatePrivilegedAclSnapshot(records,files).reason,'UNTRUSTED_OWNER_OR_REPARSE');
52
+ });
53
+ test('R89 reparse ancestor, incomplete, duplicate, unknown ACL fail closed',()=>{
54
+ const ok=protectedFixture();
55
+ const s=mutate(ok,r=>r.path.toLowerCase()==='c:\\program files',r=>r.reparse=true);
56
+ assert.equal(evaluatePrivilegedAclSnapshot(s,files).trusted,false);
57
+ assert.equal(evaluatePrivilegedAclSnapshot(ok.slice(1),files).reason,'INCOMPLETE_ACL_SNAPSHOT');
58
+ const dup=[...ok.slice(1),ok[1]];
59
+ assert.equal(evaluatePrivilegedAclSnapshot(dup,files).reason,'DUPLICATE_ACL_PATH');
60
+ const unknown=mutate(ok,r=>r.path.toLowerCase()===leaf.toLowerCase(),
61
+ r=>r.aces.push({sid:users,type:'Allow',mask:-1,inheritance:0,propagation:0}));
62
+ assert.equal(evaluatePrivilegedAclSnapshot(unknown,files).reason,'UNKNOWN_ACL_RULE');
63
+ });
64
+ test('R89 relative and UNC paths cannot supply a trusted native image',()=>{
65
+ assert.throws(()=>requiredAclPathChain(['.\\bin\\addon.node']));
66
+ assert.throws(()=>requiredAclPathChain(['\\\\server\\share\\addon.node']));
67
+ });
68
+
69
+ test('R99 Windows development candidate denies native acquire unless installation is protected',
70
+ {skip:process.platform!=='win32'||process.arch!=='x64'},async()=>{
71
+ const repo=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
72
+ const target=privilegedEpochInstallFiles(repo,process.execPath);
73
+ let protectedSlot=false;
74
+ try {
75
+ const decision=await inspectTrustedInstallation(target);
76
+ assert.equal(decision.trusted,true);
77
+ protectedSlot=true;
78
+ } catch(error) {
79
+ assert.equal(error.code,'EPOCH_NATIVE_SLOT_UNTRUSTED',`unknown ACL error: ${error.message}`);
80
+ }
81
+ const {createAuthorityEpochStore}=await import('../src/authority-continuity-r65.mjs');
82
+ const {acquireHeldEpochLock}=await import('../src/epoch-lock-r85.mjs');
83
+ const fixture=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r99-epoch-test-'));
84
+ try {
85
+ const lock=path.join(fixture,'.lock');
86
+ if(!protectedSlot) {
87
+ await assert.rejects(acquireHeldEpochLock(lock,Buffer.from('{}')),error=>{
88
+ assert.equal(error.code,'EPOCH_NATIVE_SLOT_UNTRUSTED');return true;
89
+ });
90
+ await assert.rejects(access(lock),{code:'ENOENT'});
91
+ console.log('R99_INSECURE_WIN32_INSTALL_REFUSED');
92
+ } else {
93
+ const held=await acquireHeldEpochLock(lock,Buffer.from('{}'));
94
+ assert.ok(held,'protected addon must acquire native file handle');
95
+ await held.release();
96
+ await assert.rejects(access(lock),{code:'ENOENT'});
97
+ console.log('R99_PROTECTED_WIN32_INSTALL_HELD_LOCK_RELEASE_PASS');
98
+ }
99
+ const untrusted=path.join(fixture,'untrusted-addon.node');
100
+ await writeFile(untrusted,'not-an-executable');
101
+ await assert.rejects(inspectTrustedInstallation([untrusted]),error=>{
102
+ assert.equal(error.code,'EPOCH_NATIVE_SLOT_UNTRUSTED');return true;
103
+ });
104
+ } finally {await rm(fixture,{recursive:true,force:true,maxRetries:8,retryDelay:120});}
105
+ });
106
+
107
+ // R90: unexpanded Windows generic ACE bits must not accidentally pass R89's
108
+ // file-specific write mask. Old R89 synthetic snapshots did not cover these.
109
+ test('R90 unexpanded GENERIC_WRITE and GENERIC_ALL deny a privileged native image',()=>{
110
+ for(const [kind,mask] of [['GENERIC_WRITE',0x40000000],['GENERIC_ALL',0x10000000],
111
+ ['MAXIMUM_ALLOWED',0x02000000]]){
112
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()===leaf.toLowerCase(),
113
+ r=>r.aces.push({sid:users,type:'Allow',mask,inheritance:0,propagation:0}));
114
+ const d=evaluatePrivilegedAclSnapshot(records,files);
115
+ assert.equal(d.trusted,false,kind);
116
+ assert.equal(d.reason,'UNTRUSTED_WRITE_ACE',kind);
117
+ }
118
+ });
119
+ test('R90 generic read and execute alone do not grant write',()=>{
120
+ const records=mutate(protectedFixture(),r=>r.path.toLowerCase()===leaf.toLowerCase(),
121
+ r=>r.aces.push({sid:users,type:'Allow',mask:(0x80000000|0x20000000)>>>0,inheritance:0,propagation:0}));
122
+ assert.equal(evaluatePrivilegedAclSnapshot(records,files).trusted,true);
123
+ });
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {evaluatePrivilegedAclSnapshot,requiredAclPathChain} from '../src/privileged-install-trust-r89.mjs';
4
+
5
+ const admins='S-1-5-32-544', system='S-1-5-18', users='S-1-5-11', creatorOwner='S-1-3-0';
6
+ const leaf='C:\\Program Files\\Deadbyte\\bin\\deadbyte-epoch-lock-r85.node';
7
+ const files=[leaf];
8
+ const key=s=>s.toLowerCase();
9
+ function snapshot(){return requiredAclPathChain(files).map(p=>({
10
+ path:p,owner:admins,reparse:false,directory:key(p)!==key(leaf),
11
+ aces:[{sid:system,type:'Allow',mask:0x001f01ff,inheritance:0,propagation:0},
12
+ {sid:users,type:'Allow',mask:0x001200a9,inheritance:0,propagation:0}]
13
+ }));}
14
+ function add(path,ace){const a=snapshot();const entry=a.find(x=>key(x.path)===key(path));assert.ok(entry);entry.aces.push(ace);return a;}
15
+ const ownerAce=(inheritance,propagation)=>({sid:creatorOwner,type:'Allow',mask:0x10000000,inheritance,propagation});
16
+ const userAce=(inheritance,propagation)=>({sid:users,type:'Allow',mask:0xe0010000,inheritance,propagation});
17
+
18
+ test('R92: scoped, inheritance-only root Authenticated Users cannot mutate root itself',()=>{
19
+ assert.equal(evaluatePrivilegedAclSnapshot(add('C:\\',userAce(3,2)),files).trusted,true);
20
+ });
21
+ test('R92: CREATOR OWNER inheritance-only on Program Files is not effective on this directory',()=>{
22
+ assert.equal(evaluatePrivilegedAclSnapshot(add('C:\\Program Files',ownerAce(3,2)),files).trusted,true);
23
+ console.log('R92_INHERIT_ONLY_SCOPE_NEGATIVE_CONTROL_PASS');
24
+ });
25
+ test('R92: effective Authenticated Users generic write on root remains blocked',()=>{
26
+ const d=evaluatePrivilegedAclSnapshot(add('C:\\',userAce(3,0)),files);
27
+ assert.equal(d.trusted,false);assert.equal(d.reason,'UNTRUSTED_WRITE_ACE');
28
+ });
29
+ test('R92: CREATOR OWNER effective full-control on Program Files is not whitelisted',()=>{
30
+ const d=evaluatePrivilegedAclSnapshot(add('C:\\Program Files',ownerAce(3,0)),files);
31
+ assert.equal(d.reason,'UNTRUSTED_WRITE_ACE');
32
+ });
33
+ test('R92: missing ACE scope fails closed',()=>{
34
+ const a=add('C:\\Program Files',ownerAce(3,2));delete a[0].aces[0].propagation;
35
+ assert.equal(evaluatePrivilegedAclSnapshot(a,files).reason,'UNKNOWN_ACL_SCOPE');
36
+ });
37
+ test('R92: impossible inherit-only scope fails closed',()=>{
38
+ assert.equal(evaluatePrivilegedAclSnapshot(add('C:\\Program Files',ownerAce(0,2)),files).reason,'UNKNOWN_ACL_SCOPE');
39
+ });
40
+ test('R92: file-leaf inheritance-only ACE cannot become an unchecked write grant',()=>{
41
+ assert.equal(evaluatePrivilegedAclSnapshot(add(leaf,ownerAce(3,2)),files).reason,'UNKNOWN_ACL_SCOPE');
42
+ });
43
+ test('R92: unrecognized propagation bits fail closed',()=>{
44
+ assert.equal(evaluatePrivilegedAclSnapshot(add('C:\\Program Files',ownerAce(3,8)),files).reason,'UNKNOWN_ACL_SCOPE');
45
+ });
@@ -0,0 +1,51 @@
1
+ // R94 exact .NET System.Security.AccessControl.PropagationFlags numerical oracle.
2
+ // Critically: NoPropagateInherit=1; InheritOnly=2 (R92 interchanged them).
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+ import {evaluatePrivilegedAclSnapshot,requiredAclPathChain} from '../src/privileged-install-trust-r89.mjs';
6
+ const admins='S-1-5-32-544', system='S-1-5-18', users='S-1-5-11', creator='S-1-3-0';
7
+ const file='C:\\Program Files\\Deadbyte\\bin\\deadbyte-epoch-lock-r85.node';
8
+ const files=[file];
9
+ const base=()=>requiredAclPathChain(files).map(p=>({path:p,owner:admins,directory:p.toLowerCase()!==file.toLowerCase(),reparse:false,aces:[
10
+ {sid:system,type:'Allow',mask:0x001f01ff,inheritance:0,propagation:0},
11
+ {sid:users,type:'Allow',mask:0x001200a9,inheritance:0,propagation:0}
12
+ ]}));
13
+ function withAce(where,ace){const s=base(),r=s.find(v=>v.path.toLowerCase()===where.toLowerCase());assert.ok(r);r.aces.push(ace);return s;}
14
+ const user=(mask,propagation,inheritance=3)=>({sid:users,type:'Allow',mask,propagation,inheritance});
15
+ const creatorOwner=(propagation)=>({sid:creator,type:'Allow',mask:0x10000000,propagation,inheritance:3});
16
+ const judge=(records)=>evaluatePrivilegedAclSnapshot(records,files);
17
+
18
+ test('R94 Windows .NET enum: InheritOnly=2 root write grant is NOT effective on root',()=>{
19
+ const s=withAce('C:\\',user(0xe0010000,2));
20
+ assert.deepEqual(judge(s),{trusted:true,reason:'PROTECTED_INSTALLATION_ACL_SNAPSHOT'});
21
+ });
22
+ test('R94 Windows .NET enum: NoPropagateInherit=1 does NOT suppress effective root write',()=>{
23
+ const s=withAce('C:\\',user(0xe0010000,1));
24
+ assert.equal(judge(s).reason,'UNTRUSTED_WRITE_ACE');
25
+ });
26
+ test('R94 exact R93 C drive: create-subdirectory on root + inheritance-only Authenticated Users deny-none',()=>{
27
+ const s=withAce('C:\\',user(4,0,0));
28
+ s.find(r=>r.path.toLowerCase()==='c:\\').aces.push(user(0xe0010000,2));
29
+ assert.equal(judge(s).trusted,true);
30
+ console.log('R94_R93_ROOT_ACE_FALSE_POSITIVE_FIXED');
31
+ });
32
+ test('R94 exact R93 Program Files: inheritance-only CREATOR OWNER does not grant effective modify',()=>{
33
+ const s=withAce('C:\\Program Files',creatorOwner(2));
34
+ assert.equal(judge(s).trusted,true);
35
+ });
36
+ test('R94 effective CREATOR OWNER GENERIC_ALL on protected directory must still be rejected',()=>{
37
+ const s=withAce('C:\\Program Files',creatorOwner(1));
38
+ assert.equal(judge(s).reason,'UNTRUSTED_WRITE_ACE');
39
+ });
40
+ test('R94 unscoped effective Authenticated Users delete-child on protected parent rejects',()=>{
41
+ const s=withAce('C:\\Program Files',user(0x40,0,0));
42
+ assert.equal(judge(s).reason,'UNTRUSTED_WRITE_ACE');
43
+ });
44
+ test('R94 malformed leaf inherit-only ACE remains fail-closed',()=>{
45
+ const s=withAce(file,user(0x40000000,2));
46
+ assert.equal(judge(s).reason,'UNKNOWN_ACL_SCOPE');
47
+ });
48
+ test('R94 combined InheritOnly|NoPropagateInherit=3 never applies on the inspected directory',()=>{
49
+ const s=withAce('C:\\Program Files',creatorOwner(3));
50
+ assert.equal(judge(s).trusted,true);
51
+ });
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import path from 'node:path';
4
+ import {readFile} from 'node:fs/promises';
5
+ import {fileURLToPath} from 'node:url';
6
+ import {privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
7
+ import {windowsPowerShellEnvironment as isolatedEnvironment} from '../src/windows-powershell-environment.mjs';
8
+ import {windowsPowerShellEnvironment as updaterEnvironment} from '../src/runtime-update.mjs';
9
+
10
+ const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
11
+
12
+ test('R99 privileged installation inventory closes over security-critical local imports',async()=>{
13
+ const inventory=new Set(privilegedEpochInstallFiles(root,process.execPath).map(file=>path.resolve(file).toLowerCase()));
14
+ const frontier=[
15
+ path.join(root,'src','epoch-lock-r85.mjs'),
16
+ path.join(root,'src','authority-continuity-r65.mjs'),
17
+ path.join(root,'src','foreground-process-identity.mjs'),
18
+ path.join(root,'src','foreground-marker-cas-win32.mjs')
19
+ ];
20
+ const seen=new Set();
21
+ while(frontier.length){
22
+ const file=frontier.pop();
23
+ const identity=path.resolve(file).toLowerCase();
24
+ if(seen.has(identity))continue;
25
+ seen.add(identity);
26
+ assert.ok(inventory.has(identity),`privileged dependency omitted: ${path.relative(root,file)}`);
27
+ const source=await readFile(file,'utf8');
28
+ for(const match of source.matchAll(/\bfrom\s*['"](\.\.?\/[^'"]+)['"]/g)){
29
+ frontier.push(path.resolve(path.dirname(file),match[1]));
30
+ }
31
+ for(const match of source.matchAll(/\bimport\s*\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g)){
32
+ frontier.push(path.resolve(path.dirname(file),match[1]));
33
+ }
34
+ for(const match of source.matchAll(/\bimport\s*['"](\.\.?\/[^'"]+)['"]/g)){
35
+ frontier.push(path.resolve(path.dirname(file),match[1]));
36
+ }
37
+ }
38
+ });
39
+
40
+ test('R99 isolated PowerShell environment preserves existing child semantics',()=>{
41
+ const base={SystemRoot:'C:\\Windows',USERPROFILE:'C:\\Users\\fixture',
42
+ ProgramFiles:'C:\\Program Files',PSModulePath:'C:\\CustomModules;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules',
43
+ DEADBYTE_STATE_ROOT:'D:\\isolated-state'};
44
+ assert.deepEqual(isolatedEnvironment(base),updaterEnvironment(base));
45
+ });
@@ -33,7 +33,7 @@ async function fixture({failCommand='',statusOnline=false}={}){
33
33
  const spawnedPid=path.join(dir,'spawned.pid');
34
34
  await mkdir(bin,{recursive:true});
35
35
  const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
36
- const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { $runtime=Join-Path $env:DEADBYTE_STATE_ROOT 'runtime'; New-Item -ItemType Directory -Path $runtime -Force | Out-Null; $failure=@{ schema='deadbyte.controller-start-failure.v1'; command=$Command; failed_at_utc='2026-09-26T00:00:00.000Z'; message='fixture controller evidence detail' } | ConvertTo-Json -Compress; [IO.File]::WriteAllText((Join-Path $runtime 'controller-start-failure.json'),$failure,[Text.Encoding]::UTF8); Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { if ($env:DEADBYTE_REMOTE_TEST_SPAWN_CHILD -eq '1') { $node=(Get-Command node.exe).Source; $spawned=Start-Process -FilePath $node -ArgumentList @('-e','setTimeout(()=>process.exit(0),10000)') -PassThru -WindowStyle Hidden -RedirectStandardOutput $env:DEADBYTE_REMOTE_TEST_SPAWN_STDOUT -RedirectStandardError $env:DEADBYTE_REMOTE_TEST_SPAWN_STDERR; if ($env:DEADBYTE_REMOTE_TEST_SPAWN_PID) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_SPAWN_PID,[string]$spawned.Id,[Text.Encoding]::UTF8) } }; if ($env:DEADBYTE_REMOTE_TEST_STARTED) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_STARTED,'1',[Text.Encoding]::UTF8) }; Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1' -or ($env:DEADBYTE_REMOTE_TEST_STARTED -and (Test-Path -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED))) { Write-Host 'DEADBYTE MCP = ONLINE'; Write-Host 'Bridge PID : 1001 [verified]'; Write-Host 'Cloud PID : 1002 [verified]'; Write-Host 'Local ready : True'; Write-Host 'Loop supervisor: RUNNING [verified]' } else { Write-Host 'DEADBYTE MCP = OFFLINE' } }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'desktopstatus' { Write-Host 'Desktop plane : DISARMED [disarmed]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { if ($env:DEADBYTE_REMOTE_TEST_STARTED) { Remove-Item -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED -Force -ErrorAction SilentlyContinue }; Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
36
+ const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -in ($env:DEADBYTE_REMOTE_TEST_FAIL -split ',')) { $runtime=Join-Path $env:DEADBYTE_STATE_ROOT 'runtime'; New-Item -ItemType Directory -Path $runtime -Force | Out-Null; $failure=@{ schema='deadbyte.controller-start-failure.v1'; command=$Command; failed_at_utc='2026-09-26T00:00:00.000Z'; message='fixture controller evidence detail' } | ConvertTo-Json -Compress; [IO.File]::WriteAllText((Join-Path $runtime 'controller-start-failure.json'),$failure,[Text.Encoding]::UTF8); Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { if ($env:DEADBYTE_REMOTE_TEST_SPAWN_CHILD -eq '1') { $node=(Get-Command node.exe).Source; $spawned=Start-Process -FilePath $node -ArgumentList @('-e','setTimeout(()=>process.exit(0),10000)') -PassThru -WindowStyle Hidden -RedirectStandardOutput $env:DEADBYTE_REMOTE_TEST_SPAWN_STDOUT -RedirectStandardError $env:DEADBYTE_REMOTE_TEST_SPAWN_STDERR; if ($env:DEADBYTE_REMOTE_TEST_SPAWN_PID) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_SPAWN_PID,[string]$spawned.Id,[Text.Encoding]::UTF8) } }; if ($env:DEADBYTE_REMOTE_TEST_STARTED) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_STARTED,'1',[Text.Encoding]::UTF8) }; Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1' -or ($env:DEADBYTE_REMOTE_TEST_STARTED -and (Test-Path -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED))) { Write-Host 'DEADBYTE MCP = ONLINE'; Write-Host 'Bridge PID : 1001 [verified]'; Write-Host 'Cloud PID : 1002 [verified]'; Write-Host 'Local ready : True'; Write-Host 'Loop supervisor: RUNNING [verified]' } else { Write-Host 'DEADBYTE MCP = OFFLINE' } }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'desktopstatus' { Write-Host 'Desktop plane : DISARMED [disarmed]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { if ($env:DEADBYTE_REMOTE_TEST_STARTED) { Remove-Item -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED -Force -ErrorAction SilentlyContinue }; Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
37
37
  const structured=`function PlaneArmed([string]$name,[bool]$default) { $base=Join-Path $env:DEADBYTE_STATE_ROOT ('runtime\\' + $name); if (Test-Path -LiteralPath ($base + '.disarmed')) { return $false }; if (Test-Path -LiteralPath ($base + '.armed')) { return $true }; return $default }
38
38
  if ($Command -eq 'status' -and $Json) { $names=@('process','coding','autonomous','desktop','host'); $authority=@{}; foreach($name in $names){ $initial=($name -in @('process','coding','autonomous')) -or (($name -eq 'host') -and ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1')) -or (($name -eq 'desktop') -and ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1')); $armed=PlaneArmed $name $initial; if($name -eq 'host' -and (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE_ALWAYS -eq '1') -or (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE -eq '1') -and (Test-Path -LiteralPath (Join-Path $env:DEADBYTE_STATE_ROOT 'runtime\\host.disarm-attempt'))))){$armed=$false}; $authority[$name]=@{ configured=$true; armed=$armed; reason=$(if($armed){'standing-grant'}else{'disarmed'}) } }; @{schema='deadbyte.controller-observation.v1';state='ONLINE';local_ready=$true;core_root='fixture';bridge=@{alive=$true;reason='verified'};core=@{alive=$true;reason='verified'};cloud=@{alive=$true;reason='verified'};supervisor=@{running=$true};authority=$authority} | ConvertTo-Json -Depth 5 -Compress; exit 0 }
39
39
  if ($Command -eq 'machinetest' -and $env:DEADBYTE_REMOTE_PROBE_MODE) { $hostArmed=PlaneArmed 'host' ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1'); $desktopArmed=PlaneArmed 'desktop' ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1'); $wantHost=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('host','full'); $wantDesktop=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('desktop','full'); if($hostArmed -ne $wantHost -or $desktopArmed -ne $wantDesktop){ Write-Error 'remote enforcement probe mismatch'; exit 9 } }
@@ -559,7 +559,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
559
559
  }finally{await safeRm(fx.dir);}
560
560
  });
561
561
 
562
- winTest('bare remote recovers a stale crashed session before arming a new one',async()=>{
562
+ winTest('bare remote retains an owner-unverified stale marker without starting or revoking',async()=>{
563
563
  const fx=await fixture();
564
564
  let child=null;
565
565
  try{
@@ -568,21 +568,39 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
568
568
  await writeFile(path.join(runtime,'remote-session.json'),JSON.stringify({schema:'deadbyte.remote-session.v1',pid:2147483000,started_at_utc:'2026-01-01T00:00:00.000Z'}),'utf8');
569
569
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
570
570
  const output=await collect(child);
571
- const ready=await waitFor(fx.trace,commands=>commands.length>=10&&commands[9]==='start');
572
- assert.deepEqual(ready.slice(0,8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
573
- assert.equal(ready[8],'status');
574
- assert.equal(ready[9],'start');
575
- assert.match(output.stdout,/\[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
576
- child.stdin.end();
577
571
  const ended=await boundedCompletion(output.closed);
578
572
  assert.equal(ended.timeout,undefined);
579
- assert.equal(ended.code,0,output.stderr);
573
+ assert.notEqual(ended.code,0);
574
+ assert.match(output.stderr,/ownership unverified.*marker retained/i);
575
+ assert.deepEqual(await traceCommands(fx.trace),[]);
576
+ assert.equal(JSON.parse(await readFile(path.join(runtime,'remote-session.json'),'utf8')).pid,2147483000);
580
577
  }finally{
581
578
  if(child?.exitCode===null) child.kill();
582
579
  await safeRm(fx.dir);
583
580
  }
584
581
  });
585
582
 
583
+ winTest('PID reuse cannot authorize removal of a marker with a different process identity',async()=>{
584
+ const fx=await fixture();
585
+ try{
586
+ const runtime=path.join(fx.state,'runtime');
587
+ await mkdir(runtime,{recursive:true});
588
+ const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:0,
589
+ session_nonce:'f'.repeat(64),owner_identity:{schema:'deadbyte.foreground-process.v1',pid:process.pid,
590
+ parent_pid:0,creation_utc:'2026-01-01T00:00:00Z',executable_path:process.execPath,
591
+ command_line_sha256:'a'.repeat(64)}};
592
+ await writeFile(path.join(runtime,'remote-session.json'),JSON.stringify(marker),'utf8');
593
+ const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
594
+ const output=await collect(child);
595
+ const ended=await boundedCompletion(output.closed);
596
+ assert.equal(ended.timeout,undefined);
597
+ assert.notEqual(ended.code,0);
598
+ assert.match(output.stderr,/owner identity.*marker retained/i);
599
+ assert.deepEqual(await traceCommands(fx.trace),[]);
600
+ assert.equal(JSON.parse(await readFile(path.join(runtime,'remote-session.json'),'utf8')).session_nonce,marker.session_nonce);
601
+ }finally{await safeRm(fx.dir);}
602
+ });
603
+
586
604
  winTest('remote warning lines use deterministic ASCII fallback on piped Windows output',async()=>{
587
605
  const fx=await fixture();
588
606
  let child=null;
@@ -596,6 +614,9 @@ winTest('remote warning lines use deterministic ASCII fallback on piped Windows
596
614
  const ended=await boundedCompletion(output.closed);
597
615
  assert.equal(ended.timeout,undefined,'remote session did not exit after warning fixture');
598
616
  assert.match(output.stderr,/^\[WARN\] remote session marker cleanup:/m);
617
+ assert.notEqual(ended.code,0,'unverified ownership marker must not report clean success');
618
+ assert.match(output.stderr,/ownership marker retained/i);
619
+ assert.equal(await readFile(markerPath,'utf8'),'{invalid-json\n');
599
620
  assert.doesNotMatch(output.stderr,/\p{Extended_Pictographic}/u);
600
621
  }finally{
601
622
  if(child?.exitCode===null) child.kill();
@@ -603,6 +624,29 @@ winTest('remote warning lines use deterministic ASCII fallback on piped Windows
603
624
  }
604
625
  });
605
626
 
627
+ winTest('remote cleanup never removes a marker with changed ownership identity',async()=>{
628
+ const fx=await fixture();
629
+ let child=null;
630
+ try{
631
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
632
+ const output=await collect(child);
633
+ await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4,{timeoutMs:fixtureTimeoutMs});
634
+ const markerPath=path.join(fx.state,'runtime','remote-session.json');
635
+ const marker=JSON.parse(await readFile(markerPath,'utf8'));
636
+ marker.session_nonce='f'.repeat(64);
637
+ await writeFile(markerPath,JSON.stringify(marker)+'\n','utf8');
638
+ child.stdin.end();
639
+ const ended=await boundedCompletion(output.closed);
640
+ assert.equal(ended.timeout,undefined);
641
+ assert.notEqual(ended.code,0);
642
+ assert.match(output.stderr,/ownership marker changed or missing/);
643
+ assert.equal(JSON.parse(await readFile(markerPath,'utf8')).session_nonce,'f'.repeat(64));
644
+ }finally{
645
+ if(child?.exitCode===null)child.kill();
646
+ await safeRm(fx.dir);
647
+ }
648
+ });
649
+
606
650
  winTest('R58 concurrent cold remote claims have one winner and no loser cleanup',async()=>{
607
651
  const fx=await fixture({statusOnline:true});const children=[],outputs=[];
608
652
  try{
@@ -726,6 +770,22 @@ winTest('bare remote surfaces bounded controller-start-failure evidence instead
726
770
  }finally{await safeRm(fx.dir);}
727
771
  });
728
772
 
773
+ winTest('failed startup retains primary and cleanup failure plus ownership marker',async()=>{
774
+ const fx=await fixture({failCommand:'start,disarmautonomous'});
775
+ try{
776
+ const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
777
+ const output=await collect(child);
778
+ const ended=await boundedCompletion(output.closed);
779
+ assert.equal(ended.timeout,undefined,'failed startup did not terminate');
780
+ assert.notEqual(ended.code,0);
781
+ assert.match(output.stderr,/remote activation failed/i);
782
+ assert.match(output.stderr,/controller-start-failure/i);
783
+ assert.match(output.stderr,/cleanup failed.*disarmautonomous/i);
784
+ const retained=await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8').then(()=>true,()=>false);
785
+ assert.equal(retained,true);
786
+ }finally{await safeRm(fx.dir);}
787
+ });
788
+
729
789
 
730
790
  winTest('remote --trace --filter selects presentation without dropping journaled events',async()=>{
731
791
  const fx=await fixture();