deadbyte-mcp 0.15.45 → 0.15.47

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 (53) hide show
  1. package/MANIFEST.SHA256 +52 -27
  2. package/PROVENANCE.json +260 -257
  3. package/README.txt +22 -21
  4. package/RELEASE.json +366 -346
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +501 -503
  7. package/bin/deadbyte-kernel-lock-r74.exe +0 -0
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-controller.ps1 +20 -6
  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/native/deadbyte-kernel-lock-r74.cpp +65 -0
  17. package/package.json +2 -2
  18. package/scripts/build-kernel-lock-r74.ps1 +28 -0
  19. package/scripts/gate-windows-r72.ps1 +76 -0
  20. package/scripts/npm-prepublish-contract-r74.mjs +113 -0
  21. package/scripts/npm-release-r48.mjs +4 -0
  22. package/scripts/npm-release-readiness-r72.mjs +76 -0
  23. package/scripts/npm-release-readiness-r74.mjs +46 -0
  24. package/scripts/r73-atomic-benchmark.mjs +39 -0
  25. package/src/atomic-mutation.mjs +99 -9
  26. package/src/deadbyte-cli.mjs +722 -721
  27. package/src/kernel-lock-r74.mjs +125 -0
  28. package/src/launcher-isolation.mjs +58 -0
  29. package/src/remote-authority.mjs +75 -75
  30. package/src/remote-mcp-machine-smoke-client.mjs +84 -84
  31. package/src/runtime-update.mjs +2056 -2008
  32. package/src/staging-input-isolation.mjs +79 -0
  33. package/src/version.mjs +1 -1
  34. package/test/atomic-mutation.test.mjs +68 -2
  35. package/test/commander-schema-differential-r48.test.mjs +1 -1
  36. package/test/deferred-slot-operation-behavior.test.mjs +2 -2
  37. package/test/r58-cleanup-retention.test.mjs +3 -3
  38. package/test/r58-concurrent-recovery.test.mjs +3 -3
  39. package/test/r58-stale-owner.test.mjs +1 -1
  40. package/test/r59-tool-surface-compat.test.mjs +2 -2
  41. package/test/r60-authority-coherence.test.mjs +10 -2
  42. package/test/r71-isolated-slot-rollback-restore.test.mjs +11 -11
  43. package/test/r71-release-identity.test.mjs +12 -10
  44. package/test/r72-launcher-isolation.test.mjs +99 -0
  45. package/test/r72-publication-fence.test.mjs +56 -0
  46. package/test/r72-publication-output-integrity.test.mjs +110 -0
  47. package/test/r72-remote-safe-contract.test.mjs +101 -0
  48. package/test/r72-staging-input-isolation.test.mjs +87 -0
  49. package/test/r72-staging-workspace-isolation.test.mjs +114 -0
  50. package/test/r73-package-boundary.test.mjs +22 -0
  51. package/test/r74-kernel-lock.test.mjs +150 -0
  52. package/test/r74-prepublish-contract.test.mjs +89 -0
  53. package/test/remote-session-cli.test.mjs +110 -64
