deadbyte-mcp 0.15.47 → 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 (79) hide show
  1. package/MANIFEST.SHA256 +78 -41
  2. package/PROVENANCE.json +22 -11
  3. package/README.txt +11 -11
  4. package/RELEASE.json +378 -368
  5. package/TEST-MATRIX.json +1 -1
  6. package/bin/deadbyte-epoch-lock-r85.node +0 -0
  7. package/controller/deadbyte-process-policy.json +4 -4
  8. package/docs/R75-RECOVERY-HANDOFF.md +16 -0
  9. package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
  10. package/native/deadbyte-epoch-lock-r85.cpp +241 -0
  11. package/package.json +1 -1
  12. package/scripts/build-epoch-lock-r85.ps1 +34 -0
  13. package/scripts/deploy-r48.mjs +3 -1
  14. package/scripts/npm-release-r48.mjs +22 -4
  15. package/scripts/npm-release-readiness-r108.mjs +134 -0
  16. package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
  17. package/scripts/release-manifest.mjs +1 -1
  18. package/scripts/verify-r70-full-regression.ps1 +12 -2
  19. package/src/activation-authority-proof-r75.mjs +67 -0
  20. package/src/activation-continuity-engine-r65.mjs +17 -4
  21. package/src/activation-preflight-r65.mjs +13 -0
  22. package/src/authority-continuity-r65.mjs +265 -21
  23. package/src/deadbyte-cli.mjs +55 -18
  24. package/src/epoch-lock-r85.mjs +48 -0
  25. package/src/foreground-marker-cas-win32.mjs +284 -0
  26. package/src/foreground-process-identity.mjs +3 -3
  27. package/src/privileged-install-trust-r89.mjs +196 -0
  28. package/src/remote-session-guardian.mjs +9 -7
  29. package/src/runtime-update.mjs +5 -22
  30. package/src/version.mjs +1 -1
  31. package/src/windows-powershell-environment.mjs +24 -0
  32. package/test/cli-entrypoint.test.mjs +1 -1
  33. package/test/helpers/r75-authority-proof.mjs +9 -0
  34. package/test/r100-posix-owned-lock-race.test.mjs +72 -0
  35. package/test/r101-acl-probe-environment.test.mjs +51 -0
  36. package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
  37. package/test/r107-updater-powershell-path.test.mjs +77 -0
  38. package/test/r33-closeout-regression.test.mjs +2 -1
  39. package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
  40. package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
  41. package/test/r56-rollback-launcher-restore.test.mjs +2 -2
  42. package/test/r57-core-startup-race.test.mjs +2 -2
  43. package/test/r58-concurrent-recovery.test.mjs +10 -7
  44. package/test/r58-stale-owner.test.mjs +5 -6
  45. package/test/r59-tool-surface-compat.test.mjs +1 -1
  46. package/test/r65-activation-orchestration.test.mjs +2 -1
  47. package/test/r65-activation-preflight.test.mjs +7 -6
  48. package/test/r65-authority-epoch-recovery.test.mjs +3 -1
  49. package/test/r65-authority-restored-safe.test.mjs +2 -1
  50. package/test/r65-deploy-preflight.test.mjs +13 -14
  51. package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
  52. package/test/r65-legacy-authority-migration.test.mjs +2 -1
  53. package/test/r67-npm-cross-volume.test.mjs +9 -2
  54. package/test/r71-full-verifier-artifact.test.mjs +21 -15
  55. package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
  56. package/test/r71-release-identity.test.mjs +3 -1
  57. package/test/r74-kernel-lock.test.mjs +12 -6
  58. package/test/r74-prepublish-contract.test.mjs +1 -1
  59. package/test/r75-activation-authority-proof.test.mjs +46 -0
  60. package/test/r75-authority-evidence.test.mjs +77 -0
  61. package/test/r76-pinned-marker-cas.test.mjs +78 -0
  62. package/test/r77-epoch-grant-cas.test.mjs +51 -0
  63. package/test/r78-parent-junction-cas.test.mjs +62 -0
  64. package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
  65. package/test/r79-windows-fsync-regression.test.mjs +45 -0
  66. package/test/r80-durable-epoch-commit.test.mjs +76 -0
  67. package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
  68. package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
  69. package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
  70. package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
  71. package/test/r85-epoch-repeat-append.test.mjs +30 -0
  72. package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
  73. package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
  74. package/test/r88-native-disposition-failure.test.mjs +35 -0
  75. package/test/r89-privileged-install-boundary.test.mjs +123 -0
  76. package/test/r92-acl-ace-applicability.test.mjs +45 -0
  77. package/test/r94-win32-propagation-enum.test.mjs +51 -0
  78. package/test/r99-privileged-import-closure.test.mjs +45 -0
  79. package/test/remote-session-cli.test.mjs +69 -9
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, generateKeyPairSync } from 'node:crypto';
4
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { createAuthorityEpochStore } from '../src/authority-continuity-r65.mjs';
8
+
9
+ const hash = bytes => createHash('sha256').update(bytes).digest('hex');
10
+
11
+ test('R79 Windows explicit recovery syncs signed orphan through writable pinned handle without altering event bytes', async () => {
12
+ const home = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r79-fsync-portability-'));
13
+ try {
14
+ const runtime = path.join(home, 'runtime');
15
+ const trust = path.join(home, 'trust');
16
+ await mkdir(runtime);
17
+ await mkdir(trust);
18
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519');
19
+ await writeFile(path.join(trust, 'ed25519-public.pem'), publicKey.export({ type: 'spki', format: 'pem' }), { flag: 'wx' });
20
+ await writeFile(path.join(trust, 'ed25519-private.pem'), privateKey.export({ type: 'pkcs8', format: 'pem' }), { flag: 'wx' });
21
+ const store = createAuthorityEpochStore({ runtimeDir: runtime });
22
+ const nonce = 'a'.repeat(64);
23
+ const grantBytes = Buffer.from(JSON.stringify({ instance_nonce: nonce }));
24
+ const previous = await store.append({ plane: 'coding', action: 'grant', grantBytes, grantInstanceNonce: nonce });
25
+ const headPath = path.join(runtime, 'authority-epochs', 'coding', 'head.json');
26
+ const priorHeadBytes = await readFile(headPath);
27
+ const orphan = await store.append({ plane: 'coding', action: 'transaction_revoke', grantBytes,
28
+ grantInstanceNonce: nonce, updateId: 'r79-fsync-regression', expectedHeadSha256: previous.event_sha256 });
29
+ const orphanPath = path.join(runtime, 'authority-epochs', 'coding', 'events', orphan.event_file);
30
+ const originalEventBytes = await readFile(orphanPath);
31
+ await writeFile(headPath, priorHeadBytes);
32
+ await assert.rejects(store.readHead({ plane: 'coding' }), /replay or incomplete write/);
33
+ const result = await store.reconcileOrphanedRevocation({ plane: 'coding',
34
+ expectedHeadSha256: previous.event_sha256, expectedOrphanSha256: orphan.event_sha256,
35
+ operatorConfirmed: true });
36
+ assert.equal(result.status, 'REVOKED_ONLY_RECONCILED');
37
+ assert.equal(result.standing_grant_authorized, false);
38
+ assert.deepEqual(await readFile(orphanPath), originalEventBytes, 'fsync must not change signed event bytes');
39
+ assert.equal(hash(originalEventBytes), orphan.event_sha256);
40
+ assert.equal((await store.readHead({ plane: 'coding' })).event_sha256, orphan.event_sha256);
41
+ assert.equal((await store.verifyGrant({ plane: 'coding', grantBytes, grantInstanceNonce: nonce })).trusted, false);
42
+ } finally {
43
+ await rm(home, { recursive: true, force: true });
44
+ }
45
+ });
@@ -0,0 +1,76 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createAuthorityEpochStore,authorityEpochDurabilityAssessment} from '../src/authority-continuity-r65.mjs';
8
+
9
+ const nonce=()=>randomBytes(32).toString('hex');
10
+ const data=(n)=>Buffer.from(JSON.stringify({instance_nonce:n,fixture:'R80 isolated'}));
11
+ async function isolated(fn){
12
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r80-epoch-'));
13
+ try{
14
+ const runtimeDir=path.join(root,'runtime');
15
+ const trust=path.join(root,'trust');
16
+ await mkdir(runtimeDir);await mkdir(trust);
17
+ const keys=generateKeyPairSync('ed25519');
18
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
19
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
20
+ return await fn({runtimeDir,root});
21
+ }finally{await rm(root,{recursive:true,force:true});}
22
+ }
23
+
24
+ test('R80 durability capability explicitly distinguishes file flush from power-loss proof',()=>{
25
+ const d=authorityEpochDurabilityAssessment();
26
+ assert.equal(d.event_file,'FLUSH_REQUIRED');
27
+ assert.equal(d.head_temp_file,'FLUSH_REQUIRED');
28
+ assert.equal(d.directory_metadata,process.platform==='win32'?'UNVERIFIED_ON_WINDOWS':'FSYNC_REQUIRED');
29
+ assert.equal(d.power_loss,'NOT_VERIFIED_BY_TESTS');
30
+ });
31
+
32
+ test('R80 signed grant event is flushed before head temp; head temp before rename',()=>isolated(async({runtimeDir})=>{
33
+ const seen=[];
34
+ const store=createAuthorityEpochStore({runtimeDir,onDurabilityStage:phase=>seen.push(phase)});
35
+ const n=nonce(),bytes=data(n);
36
+ const head=await store.append({plane:'process',action:'grant',grantBytes:bytes,grantInstanceNonce:n});
37
+ assert.deepEqual(seen,['event_created','event_written','event_file_synced','event_directory_barrier',
38
+ 'head_temp_created','head_temp_written','head_temp_file_synced','head_renamed','head_directory_barrier']);
39
+ assert.equal((await store.verifyGrant({plane:'process',grantBytes:bytes,grantInstanceNonce:n})).trusted,true);
40
+ const event=path.join(runtimeDir,'authority-epochs','process','events',head.event_file);
41
+ assert.ok((await readFile(event)).length>64);
42
+ }));
43
+
44
+ const phases=['event_created','event_written','event_file_synced','event_directory_barrier',
45
+ 'head_temp_created','head_temp_written','head_temp_file_synced','head_renamed','head_directory_barrier'];
46
+ for(const phase of phases){
47
+ test(`R80 simulated interruption at ${phase} preserves fail-closed or explicit post-rename commit state`,()=>isolated(async({runtimeDir})=>{
48
+ const n=nonce(),bytes=data(n);
49
+ const store=createAuthorityEpochStore({runtimeDir,onDurabilityStage:at=>{
50
+ if(at===phase)throw new Error(`R80_SIMULATED_INTERRUPTION:${at}`);
51
+ }});
52
+ await assert.rejects(store.append({plane:'process',action:'grant',grantBytes:bytes,grantInstanceNonce:n}),/R80_SIMULATED_INTERRUPTION/);
53
+ const verifyStore=createAuthorityEpochStore({runtimeDir});
54
+ const result=await verifyStore.verifyGrant({plane:'process',grantBytes:bytes,grantInstanceNonce:n});
55
+ if(phase==='head_renamed'||phase==='head_directory_barrier'){
56
+ // Rename has already committed the logically visible signed head.
57
+ // An interrupted caller has an AMBIGUOUS outcome, not guaranteed rollback.
58
+ assert.equal(result.trusted,true);
59
+ }else{
60
+ assert.equal(result.trusted,false);
61
+ }
62
+ }));
63
+ }
64
+
65
+ test('R80 an interrupted user revoke never re-authorizes the old signed grant',()=>isolated(async({runtimeDir})=>{
66
+ const n=nonce(),bytes=data(n);
67
+ const normal=createAuthorityEpochStore({runtimeDir});
68
+ await normal.append({plane:'process',action:'grant',grantBytes:bytes,grantInstanceNonce:n});
69
+ const faultStore=createAuthorityEpochStore({runtimeDir,onDurabilityStage:at=>{
70
+ if(at==='event_written')throw new Error('SIMULATED_REVOKE_CRASH');
71
+ }});
72
+ await assert.rejects(faultStore.append({plane:'process',action:'user_revoke',grantBytes:bytes,grantInstanceNonce:n}),/SIMULATED_REVOKE_CRASH/);
73
+ const verified=await normal.verifyGrant({plane:'process',grantBytes:bytes,grantInstanceNonce:n});
74
+ assert.equal(verified.trusted,false);
75
+ assert.match(verified.reason,/replay|incomplete/);
76
+ }));
@@ -0,0 +1,93 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,rename,rm,access} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+
9
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const nonce=()=>randomBytes(32).toString('hex');
11
+ const lockValue=(pid,token)=>JSON.stringify({schema:'deadbyte.authority-epoch-lock.r65.v1',pid,token,created_at_utc:'2026-10-16T00:00:00.000Z',
12
+ ...(pid===2147483647?{owner_identity:{schema:'deadbyte.foreground-process.v1',pid,parent_pid:0,
13
+ creation_utc:'2026-01-01T00:00:00Z',executable_path:process.execPath,command_line_sha256:'0'.repeat(64)}}:{})});
14
+ async function isolated(fn){
15
+ const root=await mkdtemp(path.join(os.tmpdir(),'r82-lock-cas-'));
16
+ try{
17
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
18
+ await mkdir(runtimeDir);await mkdir(trust);
19
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
20
+ await writeFile(path.join(trust,'ed25519-private.pem'),privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
21
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
22
+ const epochDir=path.join(runtimeDir,'authority-epochs','process');
23
+ const lockPath=path.join(epochDir,'.lock');
24
+ return await fn({runtimeDir,epochDir,lockPath,root});
25
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:6,retryDelay:160});}
26
+ }
27
+ async function appendGrant(store){
28
+ const n=nonce(),bytes=Buffer.from(JSON.stringify({instance_nonce:n,fixture:'R82 isolated'}));
29
+ return store.append({plane:'process',action:'grant',grantBytes:bytes,grantInstanceNonce:n});
30
+ }
31
+ const exists=file=>access(file).then(()=>true,()=>false);
32
+
33
+ // This intentionally tests in the actual R82 runtime; no source-copy shim bypasses the CAS.
34
+ test('R82 Windows: stale lock replacement before native disposition is retained', {skip:process.platform!=='win32'},()=>isolated(async({runtimeDir,epochDir,lockPath})=>{
35
+ await mkdir(epochDir,{recursive:true});
36
+ await writeFile(lockPath,lockValue(2147483647,randomBytes(16).toString('hex')));
37
+ const successor=randomBytes(16).toString('hex');
38
+ let interleaved=0;
39
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,target)=>{
40
+ if(stage!=='stale_before_cas')return;
41
+ assert.equal(target,lockPath);interleaved++;
42
+ const replacement=lockPath+'.replacement';
43
+ await writeFile(replacement,lockValue(process.pid,successor));
44
+ await rename(replacement,lockPath);
45
+ }});
46
+ await assert.rejects(()=>appendGrant(store),/authority epoch mutation locked.*stale owner changed/);
47
+ assert.equal(interleaved,1);
48
+ assert.equal(JSON.parse(await readFile(lockPath,'utf8')).token,successor);
49
+ assert.equal(await exists(path.join(epochDir,'head.json')),false);
50
+ }));
51
+
52
+ test('R85 Windows: held lock refuses replacement before owned release', {skip:process.platform!=='win32'},()=>isolated(async({runtimeDir,lockPath})=>{
53
+ let interleaved=0;
54
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,target)=>{
55
+ if(stage!=='release_before_cas')return;
56
+ assert.equal(target,lockPath);interleaved++;
57
+ const replacement=lockPath+'.replacement';
58
+ await assert.rejects(()=>rename(lockPath,replacement),error=>['EACCES','EPERM','EBUSY'].includes(error?.code));
59
+ }});
60
+ assert.equal((await appendGrant(store)).action,'grant');
61
+ assert.equal(interleaved,1);
62
+ assert.equal(await exists(lockPath),false);
63
+ const head=await store.readHead({plane:'process'});
64
+ assert.equal(head.action,'grant');
65
+ }));
66
+
67
+ test('R82 Windows: absent recovery race disposes only stale token then writes and releases new lock', {skip:process.platform!=='win32'},()=>isolated(async({runtimeDir,epochDir,lockPath})=>{
68
+ await mkdir(epochDir,{recursive:true});
69
+ await writeFile(lockPath,lockValue(2147483647,randomBytes(16).toString('hex')));
70
+ const store=createAuthorityEpochStore({runtimeDir});
71
+ assert.equal((await appendGrant(store)).action,'grant');
72
+ assert.equal(await exists(lockPath),false);
73
+ }));
74
+
75
+ test('R82 unchanged live PID stays locked on all platforms',()=>isolated(async({runtimeDir,epochDir,lockPath})=>{
76
+ await mkdir(epochDir,{recursive:true});
77
+ const successor=randomBytes(16).toString('hex');
78
+ await writeFile(lockPath,lockValue(process.pid,successor));
79
+ await assert.rejects(()=>appendGrant(createAuthorityEpochStore({runtimeDir})),/authority epoch mutation locked/);
80
+ assert.equal(JSON.parse(await readFile(lockPath,'utf8')).token,successor);
81
+ }));
82
+
83
+ test('R82 normal epoch append releases lock and preserves signed head',()=>isolated(async({runtimeDir,lockPath})=>{
84
+ const store=createAuthorityEpochStore({runtimeDir});
85
+ const head=await appendGrant(store);
86
+ assert.equal(head.action,'grant');
87
+ assert.equal((await store.readHead({plane:'process'})).event_sha256,head.event_sha256);
88
+ assert.equal(await exists(lockPath),false);
89
+ }));
90
+
91
+ test('R82 source does not silently enable a production lock interleave hook',()=>{
92
+ assert.throws(()=>createAuthorityEpochStore({runtimeDir:os.tmpdir(),onLockDispositionStage:'not-function'}),/lock observer/);
93
+ });
@@ -0,0 +1,92 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,rename,rm,access} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+ import {readPinnedEpochLockSnapshot,deletePinnedRemoteMarker} from '../src/foreground-marker-cas-win32.mjs';
9
+
10
+ const nonce=()=>randomBytes(32).toString('hex');
11
+ const lockValue=(pid,token)=>JSON.stringify({schema:'deadbyte.authority-epoch-lock.r65.v1',pid,token,created_at_utc:'2026-10-16T00:00:00.000Z',
12
+ ...(pid===2147483647?{owner_identity:{schema:'deadbyte.foreground-process.v1',pid,parent_pid:0,
13
+ creation_utc:'2026-01-01T00:00:00Z',executable_path:process.execPath,command_line_sha256:'0'.repeat(64)}}:{})});
14
+ const exists=file=>access(file).then(()=>true,()=>false);
15
+ async function fixture(fn){
16
+ const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r83-cas-'));
17
+ try{
18
+ const runtimeDir=path.join(base,'runtime'),trust=path.join(base,'trust');
19
+ await mkdir(runtimeDir);await mkdir(trust);
20
+ const keys=generateKeyPairSync('ed25519');
21
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
22
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
23
+ const epochDir=path.join(runtimeDir,'authority-epochs','process'),lockPath=path.join(epochDir,'.lock');
24
+ return await fn({runtimeDir,epochDir,lockPath});
25
+ }finally{await rm(base,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
26
+ }
27
+ async function grant(store){
28
+ const n=nonce();return store.append({plane:'process',action:'grant',grantBytes:Buffer.from(JSON.stringify({instance_nonce:n,r83:true})),grantInstanceNonce:n});
29
+ }
30
+ async function replaceSameBytes(lockPath){
31
+ const original=await readFile(lockPath);
32
+ const replacement=lockPath+'.r83-'+randomBytes(6).toString('hex');
33
+ await writeFile(replacement,original,{flag:'wx'});
34
+ await rename(replacement,lockPath);
35
+ assert.deepEqual(await readFile(lockPath),original,'replacement must have byte-identical content');
36
+ }
37
+
38
+ test('R83 Windows: native snapshot binds bytes and FileIdInfo on one handle',{skip:process.platform!=='win32'},()=>fixture(async({epochDir,lockPath})=>{
39
+ await mkdir(epochDir,{recursive:true});
40
+ const bytes=Buffer.from(lockValue(process.pid,nonce()));
41
+ await writeFile(lockPath,bytes,{flag:'wx'});
42
+ const snap=readPinnedEpochLockSnapshot(lockPath);
43
+ assert.deepEqual(snap.bytes,bytes);
44
+ assert.match(snap.fileId,/^[a-f0-9]{16}:[a-f0-9]{32}$/);
45
+ assert.equal(deletePinnedRemoteMarker(lockPath,snap.sha256,{expectedFileId:snap.fileId}),'REMOVED');
46
+ assert.equal(await exists(lockPath),false);
47
+ }));
48
+
49
+ test('R83 Windows: same-byte successor survives stale lock recovery',{skip:process.platform!=='win32'},()=>fixture(async({runtimeDir,epochDir,lockPath})=>{
50
+ await mkdir(epochDir,{recursive:true});
51
+ await writeFile(lockPath,lockValue(2147483647,nonce()),{flag:'wx'});
52
+ let swapped=0;
53
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,target)=>{
54
+ if(stage==='stale_before_cas'){assert.equal(target,lockPath);swapped++;await replaceSameBytes(lockPath);}
55
+ }});
56
+ await assert.rejects(()=>grant(store),/authority epoch mutation locked.*stale owner changed: CHANGED/);
57
+ assert.equal(swapped,1);
58
+ assert.equal(await exists(lockPath),true,'successor lock must survive');
59
+ assert.equal(await exists(path.join(epochDir,'head.json')),false);
60
+ }));
61
+
62
+ test('R85 Windows: held owner lock reaches release after signed commit',{skip:process.platform!=='win32'},()=>fixture(async({runtimeDir,lockPath})=>{
63
+ let swapped=0;
64
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,target)=>{
65
+ if(stage==='release_before_cas'){
66
+ assert.equal(target,lockPath);swapped++;
67
+ }
68
+ }});
69
+ assert.equal((await grant(store)).action,'grant');
70
+ assert.equal(swapped,1);
71
+ assert.equal(await exists(lockPath),false,'owned lock must be released');
72
+ assert.equal((await store.readHead({plane:'process'})).action,'grant');
73
+ }));
74
+
75
+ test('R83 Windows: old File ID cannot delete a same-byte successor directly',{skip:process.platform!=='win32'},()=>fixture(async({epochDir,lockPath})=>{
76
+ await mkdir(epochDir,{recursive:true});
77
+ await writeFile(lockPath,lockValue(process.pid,nonce()),{flag:'wx'});
78
+ const before=readPinnedEpochLockSnapshot(lockPath);
79
+ await replaceSameBytes(lockPath);
80
+ const after=readPinnedEpochLockSnapshot(lockPath);
81
+ assert.deepEqual(before.bytes,after.bytes);
82
+ assert.equal(before.sha256,after.sha256);
83
+ assert.notEqual(before.fileId,after.fileId,'native File ID must change on a new file');
84
+ assert.equal(deletePinnedRemoteMarker(lockPath,before.sha256,{expectedFileId:before.fileId}),'CHANGED');
85
+ assert.equal(await exists(lockPath),true);
86
+ assert.equal(deletePinnedRemoteMarker(lockPath,after.sha256,{expectedFileId:after.fileId}),'REMOVED');
87
+ }));
88
+
89
+ // Only verifies no test interleave hook is accepted from a production runtime.
90
+ test('R83 production prohibits string lock interleave observer',()=>{
91
+ assert.throws(()=>createAuthorityEpochStore({runtimeDir:os.tmpdir(),onLockDispositionStage:'bad'}),/lock observer/);
92
+ });
@@ -0,0 +1,112 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,rename,rm,access} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+
9
+ test('R85 Windows: held lock cannot be replaced before event publication',
10
+ {skip:process.platform!=='win32'},async()=>{
11
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r84-create-id-'));
12
+ try{
13
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
14
+ await mkdir(runtimeDir);await mkdir(trust);
15
+ const keys=generateKeyPairSync('ed25519');
16
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
17
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
18
+ const lockPath=path.join(runtimeDir,'authority-epochs','process','.lock');
19
+ let replacements=0;
20
+ const store=createAuthorityEpochStore({runtimeDir,onDurabilityStage:async stage=>{
21
+ if(stage!=='event_created')return;
22
+ const replacement=lockPath+'.replacement';
23
+ await assert.rejects(()=>rename(lockPath,replacement),error=>['EACCES','EPERM','EBUSY'].includes(error?.code));
24
+ replacements++;
25
+ }});
26
+ const nonce=randomBytes(32).toString('hex');
27
+ assert.equal((await store.append({plane:'process',action:'grant',
28
+ grantBytes:Buffer.from(JSON.stringify({instance_nonce:nonce})),grantInstanceNonce:nonce})).action,'grant');
29
+ assert.equal(replacements,1);
30
+ assert.equal(await access(lockPath).then(()=>true,()=>false),false,'owned lock must be released');
31
+ assert.equal(await access(path.join(runtimeDir,'authority-epochs','process','head.json')).then(()=>true,()=>false),
32
+ true,'exclusive held lock permits an uncontested authority head');
33
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
34
+ });
35
+
36
+ test('R85 Windows: held lock cannot be replaced during head publication',
37
+ {skip:process.platform!=='win32'},async()=>{
38
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r84-postcheck-'));
39
+ try{
40
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
41
+ await mkdir(runtimeDir);await mkdir(trust);
42
+ const keys=generateKeyPairSync('ed25519');
43
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
44
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
45
+ const epochDir=path.join(runtimeDir,'authority-epochs','process');
46
+ const lockPath=path.join(epochDir,'.lock');
47
+ let replacements=0;
48
+ const store=createAuthorityEpochStore({runtimeDir,onDurabilityStage:async stage=>{
49
+ if(stage!=='head_temp_created')return;
50
+ const replacement=lockPath+'.replacement';
51
+ await assert.rejects(()=>rename(lockPath,replacement),error=>['EACCES','EPERM','EBUSY'].includes(error?.code));
52
+ replacements++;
53
+ }});
54
+ const nonce=randomBytes(32).toString('hex');
55
+ assert.equal((await store.append({plane:'process',action:'grant',
56
+ grantBytes:Buffer.from(JSON.stringify({instance_nonce:nonce})),grantInstanceNonce:nonce})).action,'grant');
57
+ assert.equal(replacements,1);
58
+ assert.equal(await access(path.join(epochDir,'head.json')).then(()=>true,()=>false),true,
59
+ 'authority head commits only while the original lock remains held');
60
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
61
+ });
62
+
63
+ test('R84 Windows: legacy stale lock without process birth identity is not auto-reclaimed',
64
+ {skip:process.platform!=='win32'},async()=>{
65
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r84-owner-id-'));
66
+ try{
67
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
68
+ await mkdir(runtimeDir);await mkdir(trust);
69
+ const keys=generateKeyPairSync('ed25519');
70
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
71
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
72
+ const epochDir=path.join(runtimeDir,'authority-epochs','process');
73
+ await mkdir(epochDir,{recursive:true});
74
+ const lockPath=path.join(epochDir,'.lock');
75
+ const legacy=JSON.stringify({schema:'deadbyte.authority-epoch-lock.r65.v1',pid:2147483647,
76
+ token:randomBytes(16).toString('hex'),created_at_utc:'2026-10-16T00:00:00.000Z'});
77
+ await writeFile(lockPath,legacy,{flag:'wx'});
78
+ const nonce=randomBytes(32).toString('hex');
79
+ const store=createAuthorityEpochStore({runtimeDir});
80
+ await assert.rejects(()=>store.append({plane:'process',action:'grant',
81
+ grantBytes:Buffer.from(JSON.stringify({instance_nonce:nonce})),grantInstanceNonce:nonce}),
82
+ /authority epoch mutation locked.*owner identity unverified/);
83
+ assert.equal(await readFile(lockPath,'utf8'),legacy);
84
+ assert.equal(await access(path.join(epochDir,'head.json')).then(()=>true,()=>false),false);
85
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
86
+ });
87
+
88
+ test('R84 Windows: reused live PID with different birth identity cannot surrender its lock',
89
+ {skip:process.platform!=='win32'},async()=>{
90
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r84-reused-pid-'));
91
+ try{
92
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
93
+ await mkdir(runtimeDir);await mkdir(trust);
94
+ const keys=generateKeyPairSync('ed25519');
95
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
96
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
97
+ const epochDir=path.join(runtimeDir,'authority-epochs','process');
98
+ await mkdir(epochDir,{recursive:true});
99
+ const lockPath=path.join(epochDir,'.lock');
100
+ const lock=JSON.stringify({schema:'deadbyte.authority-epoch-lock.r65.v1',pid:process.pid,
101
+ token:randomBytes(16).toString('hex'),created_at_utc:'2026-01-01T00:00:00Z',
102
+ owner_identity:{schema:'deadbyte.foreground-process.v1',pid:process.pid,parent_pid:0,
103
+ creation_utc:'2026-01-01T00:00:00Z',executable_path:process.execPath,command_line_sha256:'0'.repeat(64)}});
104
+ await writeFile(lockPath,lock,{flag:'wx'});
105
+ const nonce=randomBytes(32).toString('hex');
106
+ await assert.rejects(()=>createAuthorityEpochStore({runtimeDir}).append({plane:'process',action:'grant',
107
+ grantBytes:Buffer.from(JSON.stringify({instance_nonce:nonce})),grantInstanceNonce:nonce}),
108
+ /authority epoch mutation locked \(owner process birth changed\)/);
109
+ assert.equal(await readFile(lockPath,'utf8'),lock);
110
+ assert.equal(await access(path.join(epochDir,'head.json')).then(()=>true,()=>false),false);
111
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
112
+ });
@@ -0,0 +1,25 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp, mkdir, rename, rm} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {acquireHeldEpochLock} from '../src/epoch-lock-r85.mjs';
7
+
8
+ test('R85 released native lock permits immediate reacquire and directory rename',
9
+ {skip: process.platform !== 'win32'}, async () => {
10
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r85-handle-lifetime-'));
11
+ try {
12
+ const epoch = path.join(root, 'authority-epochs', 'process');
13
+ await mkdir(epoch, {recursive: true});
14
+ const lease = await acquireHeldEpochLock(path.join(epoch, '.lock'), Buffer.from('{}'));
15
+ assert.ok(lease);
16
+ await lease.release();
17
+ const second = await acquireHeldEpochLock(path.join(epoch, '.lock'), Buffer.from('{}'));
18
+ assert.ok(second);
19
+ await second.release();
20
+ await rename(epoch, epoch + '-moved');
21
+ await assert.rejects(() => lease.release(), /already released/);
22
+ } finally {
23
+ await rm(root, {recursive: true, force: true, maxRetries: 8, retryDelay: 150});
24
+ }
25
+ });
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,readdir,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+
9
+ test('R85 repeated append reacquires a released kernel lock', async()=>{
10
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r79-fsync-portability-'));
11
+ try{
12
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
13
+ await mkdir(runtimeDir);await mkdir(trust);
14
+ const keys=generateKeyPairSync('ed25519');
15
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
16
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
17
+ const store=createAuthorityEpochStore({runtimeDir});
18
+ const nonce='a'.repeat(64),grantBytes=Buffer.from(JSON.stringify({instance_nonce:nonce}));
19
+ const first=await store.append({plane:'coding',action:'grant',grantBytes,grantInstanceNonce:nonce});
20
+ const epoch=path.join(runtimeDir,'authority-epochs','coding');
21
+ assert.ok((await readFile(path.join(epoch,'head.json'))).length>0);
22
+ try{
23
+ await store.append({plane:'coding',action:'transaction_revoke',grantBytes,grantInstanceNonce:nonce,
24
+ updateId:'r85-repeat',expectedHeadSha256:first.event_sha256});
25
+ }catch(error){
26
+ error.message+=` [epoch-files=${JSON.stringify(await readdir(epoch))}]`;
27
+ throw error;
28
+ }
29
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
30
+ });
@@ -0,0 +1,126 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,access,rm,copyFile} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {spawn} from 'node:child_process';
10
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
11
+
12
+ const repo=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
13
+ const exists=async file=>access(file).then(()=>true,()=>false);
14
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
15
+
16
+ // This uses NODE_TEST_CONTEXT-only fault injection; it is NOT exposed by the production CLI.
17
+ test('R86 Windows: observer rejection cannot strand the owned native lock after head publication',
18
+ {skip:process.platform!=='win32'},async()=>{
19
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r86-hook-cleanup-'));
20
+ try{
21
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
22
+ await mkdir(runtimeDir);await mkdir(trust);
23
+ const keys=generateKeyPairSync('ed25519');
24
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
25
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
26
+ let injected=0;
27
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async stage=>{
28
+ if(stage==='release_before_cas'&&injected++===0)throw new Error('R86_INJECTED_RELEASE_OBSERVER_FAILURE');
29
+ }});
30
+ const grantInstanceNonce=randomBytes(32).toString('hex');
31
+ const grantBytes=Buffer.from(JSON.stringify({instance_nonce:grantInstanceNonce}));
32
+ await assert.rejects(store.append({plane:'process',action:'grant',grantBytes,grantInstanceNonce}),
33
+ /R86_INJECTED_RELEASE_OBSERVER_FAILURE/);
34
+ const epoch=path.join(runtimeDir,'authority-epochs','process');
35
+ assert.equal(await exists(path.join(epoch,'.lock')),false,'injected observer error must not strand lock');
36
+ const head=await store.readHead({plane:'process'});
37
+ assert.equal(head?.action,'grant','post-rename commit may have succeeded before observer failure');
38
+ const next=await store.append({plane:'process',action:'user_revoke',grantBytes,grantInstanceNonce});
39
+ assert.equal(next.action,'user_revoke','subsequent independent mutation must reacquire owned lock');
40
+ assert.equal(await exists(path.join(epoch,'.lock')),false);
41
+ console.log('R86_OBSERVER_FAILURE_CLEANUP_GREEN_OBSERVED');
42
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
43
+ });
44
+
45
+ test('R86 static audit: native error cleanup cannot ignore failed disposition',async()=>{
46
+ const source=await readFile(path.join(repo,'native','deadbyte-epoch-lock-r85.cpp'),'utf8');
47
+ assert.match(source,/DWORD DisposeHeldLeaf\(Lease& lease\)/);
48
+ assert.match(source,/ThrowWin32WithCleanup/);
49
+ assert.doesNotMatch(source,/void DisposeHeldLeaf\(/);
50
+ assert.match(source,/cleanup != ERROR_SUCCESS/);
51
+ assert.match(source,/DWORD ClosePinnedAncestors\(Lease& lease\)/);
52
+ assert.match(source,/DWORD ancestorCloseError = ClosePinnedAncestors\(\*lease\)/);
53
+ assert.match(source,/if \(cleanup == ERROR_SUCCESS && \(error == ERROR_FILE_EXISTS/);
54
+ console.log('R86_STATIC_NATIVE_CLEANUP_ERROR_PROPAGATION_REVIEW_PASS');
55
+ });
56
+
57
+ test('R86 Windows: OS death releases native handle and restart reconciles exact committed head',
58
+ {skip:process.platform!=='win32',timeout:15000},async()=>{
59
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r86-crash-restart-'));
60
+ try{
61
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
62
+ await mkdir(runtimeDir);await mkdir(trust);
63
+ const keys=generateKeyPairSync('ed25519');
64
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
65
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
66
+ const grantInstanceNonce=randomBytes(32).toString('hex');
67
+ const grantBytes=Buffer.from(JSON.stringify({instance_nonce:grantInstanceNonce}));
68
+ const moduleUrl=pathToFileURL(path.join(repo,'src','authority-continuity-r65.mjs')).href;
69
+ const script=`import {createAuthorityEpochStore} from ${JSON.stringify(moduleUrl)};
70
+ const store=createAuthorityEpochStore({runtimeDir:${JSON.stringify(runtimeDir)},
71
+ onDurabilityStage:stage=>{if(stage==='head_renamed')process.kill(process.pid,'SIGKILL');}});
72
+ await store.append({plane:'process',action:'grant',
73
+ grantBytes:Buffer.from(${JSON.stringify(grantBytes.toString('base64'))},'base64'),
74
+ grantInstanceNonce:${JSON.stringify(grantInstanceNonce)}});`;
75
+ const child=spawn(process.execPath,['--input-type=module','-e',script],
76
+ {env:{...process.env,NODE_TEST_CONTEXT:'child-v8'},windowsHide:true,stdio:'ignore'});
77
+ const outcome=await new Promise((resolve,reject)=>{
78
+ const timer=setTimeout(()=>{child.kill();reject(new Error('R86 crash child timeout'));},10000);
79
+ child.once('error',error=>{clearTimeout(timer);reject(error);});
80
+ child.once('exit',(code,signal)=>{clearTimeout(timer);resolve({code,signal});});
81
+ });
82
+ assert.notEqual(outcome.code,0,'child must die at post-rename crash boundary');
83
+ const store=createAuthorityEpochStore({runtimeDir});
84
+ const committed=await store.readHead({plane:'process'});
85
+ assert.equal(committed?.action,'grant','restart must verify exact signed post-rename head');
86
+ const revoked=await store.append({plane:'process',action:'user_revoke',grantBytes,grantInstanceNonce});
87
+ assert.equal(revoked.action,'user_revoke','OS-released handle must permit bounded stale recovery');
88
+ assert.equal((await store.verifyGrant({plane:'process',grantBytes,grantInstanceNonce})).trusted,false);
89
+ assert.equal(await exists(path.join(runtimeDir,'authority-epochs','process','.lock')),false);
90
+ console.log('R86_PROCESS_CRASH_RESTART_REVOKE_GREEN_OBSERVED');
91
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
92
+ });
93
+
94
+ // Retain the historical post-hash swap injection, but verify that the R89
95
+ // protected-installation precondition now rejects this writable test copy
96
+ // before the injected write can run. The old R86 test omitted its newer R89
97
+ // import and was failing at module resolution rather than the trust boundary.
98
+ test('R103 writable-tree addon is denied before post-hash injection',
99
+ {skip:process.platform!=='win32'||process.arch!=='x64'},async()=>{
100
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r86-loader-red-'));
101
+ try{
102
+ for(const rel of ['src','docs','native','bin'])await mkdir(path.join(root,rel),{recursive:true});
103
+ for(const rel of ['docs/R85-EPOCH-LOCK-BINARY.json','native/deadbyte-epoch-lock-r85.cpp',
104
+ 'bin/deadbyte-epoch-lock-r85.node','src/privileged-install-trust-r89.mjs',
105
+ 'src/authority-continuity-r65.mjs','src/canonical-json.mjs','src/text-policy.mjs',
106
+ 'src/foreground-marker-cas-win32.mjs','src/foreground-process-identity.mjs',
107
+ 'src/windows-powershell-environment.mjs'])
108
+ await copyFile(path.join(repo,rel),path.join(root,rel));
109
+ const original=await readFile(path.join(repo,'src','epoch-lock-r85.mjs'),'utf8');
110
+ const anchor='addon??=require(binary);';
111
+ assert.equal(original.split(anchor).length,2,'unexpected loader structure');
112
+ const injection=`await (await import('node:fs/promises')).writeFile(binary,Buffer.from('R86_TEST_ONLY_INVALID_PE_NOT_EXECUTABLE'));\n ${anchor}`;
113
+ await writeFile(path.join(root,'src','epoch-lock-r85.mjs'),original.replace(anchor,injection));
114
+ const meta=JSON.parse(await readFile(path.join(root,'docs','R85-EPOCH-LOCK-BINARY.json'),'utf8'));
115
+ assert.equal(hash(await readFile(path.join(root,meta.addon))),meta.addon_sha256);
116
+ assert.equal(hash(await readFile(path.join(root,meta.source))),meta.source_sha256);
117
+ const module=await import(pathToFileURL(path.join(root,'src','epoch-lock-r85.mjs')).href);
118
+ const lockFile=path.join(root,'.lock');
119
+ await assert.rejects(module.acquireHeldEpochLock(lockFile,Buffer.from('{}')),
120
+ error=>{assert.equal(error.code,'EPOCH_NATIVE_SLOT_UNTRUSTED');return true;});
121
+ assert.equal(hash(await readFile(path.join(root,meta.addon))),meta.addon_sha256,
122
+ 'untrusted writable copy must be refused before post-hash injection');
123
+ assert.equal(await exists(lockFile),false);
124
+ console.log('R103_WRITABLE_LOADER_DENIED_BEFORE_POST_HASH_INJECTION');
125
+ }finally{await rm(root,{recursive:true,force:true,maxRetries:8,retryDelay:150});}
126
+ });