@@ -0,0 +1,110 @@
1
+ import assert from 'node:assert/strict';
2
+ import {test} from 'node:test';
3
+ import {createHash} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,rm,symlink,rename} from 'node:fs/promises';
5
+ import {tmpdir} from 'node:os';
6
+ import path from 'node:path';
7
+ import {assertR72NpmPublishReady} from '../scripts/npm-release-readiness-r72.mjs';
8
+
9
+ const sha = bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const gates = ['full_regression','windows_lifecycle','package_acceptance','independent_judge'];
11
+ const parent = {
12
+ version:'0.15.45',release_id:'v0.15.45-34aa8d1241cfb8bf',
13
+ manifest_sha256:'34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
14
+ generation_manifest_sha256:'ff2b73410062688651abd7bc2dd7b2b8c87810a28e3808bfe1f4352a25b0e6e4'
15
+ };
16
+ const release={version:'0.15.46',name:'deadbyte-mcp',train:'R72',parent,runtime_predecessor:parent,r72_hotfix:{
17
+ status:'CANDIDATE_SEALED',windows_regression:'PASS',package_acceptance:'PASS',manifest:'PASS',
18
+ remote_lifecycle:'PASS',security_gate:'PASS',independent_judge:'PASS'
19
+ }};
20
+
21
+ async function fixture(){
22
+ const root=await mkdtemp(path.join(tmpdir(),'r72-fence-integrity-'));
23
+ const evidence=path.join(root,'testing','r72-release-evidence');
24
+ const outputs=path.join(evidence,'outputs');
25
+ await mkdir(outputs,{recursive:true});
26
+ const manifest=Buffer.from('synthetic test-only manifest, not a real release\n');
27
+ await writeFile(path.join(root,'MANIFEST.SHA256'),manifest);
28
+ const index={schema:'deadbyte.r72-release-evidence-index.v1',version:'0.15.46',parent,
29
+ manifest_sha256:sha(manifest),gate_sha256:{}};
30
+ const outputPaths={};
31
+ for(const gate of gates){
32
+ const output=Buffer.from(`SYNTHETIC-TEST-ONLY\nGATE=${gate}\nEXIT=0\n`);
33
+ const outputPath=path.join(outputs,`${gate}.log`);
34
+ outputPaths[gate]=outputPath;
35
+ await writeFile(outputPath,output);
36
+ const receipt={schema:'deadbyte.r72-gate-result.v1',gate,version:'0.15.46',
37
+ manifest_sha256:index.manifest_sha256,status:'PASS',exit_code:0,
38
+ output_file:`outputs/${gate}.log`,output_sha256:sha(output),
39
+ ...(gate==='full_regression'?{pass_count:1,fail_count:0}:{}),
40
+ ...(gate==='windows_lifecycle'?{safe_default_arm_count:0,operator_owner_consent_enforced:true,
41
+ stale_owner_recovery_verified:true,signed_epoch_fail_closed:true}:{}),
42
+ ...(gate==='independent_judge'?{independent_verification:'PASS'}:{})};
43
+ const bytes=Buffer.from(JSON.stringify(receipt)+'\n');
44
+ index.gate_sha256[gate]=sha(bytes);
45
+ await writeFile(path.join(evidence,`${gate}.json`),bytes);
46
+ }
47
+ await writeFile(path.join(evidence,'ready.json'),JSON.stringify(index)+'\n');
48
+ return {root,evidence,outputPaths,dispose:()=>rm(root,{recursive:true,force:true})};
49
+ }
50
+
51
+ test('synthetic complete fixture validates receipt/output bytes linkage (not real release evidence)',async()=>{
52
+ const f=await fixture();try{
53
+ const result=await assertR72NpmPublishReady(release,{root:f.root});
54
+ assert.equal(result.status,'R72_RELEASE_READY_EVIDENCE_BOUND');
55
+ } finally {await f.dispose();}
56
+ });
57
+
58
+ test('mutating full regression output, without changing receipt, rejects',async()=>{
59
+ const f=await fixture();try{
60
+ await writeFile(f.outputPaths.full_regression,'tampered output\n');
61
+ await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*(mismatch|tamper|hash)/i);
62
+ } finally {await f.dispose();}
63
+ });
64
+
65
+ test('missing Windows lifecycle output rejects even with intact receipt',async()=>{
66
+ const f=await fixture();try{
67
+ await rm(f.outputPaths.windows_lifecycle);
68
+ await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*(missing|invalid|unavailable)/i);
69
+ } finally {await f.dispose();}
70
+ });
71
+
72
+ test('receipt pointing to other output file rejects even with recomputed index SHA',async()=>{
73
+ const f=await fixture();try{
74
+ const gate='package_acceptance',receiptPath=path.join(f.evidence,`${gate}.json`);
75
+ const receipt=JSON.parse(await readFile(receiptPath,'utf8'));
76
+ receipt.output_file='outputs/full_regression.log';
77
+ const content=Buffer.from(JSON.stringify(receipt)+'\n');
78
+ await writeFile(receiptPath,content);
79
+ const indexPath=path.join(f.evidence,'ready.json');
80
+ const index=JSON.parse(await readFile(indexPath,'utf8'));
81
+ index.gate_sha256[gate]=sha(content);
82
+ await writeFile(indexPath,JSON.stringify(index)+'\n');
83
+ await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*path/i);
84
+ } finally {await f.dispose();}
85
+ });
86
+
87
+ test('symlinked evidence output or junction directory must not be trusted',async()=>{
88
+ const f=await fixture();try{
89
+ const target=f.outputPaths.independent_judge,other=path.join(f.root,'external.log');
90
+ await writeFile(other,await readFile(target));
91
+ await rm(target);
92
+ try {await symlink(other,target,'file');}
93
+ catch(e){
94
+ if(!['EPERM','EACCES','ENOTSUP'].includes(e.code))throw e;
95
+ // Windows can deny file symlinks while permitting unprivileged directory junctions.
96
+ const outputs=path.join(f.evidence,'outputs');
97
+ const externalOutputs=path.join(f.root,'external-outputs');
98
+ await rename(outputs,externalOutputs);
99
+ await symlink(externalOutputs,outputs,'junction');
100
+ }
101
+ await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/(output.*(regular|symlink)|evidence\/output directory is not a real directory)/i);
102
+ } finally {await f.dispose();}
103
+ });
104
+
105
+ test('missing status seal cannot be bypassed by synthetic receipts',async()=>{
106
+ const f=await fixture();try{
107
+ const uns={...release,r72_hotfix:{...release.r72_hotfix,status:'CANDIDATE_UNSEALED'}};
108
+ await assert.rejects(assertR72NpmPublishReady(uns,{root:f.root}),/CANDIDATE_SEALED/);
109
+ } finally {await f.dispose();}
110
+ });
@@ -0,0 +1,101 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,readFile,rm,writeFile,access} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {authorityVector} from '../src/remote-authority.mjs';
8
+
9
+ const controllerPath=new URL('../controller/deadbyte-controller.ps1',import.meta.url);
10
+ const quote=value=>`'${String(value).replaceAll("'","''")}'`;
11
+ const winTest=process.platform==='win32'?test:test.skip;
12
+
13
+ // The legacy elevated contract is still available, but must be explicitly selected.
14
+ test('R72 default remote asks for no standing authority',()=>{
15
+ assert.deepEqual(authorityVector('default'),{
16
+ process:false,coding:false,autonomous:false,desktop:false,host:false
17
+ });
18
+ });
19
+ test('R72 operator retains explicit process/coding/autonomous and no host/desktop',()=>{
20
+ assert.deepEqual(authorityVector('operator'),{
21
+ process:true,coding:true,autonomous:true,desktop:false,host:false
22
+ });
23
+ });
24
+ test('R72 desktop/host/full remain explicit elevated choices',()=>{
25
+ for(const [mode,desktop,host] of [['desktop',true,false],['host',false,true],['full',true,true]]){
26
+ assert.deepEqual(authorityVector(mode),{
27
+ process:true,coding:true,autonomous:true,desktop,host
28
+ });
29
+ }
30
+ assert.throws(()=>authorityVector('invalid'),/mode invalid/);
31
+ });
32
+
33
+ async function controllerScenario({grant=false,escape=false,epochFails=false}={}){
34
+ const temp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r72-revocation-'));
35
+ const runtime=path.join(temp,'runtime');
36
+ const file=path.join(temp,'autonomous.json');
37
+ const grantFile=path.join(escape?temp:runtime,'autonomous-grant.json');
38
+ const epochTrace=path.join(temp,'epoch-trace.txt');
39
+ await mkdir(runtime,{recursive:true});
40
+ await writeFile(file,JSON.stringify({
41
+ schema:'deadbyte.autonomous-policy.v1',enabled:true,
42
+ lease_file:grantFile,coding_policy_sha256:'a'.repeat(64),root_ids:['core']
43
+ }));
44
+ if(grant)await writeFile(grantFile,'fixture-grant');
45
+ const source=await readFile(controllerPath,'utf8');
46
+ const begin=source.indexOf('function Get-AutonomousRevocationLeaseFile(');
47
+ const end=source.indexOf('function Show-AutonomousStatus(',begin);
48
+ const readBegin=source.indexOf('function Read-ControllerPolicySnapshot(');
49
+ const readEnd=source.indexOf('function Get-CodingPolicyMetadata(',readBegin);
50
+ assert.ok(begin>=0&&end>begin&&readBegin>=0&&readEnd>readBegin);
51
+ const script=`$ErrorActionPreference='Stop';
52
+ $RuntimeDir=${quote(runtime)};
53
+ function Fail([string]$m){throw $m};
54
+ function Get-AutonomousPolicyMetadata($Cfg){throw 'ISSUANCE_PATH_MUST_NOT_RUN'};
55
+ function Invoke-AuthorityEpochCommand($Cfg,[string]$Plane,[string]$Action,[string]$GrantFile){
56
+ [IO.File]::AppendAllText(${quote(epochTrace)},("$Plane/$Action"+[Environment]::NewLine));
57
+ ${epochFails?"throw 'FIXTURE_EPOCH_FAILED'":"return 'FIXTURE_SIGNED_REVOKE'"}
58
+ };
59
+ ${source.slice(readBegin,readEnd)};
60
+ ${source.slice(begin,end)};
61
+ try { Disarm-Autonomous @{AutonomousPolicyFile=${quote(file)}};Write-Output 'REVOKE_OK' }
62
+ catch {Write-Output ('REVOKE_FAILED: '+$_.Exception.Message);exit 7}
63
+ `;
64
+ const result=spawnSync('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-Command',script],{
65
+ encoding:'utf8',timeout:15000,maxBuffer:1024*1024
66
+ });
67
+ const remaining=await access(grantFile).then(()=>true,()=>false);
68
+ const epoch=await readFile(epochTrace,'utf8').catch(error=>{
69
+ if(error?.code==='ENOENT')return '';throw error;
70
+ });
71
+ await rm(temp,{recursive:true,force:true});
72
+ return {result,remaining,epoch};
73
+ }
74
+
75
+ winTest('R72 disarm with broad Coding roots and no Autonomous grant succeeds without grant issuance',async()=>{
76
+ const {result,remaining,epoch}=await controllerScenario();
77
+ assert.equal(result.status,0,result.stderr+'\n'+result.stdout);
78
+ assert.match(result.stdout,/REVOKE_OK/);
79
+ assert.equal(remaining,false);
80
+ assert.equal(epoch,'');
81
+ });
82
+ winTest('R72 present Autonomous grant is sent to epoch revoker before removal',async()=>{
83
+ const {result,remaining,epoch}=await controllerScenario({grant:true});
84
+ assert.equal(result.status,0,result.stderr+'\n'+result.stdout);
85
+ assert.match(epoch,/autonomous\/user-revoke/);
86
+ assert.equal(remaining,false);
87
+ });
88
+ winTest('R72 external grant path is rejected without touching the file',async()=>{
89
+ const {result,remaining,epoch}=await controllerScenario({grant:true,escape:true});
90
+ assert.notEqual(result.status,0);
91
+ assert.match(result.stdout,/must live under controller runtime directory/);
92
+ assert.equal(remaining,true);
93
+ assert.equal(epoch,'');
94
+ });
95
+ winTest('R72 epoch revocation error remains a failure even after file removal',async()=>{
96
+ const {result,remaining,epoch}=await controllerScenario({grant:true,epochFails:true});
97
+ assert.notEqual(result.status,0);
98
+ assert.match(result.stdout,/FIXTURE_EPOCH_FAILED/);
99
+ assert.match(epoch,/autonomous\/user-revoke/);
100
+ assert.equal(remaining,false);
101
+ });
@@ -0,0 +1,87 @@
1
+ import {test} from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,rm,writeFile,symlink,readFile} from 'node:fs/promises';
4
+ import {tmpdir,homedir} from 'node:os';
5
+ import path from 'node:path';
6
+ import {assertStageOwnedPath,assertStageNoProductionPath,assertStagingBootstrapInputs,assertStagingControlConfig} from '../src/staging-input-isolation.mjs';
7
+ const usingStage=async fn=>{const dir=await mkdtemp(path.join(tmpdir(),'r72-g2-w2-'));try{const state=path.join(dir,'isolated-state');await mkdir(state);await fn({dir,state});}finally{await rm(dir,{recursive:true,force:true});}};
8
+ const cfg=state=>({
9
+ CoreRoot:path.join(state,'releases','predecessor'),WorkspaceRoot:path.join(state,'workspaces'),
10
+ CodingPolicyFile:path.join(state,'policies','coding.json'),AutonomousPolicyFile:path.join(state,'policies','autonomous.json'),
11
+ HostPolicyFile:path.join(state,'policies','host.json'),ProcessPolicyFile:path.join(state,'policies','process.json'),
12
+ DesktopPolicyFile:path.join(state,'policies','desktop.json'),
13
+ Bridge:{FilePath:path.join(state,'bin','node.exe'),WorkingDirectory:path.join(state,'bridge'),ScriptPath:path.join(state,'bridge','src','bridge.mjs'),Dependencies:[{Name:'bridge-local',Path:path.join(state,'bridge','src','helper.mjs')}]},
14
+ Cloud:{FilePath:path.join(state,'bin','tunnel.exe'),WorkingDirectory:path.join(state,'releases','predecessor'),Dependencies:[{Name:'cloudflared',Path:path.join(path.parse(state).root,'opt','cloudflared.exe')},{Name:'tunnel-token',Path:path.join(state,'trust','cloud-token.txt')}]}
15
+ });
16
+ test('G2-W2 accepts stage-owned policy, Bridge and Cloud config paths',async()=>usingStage(async({state})=>{
17
+ assert.doesNotThrow(()=>assertStagingControlConfig(cfg(state),state,{configPath:path.join(state,'generations','config.psd1')}));
18
+ }));
19
+ test('G2-W2 rejects each external controller policy path before policy read',async()=>usingStage(async({dir,state})=>{
20
+ for(const key of ['CodingPolicyFile','AutonomousPolicyFile','HostPolicyFile','ProcessPolicyFile','DesktopPolicyFile']){
21
+ const c=cfg(state);c[key]=path.join(dir,'outside.json');
22
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),new RegExp('STAGING_EXTERNAL_INPUT: '+key));
23
+ }
24
+ }));
25
+ test('G2-W2 rejects external controller config before PowerShell Import-PowerShellDataFile',async()=>usingStage(async({dir,state})=>{
26
+ assert.throws(()=>assertStageOwnedPath(path.join(dir,'production','config.psd1'),state,'controller config file'),/STAGING_EXTERNAL_INPUT/);
27
+ }));
28
+ test('G2-W2 rejects bridge and Cloud external working directories, dependencies and executables',async()=>usingStage(async({dir,state})=>{
29
+ for(const [component,key] of [['Bridge','WorkingDirectory'],['Bridge','ScriptPath'],['Cloud','WorkingDirectory']]){
30
+ const c=cfg(state);c[component][key]=path.join(dir,'foreign','escape.exe');
31
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
32
+ }
33
+ const c=cfg(state);c.Bridge.Dependencies[0].Path=path.join(dir,'foreign.js');
34
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
35
+ }));
36
+ test('G2-W2 permits a shared non-production executable but forbids a production-state executable',async()=>usingStage(async({dir,state})=>{
37
+ const c=cfg(state);c.Bridge.FilePath=path.join(dir,'system-node.exe');
38
+ assert.doesNotThrow(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}));
39
+ c.Bridge.FilePath=path.join(homedir(),'.deadbyte-mcp','bin','node.exe');
40
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_PRODUCTION_INPUT/);
41
+ }));
42
+ test('G2-W2 rejects external cloud credential in existing config',async()=>usingStage(async({dir,state})=>{
43
+ const c=cfg(state);c.Cloud.Dependencies[1].Path=path.join(dir,'production','token.txt');
44
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
45
+ }));
46
+ test('G2-W2 rejects external policy override before state mutation',async()=>usingStage(async({dir,state})=>{
47
+ for(const key of ['codingPolicyBootstrap','autonomousPolicyBootstrap','hostPolicyBootstrap','processPolicyBootstrap','desktopPolicyBootstrap','ownerApprovalBootstrap','semanticLspBootstrap']){
48
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,[key]:{path:path.join(dir,'outside.json'),sha256:'a'.repeat(64)}}),/STAGING_EXTERNAL_INPUT/);
49
+ }
50
+ }));
51
+ test('G2-W2 rejects production legacy --source-root and Bridge bootstrap before state mutation',async()=>usingStage(async({dir,state})=>{
52
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,sourceRoot:path.join(dir,'legacy-core')}),/STAGING_EXTERNAL_INPUT/);
53
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,bridgeBootstrap:{root:path.join(dir,'bridge'),scriptSha256:'a'.repeat(64)}}),/STAGING_EXTERNAL_INPUT/);
54
+ }));
55
+ test('G2-W2 requires named-tunnel token and working directory under stage state',async()=>usingStage(async({dir,state})=>{
56
+ const c={cloudflared:{path:path.join(dir,'nonprod-cloudflared.exe')},tokenFile:{path:path.join(state,'trust','token.txt')},childWorkingDirectory:path.join(state,'cloud')};
57
+ assert.doesNotThrow(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:c}));
58
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:{...c,tokenFile:{path:path.join(dir,'external-token.txt')}}}),/STAGING_EXTERNAL_INPUT/);
59
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:{...c,childWorkingDirectory:path.join(dir,'foreign')}}),/STAGING_EXTERNAL_INPUT/);
60
+ }));
61
+ test('G2-W2 prohibits production config, token and cloudflared even when explicitly pinned',async()=>usingStage(async({state})=>{
62
+ const prod=path.join(homedir(),'.deadbyte-mcp');
63
+ assert.throws(()=>assertStageOwnedPath(path.join(prod,'trust','update-key.json'),state,'production key'),/STAGING_EXTERNAL_INPUT/);
64
+ assert.throws(()=>assertStageNoProductionPath(path.join(prod,'bin','cloudflared.exe'),state,'cloudflared'),/STAGING_PRODUCTION_INPUT/);
65
+ }));
66
+ test('G2-W2 rejects staging nested in production root',()=>{
67
+ const prod=path.join(homedir(),'.deadbyte-mcp');
68
+ assert.throws(()=>assertStageOwnedPath(path.join(prod,'stage','policies','coding.json'),path.join(prod,'stage'),'policy'),/STAGING_STATE_OVERLAPS_PRODUCTION/);
69
+ });
70
+ test('G2-W2 allows production legacy policy paths without modifying production data',async()=>usingStage(async({dir})=>{
71
+ const production=path.join(homedir(),'.deadbyte-mcp');
72
+ const c=cfg(path.join(dir,'separate-stage'));
73
+ assert.doesNotThrow(()=>assertStagingControlConfig(c,production,{configPath:path.join(dir,'external.psd1')}));
74
+ assert.doesNotThrow(()=>assertStagingBootstrapInputs({stateRoot:production,sourceRoot:dir,cloudTransport:{tokenFile:{path:dir}}}));
75
+ }));
76
+ test('G2-W2 rejects a symlink path component in an otherwise owned policy path',async t=>usingStage(async({dir,state})=>{
77
+ const external=path.join(dir,'external');await mkdir(external);
78
+ try{await symlink(external,path.join(state,'linked'),process.platform==='win32'?'junction':'dir');}
79
+ catch(error){if(['EPERM','EACCES','ENOSYS'].includes(error.code)){t.diagnostic('symlink privilege not present');return;}throw error;}
80
+ const c=cfg(state);c.CodingPolicyFile=path.join(state,'linked','coding.json');
81
+ assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_REPARSE_INPUT/);
82
+ }));
83
+ test('G2-W2 rejection never modifies foreign source bytes',async()=>usingStage(async({dir,state})=>{
84
+ const foreign=path.join(dir,'foreign.json');await writeFile(foreign,'private-boundary-marker');
85
+ assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,codingPolicyBootstrap:{path:foreign}}),/STAGING_EXTERNAL_INPUT/);
86
+ assert.equal(await readFile(foreign,'utf8'),'private-boundary-marker');
87
+ }));
@@ -0,0 +1,114 @@
1
+ import {test} from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,rm,symlink} from 'node:fs/promises';
4
+ import {tmpdir,homedir} from 'node:os';
5
+ import {existsSync, lstatSync} from 'node:fs';
6
+ import path from 'node:path';
7
+ import {workspacePlan} from '../src/runtime-update.mjs';
8
+
9
+ const stage = async fn => {
10
+ const dir=await mkdtemp(path.join(tmpdir(),'r72-g2-workspace-'));
11
+ try {await fn(dir,path.join(dir,'stage-state'));}
12
+ finally {await rm(dir,{recursive:true,force:true});}
13
+ };
14
+
15
+ test('staging rejects external existing workspace before any rename can be planned', async()=>{
16
+ await stage(async(base,state)=>{
17
+ const foreign=path.join(base,'production-workspaces');
18
+ await mkdir(foreign);
19
+ await assert.rejects(workspacePlan(foreign,state), /STAGING_WORKSPACE_OUTSIDE_STATE/);
20
+ });
21
+ });
22
+
23
+ test('staging rejects production-managed workspace path even if not present', async()=>{
24
+ await stage(async(base,state)=>{
25
+ const prod=path.join(homedir(),'.deadbyte-mcp','workspaces');
26
+ await assert.rejects(workspacePlan(prod,state), /STAGING_WORKSPACE_OUTSIDE_STATE/);
27
+ });
28
+ });
29
+
30
+ test('staging permits a stage-owned legacy workspace and preserves plan-only semantics', async()=>{
31
+ await stage(async(base,state)=>{
32
+ const old=path.join(state,'legacy-workspaces');
33
+ await mkdir(old,{recursive:true});
34
+ const planned=await workspacePlan(old,state);
35
+ assert.equal(planned.mode,'rename');
36
+ assert.equal(planned.target,path.join(state,'workspaces'));
37
+ assert.equal(planned.moved,false);
38
+ });
39
+ });
40
+
41
+ test('staging rejects a workspace equal to state root', async()=>{
42
+ await stage(async(base,state)=>{
43
+ await mkdir(state);
44
+ await assert.rejects(workspacePlan(state,state), /STAGING_WORKSPACE_STATE_ROOT/);
45
+ });
46
+ });
47
+
48
+ test('production keeps its established external-workspace migration behavior', async()=>{
49
+ await stage(async(base)=>{
50
+ const prod=path.join(homedir(),'.deadbyte-mcp');
51
+ const stable=path.join(prod,'workspaces');
52
+ const external=path.join(base,'legacy-workspaces');
53
+ await mkdir(external);
54
+ // Observe real target state read-only: production might already contain workspaces.
55
+ if(existsSync(stable)){
56
+ const info=lstatSync(stable);
57
+ if(!info.isDirectory()||info.isSymbolicLink()){
58
+ await assert.rejects(workspacePlan(external,prod),/stable workspace root must be a real directory/);
59
+ }else{
60
+ await assert.rejects(workspacePlan(external,prod),/workspace migration conflict: both source and stable target exist/);
61
+ }
62
+ }else{
63
+ const result=await workspacePlan(external,prod);
64
+ assert.equal(result.source,external);
65
+ assert.equal(result.target,stable);
66
+ assert.equal(result.mode,'rename');
67
+ assert.equal(result.moved,false);
68
+ }
69
+ // Planning must never rename or remove the legacy source.
70
+ assert.equal(existsSync(external),true);
71
+ });
72
+ });
73
+
74
+ test('staging refuses workspace path through symlink or junction', async(t)=>{
75
+ await stage(async(base,state)=>{
76
+ const real=path.join(base,'foreign');await mkdir(real);
77
+ const link=path.join(state,'link');await mkdir(state);
78
+ try{await symlink(real,link,process.platform==='win32'?'junction':'dir');}
79
+ catch(e){if(['EPERM','EACCES','ENOSYS'].includes(e.code)){t.diagnostic('symlink creation not permitted');return;}throw e;}
80
+ await assert.rejects(workspacePlan(link,state), /STAGING_WORKSPACE_REPARSE/);
81
+ });
82
+ });
83
+
84
+ test('staging refuses reserved trust, release and runtime trees as workspace sources',async()=>{
85
+ await stage(async(base,state)=>{
86
+ for(const child of ['trust','releases','runtime','bin','evidence']){
87
+ const source=path.join(state,child,'workspace');
88
+ await assert.rejects(workspacePlan(source,state),/STAGING_WORKSPACE_RESERVED_STATE/);
89
+ }
90
+ });
91
+ });
92
+
93
+ test('workspace apply rejects a newly introduced target junction/symlink',async(t)=>{
94
+ await stage(async(base,state)=>{
95
+ const old=path.join(state,'old-workspaces');await mkdir(old,{recursive:true});
96
+ const plan=await workspacePlan(old,state);
97
+ const external=path.join(base,'foreign');await mkdir(external);
98
+ try{await symlink(external,path.join(state,'workspaces'),process.platform==='win32'?'junction':'dir');}
99
+ catch(e){if(['EPERM','EACCES','ENOSYS'].includes(e.code)){t.diagnostic('symlink creation not permitted');return;}throw e;}
100
+ const {applyWorkspacePlan}=await import('../src/runtime-update.mjs');
101
+ await assert.rejects(applyWorkspacePlan(plan),/STAGING_WORKSPACE_REPARSE/);
102
+ assert.equal((await import('node:fs')).existsSync(old),true);
103
+ });
104
+ });
105
+
106
+ test('workspace rollback rejects a foreign workspace source without touching it',async()=>{
107
+ await stage(async(base,state)=>{
108
+ const foreign=path.join(base,'foreign');await mkdir(foreign);
109
+ const {rollbackWorkspacePlan}=await import('../src/runtime-update.mjs');
110
+ const fake={source:foreign,target:path.join(state,'workspaces'),mode:'rename',moved:true};
111
+ await assert.rejects(rollbackWorkspacePlan(fake),/STAGING_WORKSPACE_OUTSIDE_STATE/);
112
+ assert.equal((await import('node:fs')).existsSync(foreign),true);
113
+ });
114
+ });
@@ -0,0 +1,22 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {spawnSync} from 'node:child_process';
4
+ import path from 'node:path';
5
+ import {fileURLToPath} from 'node:url';
6
+
7
+ const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
8
+ const npmCli=process.env.npm_execpath || path.join(path.dirname(process.execPath),'node_modules','npm','bin','npm-cli.js');
9
+
10
+ test('R73 npm dry-run package excludes generated test evidence and reference trees',()=>{
11
+ const result=spawnSync(process.execPath,[npmCli,'pack','--dry-run','--json'],{
12
+ cwd:root,encoding:'utf8',timeout:120000,windowsHide:true,env:{...process.env,npm_config_audit:'false'}
13
+ });
14
+ assert.equal(result.error,undefined,String(result.error));
15
+ assert.equal(result.status,0,result.stderr||result.stdout);
16
+ const parsed=JSON.parse(result.stdout);
17
+ const record=Array.isArray(parsed)?parsed[0]:parsed['deadbyte-mcp'];
18
+ assert.ok(record&&Array.isArray(record.files),'npm file inventory missing');
19
+ const leaked=record.files.map(row=>row.path).filter(name=>
20
+ /^(?:testing|references|\.git)(?:\/|$)|(?:^|\/)\.env(?:\.|$)/i.test(name));
21
+ assert.deepEqual(leaked,[],'generated evidence or private trees must not enter npm package');
22
+ });
@@ -0,0 +1,150 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp, readFile, rm, writeFile} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {acquireKernelExclusiveLock, verifyKernelLockBinary} from '../src/kernel-lock-r74.mjs';
7
+
8
+ async function safeRm(dir) {
9
+ for (let i = 0; i < 15; i++) {
10
+ try {
11
+ await rm(dir, {recursive: true, force: true});
12
+ return;
13
+ } catch (err) {
14
+ if (err.code !== 'EBUSY' && err.code !== 'EPERM') throw err;
15
+ await new Promise(r => setTimeout(r, 40));
16
+ }
17
+ }
18
+ await rm(dir, {recursive: true, force: true});
19
+ }
20
+
21
+ test('kernel lock binary is verified and hash-pinned', async () => {
22
+ const verified = await verifyKernelLockBinary();
23
+ assert.equal(verified.ok, true, verified.reason);
24
+ assert.ok(/^[0-9a-f]{64}$/.test(verified.sha256));
25
+ });
26
+
27
+ test('kernel exclusive lock can be acquired and released normally', async () => {
28
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-basic-'));
29
+ const lock = path.join(root, 'target.lock');
30
+ try {
31
+ const release = await acquireKernelExclusiveLock(lock);
32
+ assert.equal(typeof release, 'function');
33
+ assert.ok(Number.isSafeInteger(release.pid));
34
+ await release();
35
+ const content = await readFile(lock, 'utf8');
36
+ assert.match(content, /^deadbyte\.kernel-lock\.r74:pid=\d+/);
37
+ } finally {
38
+ await safeRm(root);
39
+ }
40
+ });
41
+
42
+ test('concurrent writer is blocked while lock is held by another process', async () => {
43
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-contention-'));
44
+ const lock = path.join(root, 'target.lock');
45
+ try {
46
+ const release = await acquireKernelExclusiveLock(lock);
47
+ let error = null;
48
+ try {
49
+ await acquireKernelExclusiveLock(lock, {timeoutMs: 100, pollMs: 15});
50
+ } catch (err) {
51
+ error = err;
52
+ }
53
+ assert.ok(error, 'second writer should have been blocked');
54
+ assert.match(error.message, /atomic mutation lock timed out/);
55
+ await release();
56
+ } finally {
57
+ await safeRm(root);
58
+ }
59
+ });
60
+
61
+ test('crash-safe recovery: killed owner releases handle via OS, successor acquires without deleting lock pathname', async () => {
62
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-crash-'));
63
+ const lock = path.join(root, 'target.lock');
64
+ try {
65
+ const releaseFirst = await acquireKernelExclusiveLock(lock);
66
+ const lockPid = releaseFirst.pid;
67
+ assert.ok(Number.isSafeInteger(lockPid));
68
+
69
+ // Abrupt termination (simulating crash)
70
+ process.kill(lockPid, 'SIGKILL');
71
+
72
+ // Wait a brief moment for Windows kernel to close the handle
73
+ await new Promise(r => setTimeout(r, 60));
74
+
75
+ // Successor MUST be able to acquire the lock immediately WITHOUT deleting the lock file!
76
+ const releaseSecond = await acquireKernelExclusiveLock(lock, {timeoutMs: 1000});
77
+ assert.equal(typeof releaseSecond, 'function');
78
+
79
+ // Clean release
80
+ await releaseSecond();
81
+ } finally {
82
+ await safeRm(root);
83
+ }
84
+ });
85
+
86
+ test('dead PID or stale on-disk marker does not block new acquisition when no handle is held', async () => {
87
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-pidreuse-'));
88
+ const lock = path.join(root, 'target.lock');
89
+ try {
90
+ // Write a stale/foreign PID into the lock file
91
+ await writeFile(lock, 'deadbyte.kernel-lock.r74:pid=999999999\n');
92
+
93
+ // Because no live kernel handle holds the file, acquisition must succeed immediately
94
+ const release = await acquireKernelExclusiveLock(lock, {timeoutMs: 500});
95
+ assert.equal(typeof release, 'function');
96
+ await release();
97
+ } finally {
98
+ await safeRm(root);
99
+ }
100
+ });
101
+
102
+ test('concurrent recovery: exactly one worker acquires lock, others are serialized', async () => {
103
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-concurrent-'));
104
+ const lock = path.join(root, 'target.lock');
105
+ const sequence = [];
106
+ try {
107
+ const runWorker = async (id) => {
108
+ const release = await acquireKernelExclusiveLock(lock, {timeoutMs: 5000, pollMs: 10});
109
+ sequence.push(`enter_${id}`);
110
+ await new Promise(r => setTimeout(r, 20));
111
+ sequence.push(`exit_${id}`);
112
+ await release();
113
+ };
114
+
115
+ // Run 3 workers concurrently
116
+ await Promise.all([runWorker(1), runWorker(2), runWorker(3)]);
117
+
118
+ assert.equal(sequence.length, 6);
119
+ // Every enter must be followed by its matching exit before next enter
120
+ for (let i = 0; i < sequence.length; i += 2) {
121
+ const enter = sequence[i];
122
+ const exit = sequence[i + 1];
123
+ const id = enter.split('_')[1];
124
+ assert.equal(exit, `exit_${id}`, `non-exclusive execution detected: ${enter} followed by ${exit}`);
125
+ }
126
+ } finally {
127
+ await safeRm(root);
128
+ }
129
+ });
130
+
131
+ test('replaced binary negative: tampered executable fails closed with recovery blocked', async () => {
132
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r74-lock-tampered-'));
133
+ const lock = path.join(root, 'target.lock');
134
+ // Temporarily override docs/R74-KERNEL-LOCK-BINARY.json hash
135
+ const metaPath = path.resolve('docs/R74-KERNEL-LOCK-BINARY.json');
136
+ const originalMeta = await readFile(metaPath, 'utf8');
137
+ try {
138
+ const tampered = JSON.parse(originalMeta);
139
+ tampered.sha256 = '0'.repeat(64);
140
+ await writeFile(metaPath, JSON.stringify(tampered, null, 2) + '\n');
141
+
142
+ await assert.rejects(
143
+ acquireKernelExclusiveLock(lock, {timeoutMs: 200}),
144
+ /atomic mutation lock recovery blocked: native exclusive locking unavailable/
145
+ );
146
+ } finally {
147
+ await writeFile(metaPath, originalMeta);
148
+ await safeRm(root);
149
+ }
150
+ });