deadbyte-mcp 0.15.72 → 0.15.75

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 (144) hide show
  1. package/MANIFEST.SHA256 +143 -35
  2. package/PROVENANCE.json +47 -21
  3. package/README.txt +29 -18
  4. package/RELEASE.json +85 -18
  5. package/SBOM.cdx.json +14 -2
  6. package/TEST-MATRIX.json +50 -2
  7. package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
  8. package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
  9. package/bin/deadbyte-desktop-uia-r199.exe +0 -0
  10. package/bin/deadbyte-desktop-uia-r199.obj +0 -0
  11. package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
  12. package/bin/deadbyte-fs-read-r198.exe +0 -0
  13. package/bin/deadbyte-fs-read-r198.obj +0 -0
  14. package/bin/deadbyte-owner-token-r193.exe +0 -0
  15. package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
  16. package/bridge/package.json +1 -1
  17. package/controller/deadbyte-process-policy.json +4 -4
  18. package/docs/R184-package-lock.json +2 -8
  19. package/native/deadbyte-desktop-uia-r199.cpp +345 -0
  20. package/native/deadbyte-fs-read-r197.cpp +174 -0
  21. package/native/deadbyte-fs-read-r198.cpp +181 -0
  22. package/native/deadbyte-owner-token-r193.cpp +156 -0
  23. package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
  24. package/package.json +4 -2
  25. package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
  26. package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
  27. package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
  28. package/scripts/build-native-fs-read-r197.ps1 +28 -0
  29. package/scripts/build-native-fs-read-r198.ps1 +28 -0
  30. package/scripts/build-native-root-probe-r197.ps1 +28 -0
  31. package/scripts/gate-windows-r193.ps1 +108 -0
  32. package/scripts/gate-windows-r194.ps1 +112 -0
  33. package/scripts/gate-windows-r195.ps1 +114 -0
  34. package/scripts/gate-windows-r206.ps1 +116 -0
  35. package/scripts/init-desktop-policy-r199.ps1 +85 -0
  36. package/scripts/npm-interactive-r194.mjs +13 -0
  37. package/scripts/npm-publication-r65.mjs +64 -4
  38. package/scripts/npm-release-r48.mjs +75 -11
  39. package/scripts/npm-release-readiness-r193.mjs +80 -0
  40. package/scripts/npm-release-readiness-r194.mjs +80 -0
  41. package/scripts/npm-release-readiness-r195.mjs +72 -0
  42. package/scripts/npm-release-readiness-r206.mjs +76 -0
  43. package/scripts/r188-owner-uac-review.ps1 +91 -0
  44. package/scripts/r188-uac-review-preview.mjs +65 -0
  45. package/scripts/r193-compile-token.ps1 +22 -0
  46. package/scripts/r193-evidence-relocate.ps1 +12 -0
  47. package/scripts/r193-focused-gate.mjs +20 -0
  48. package/scripts/r193-import-r188-scripts.ps1 +14 -0
  49. package/scripts/r193-limited-subject-probe.ps1 +25 -0
  50. package/scripts/r193-migrate-runtime-limited.ps1 +103 -0
  51. package/scripts/r193-owner-keygen.mjs +43 -0
  52. package/scripts/r193-owner-request-live.mjs +49 -0
  53. package/scripts/r193-owner-uac-approve.ps1 +42 -0
  54. package/scripts/r193-owner-uac-broker.mjs +139 -0
  55. package/scripts/r193-owner-uac-enroll-and-approve.ps1 +62 -0
  56. package/scripts/r193-test-limited-token.ps1 +49 -0
  57. package/scripts/r193-toolchain-inventory.ps1 +7 -0
  58. package/scripts/r193-uac-bridge-focused.mjs +21 -0
  59. package/scripts/r205-candidate-selection.mjs +22 -0
  60. package/src/candidate-selection-evidence-r206.mjs +71 -0
  61. package/src/candidate-selection-r205.mjs +96 -0
  62. package/src/desktop-postcondition-r196.mjs +30 -0
  63. package/src/desktop-runtime-r40.mjs +44 -7
  64. package/src/desktop-uia-identity-r199.mjs +51 -0
  65. package/src/desktop-uia-r40.mjs +9 -1
  66. package/src/machine-fs-handle-read-r196.mjs +43 -0
  67. package/src/machine-fs-native-read-r197.mjs +73 -0
  68. package/src/machine-fs-native-read-r198.mjs +76 -0
  69. package/src/machine-fs-read-worker.mjs +5 -25
  70. package/src/machine-fs.mjs +31 -16
  71. package/src/r188-owner-uac.mjs +105 -0
  72. package/src/r188-uac-boundary.mjs +21 -0
  73. package/src/r193-live-owner-subject.mjs +110 -0
  74. package/src/r193-owner-secret-acl.mjs +67 -0
  75. package/src/r193-owner-uac-broker.mjs +85 -0
  76. package/src/r193-uac-r70-bridge.mjs +54 -0
  77. package/src/r195-canonical-owner-observation.mjs +15 -0
  78. package/src/remote-destination-journal-r202.mjs +120 -0
  79. package/src/remote-device-r44.mjs +89 -14
  80. package/src/remote-payload-boundary-r201.mjs +71 -0
  81. package/src/remote-terminal-evidence-r200.mjs +60 -0
  82. package/src/version.mjs +1 -1
  83. package/test/desktop-runtime-r40.test.mjs +111 -0
  84. package/test/desktop-uia-r40.test.mjs +15 -0
  85. package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
  86. package/test/fixtures/r203-journal-writer.mjs +6 -0
  87. package/test/process-policy-refresh.test.mjs +4 -4
  88. package/test/r130-release-security-contract.test.mjs +8 -8
  89. package/test/r132-release-identity.test.mjs +7 -7
  90. package/test/r153-prepublish-identity.test.mjs +1 -1
  91. package/test/r160-release-contract.test.mjs +3 -3
  92. package/test/r165-live-cli-skew.test.mjs +1 -1
  93. package/test/r167-release-contract.test.mjs +11 -11
  94. package/test/r168-release-contract.test.mjs +4 -4
  95. package/test/r172-release-contract.test.mjs +6 -6
  96. package/test/r173-release-contract.test.mjs +16 -16
  97. package/test/r179-release-contract.test.mjs +12 -12
  98. package/test/r188-owner-uac.test.mjs +48 -0
  99. package/test/r188-uac-boundary.test.mjs +42 -0
  100. package/test/r190-release-contract.test.mjs +10 -10
  101. package/test/r191-release-contract.test.mjs +12 -12
  102. package/test/r192-release-identity.test.mjs +10 -10
  103. package/test/r193-limited-migration-contract.test.mjs +26 -0
  104. package/test/r193-live-owner-subject.test.mjs +61 -0
  105. package/test/r193-native-process-observer.test.mjs +36 -0
  106. package/test/r193-native-token.test.mjs +43 -0
  107. package/test/r193-owner-enrollment-entrypoint.test.mjs +38 -0
  108. package/test/r193-owner-secret-acl.test.mjs +23 -0
  109. package/test/r193-owner-uac-broker.test.mjs +82 -0
  110. package/test/r193-owner-uac-entrypoint.test.mjs +36 -0
  111. package/test/r193-release-identity.test.mjs +58 -0
  112. package/test/r193-release-readiness.test.mjs +87 -0
  113. package/test/r193-uac-r70-bridge.test.mjs +88 -0
  114. package/test/r193-windows-gate.test.mjs +30 -0
  115. package/test/r194-npm-auth-recovery.test.mjs +77 -0
  116. package/test/r194-npm-preauth.test.mjs +57 -0
  117. package/test/r194-release-identity.test.mjs +47 -0
  118. package/test/r194-release-readiness.test.mjs +87 -0
  119. package/test/r194-windows-gate.test.mjs +22 -0
  120. package/test/r195-canonical-owner-observation.test.mjs +32 -0
  121. package/test/r195-release-readiness.test.mjs +22 -0
  122. package/test/r196-desktop-postcondition.test.mjs +53 -0
  123. package/test/r196-handle-bound-read.test.mjs +99 -0
  124. package/test/r196-remote-outcome.test.mjs +94 -0
  125. package/test/r197-namespace-race.test.mjs +41 -0
  126. package/test/r197-native-dispatch-race.test.mjs +49 -0
  127. package/test/r197-native-file-read.test.mjs +60 -0
  128. package/test/r197-native-root-open.test.mjs +61 -0
  129. package/test/r198-root-identity.test.mjs +88 -0
  130. package/test/r199-native-uia-worker.test.mjs +28 -0
  131. package/test/r199-uia-identity.test.mjs +43 -0
  132. package/test/r200-remote-reconcile.test.mjs +106 -0
  133. package/test/r201-payload-boundary.test.mjs +78 -0
  134. package/test/r201-payload-fuzz.test.mjs +37 -0
  135. package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
  136. package/test/r202-destination-journal.test.mjs +92 -0
  137. package/test/r203-atomic-journal.test.mjs +86 -0
  138. package/test/r205-candidate-selection-cli.test.mjs +31 -0
  139. package/test/r205-candidate-selection.test.mjs +83 -0
  140. package/test/r206-candidate-evidence.test.mjs +64 -0
  141. package/test/r206-release-contract.test.mjs +42 -0
  142. package/test/r206-release-readiness.test.mjs +23 -0
  143. package/test/r59-tool-surface-compat.test.mjs +1 -1
  144. package/test/r64-machine-fs-large-read.test.mjs +26 -12
@@ -0,0 +1,43 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {existsSync,readFileSync} from 'node:fs';
5
+ import {createHash} from 'node:crypto';
6
+ import path from 'node:path';
7
+ import {fileURLToPath} from 'node:url';
8
+
9
+ const root=fileURLToPath(new URL('..',import.meta.url));
10
+ const file=path.join(root,'bin','deadbyte-owner-token-r193.exe');
11
+ const binarySha='d554d732872f3d671fd2fb63c11fc0dac1f8cae377259c6d64dd0c5b3762f2b8';
12
+ const sha=x=>createHash('sha256').update(x).digest('hex');
13
+ const onlyWindows={skip:process.platform!=='win32'?'Windows token ABI required':false};
14
+
15
+ test('R193 native token inspection reports observed privilege, but never owner consent',onlyWindows,()=>{
16
+ assert.ok(existsSync(file),'compile R193 native Windows token gate before testing');
17
+ assert.equal(sha(readFileSync(file)),binarySha);
18
+ const result=spawnSync(file,['--report'],{encoding:'utf8',shell:false,timeout:10000});
19
+ assert.equal(result.status,0,result.stderr);
20
+ const v=JSON.parse(result.stdout);
21
+ assert.equal(v.schema,'deadbyte.r193-native-token.v1');
22
+ assert.equal(v.parent_independence_verified,false);
23
+ assert.equal(v.owner_approved,false);
24
+ assert.equal(v.authority_granted,false);
25
+ assert.equal(v.token_type,1);
26
+ const elevated=v.elevated||v.admin_enabled||v.integrity_rid>=12288;
27
+ assert.equal(v.status,elevated?'BLOCKED':'STANDARD_TOKEN_OBSERVED');
28
+ });
29
+
30
+ test('R193 native --require-standard denies elevated processes and honors limited tokens',onlyWindows,()=>{
31
+ const raw=spawnSync(file,['--require-standard'],{encoding:'utf8',shell:false,timeout:10000});
32
+ const v=JSON.parse(raw.stdout);
33
+ assert.equal(raw.status,v.status==='STANDARD_TOKEN_OBSERVED'?0:3,raw.stderr);
34
+ assert.equal(v.owner_approved,false);
35
+ assert.equal(v.authority_granted,false);
36
+ });
37
+
38
+ test('R193 native gate does not expose any grant subcommand',onlyWindows,()=>{
39
+ const raw=spawnSync(file,['--grant'],{encoding:'utf8',shell:false,timeout:10000});
40
+ assert.equal(raw.status,64);
41
+ assert.match(raw.stderr,/usage:/i);
42
+ assert.equal(raw.stdout,'');
43
+ });
@@ -0,0 +1,38 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {fileURLToPath} from 'node:url';
6
+ import path from 'node:path';
7
+ import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
8
+ const root=fileURLToPath(new URL('..',import.meta.url));
9
+ test('R193 one-time enrollment has fixed owner path, no key export and no grant operation',async()=>{
10
+ const keygen=await readFile(path.join(root,'scripts','r193-owner-keygen.mjs'),'utf8');
11
+ const installer=await readFile(path.join(root,'scripts','r193-owner-uac-enroll-and-approve.ps1'),'utf8');
12
+ assert.match(keygen,/const OWNER='C:\\\\Program Files\\\\DEADBYTE-OwnerAuthority'/);
13
+ assert.match(keygen,/process\.argv\.length!==2/);
14
+ assert.match(keygen,/private_key_exported:false/);
15
+ assert.doesNotMatch(keygen,/issueR186AutonomousGrant|createAuthorityEpochStore|Arm-Autonomous|armautonomous/i);
16
+ assert.match(installer,/#requires -RunAsAdministrator/);
17
+ assert.match(installer,/SetAccessRuleProtection\(\$true,\$false\)/);
18
+ assert.doesNotMatch(installer,/BuiltinUsers|S-1-5-32-545|Users.*Read/i);
19
+ assert.match(installer,/FileMode\]::CreateNew/);
20
+ assert.match(installer,/Read-Host/);
21
+ assert.doesNotMatch(installer,/Start-Process.*-Verb\s+RunAs/i);
22
+ });
23
+ test('R193 keygen refuses mutable development bytes before creating owner material',async t=>{
24
+ let trusted=false;
25
+ try{
26
+ trusted=(await inspectTrustedInstallation(privilegedEpochInstallFiles(root,process.execPath))).trusted===true;
27
+ }catch{}
28
+ if(trusted){
29
+ t.skip('protected installation: executing keygen would cross the owner-enrollment boundary');
30
+ return;
31
+ }
32
+ const cli=path.join(root,'scripts','r193-owner-keygen.mjs');
33
+ const result=spawnSync(process.execPath,[cli],{cwd:root,encoding:'utf8',windowsHide:true,
34
+ timeout:20000,maxBuffer:256*1024});
35
+ assert.equal(result.status,2);
36
+ assert.match(result.stderr,/R89 privileged installation not protected|R193/i);
37
+ assert.equal(result.stdout,'');
38
+ });
@@ -0,0 +1,23 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {evaluateR193OwnerSecretAcl} from '../src/r193-owner-secret-acl.mjs';
4
+ const admin='S-1-5-32-544',system='S-1-5-18',users='S-1-5-32-545';
5
+ const files=['C:\\Program Files\\DEADBYTE-OwnerAuthority','C:\\Program Files\\DEADBYTE-OwnerAuthority\\owner-private.der'];
6
+ const records=()=>[
7
+ {path:files[0],owner:admin,reparse:false,protected:true,directory:true,
8
+ aces:[{sid:admin,type:'Allow',mask:0x1f01ff,inherited:false},{sid:system,type:'Allow',mask:0x1f01ff,inherited:false}]},
9
+ {path:files[1],owner:admin,reparse:false,protected:true,directory:false,
10
+ aces:[{sid:admin,type:'Allow',mask:0x1f01ff,inherited:false},{sid:system,type:'Allow',mask:0x1f01ff,inherited:false}]}
11
+ ];
12
+ test('R193 owner secret ACL accepts only protected Administrator and SYSTEM access',()=>{
13
+ assert.deepEqual(evaluateR193OwnerSecretAcl(records(),files),
14
+ {trusted:true,reason:'OWNER_SECRET_ADMIN_SYSTEM_ONLY'});
15
+ });
16
+ test('R193 owner secret ACL rejects Users read, inheritance, reparse and wrong owner',()=>{
17
+ const cases=[
18
+ x=>x[1].aces.push({sid:users,type:'Allow',mask:0x120089,inherited:false}),
19
+ x=>x[1].aces[0].inherited=true,x=>x[0].reparse=true,x=>x[0].owner=users
20
+ ];
21
+ for(const mutate of cases){const value=records();mutate(value);
22
+ assert.equal(evaluateR193OwnerSecretAcl(value,files).trusted,false);}
23
+ });
@@ -0,0 +1,82 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createR188OwnerUacRequest} from '../src/r188-owner-uac.mjs';
8
+ import {verifyProjectOwnerApproval} from '../src/r70-owner-approval.mjs';
9
+ import {approveR193OwnerRequest} from '../src/r193-owner-uac-broker.mjs';
10
+
11
+ const h=c=>c.repeat(64);
12
+ async function fixture(){
13
+ const projectRoot=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r193-broker-'));
14
+ const now=new Date('2026-10-29T08:00:00.000Z');
15
+ const expected={machineEpoch:h('e'),releaseManifestSha256:h('a'),
16
+ codingPolicySha256:h('b'),autonomousPolicySha256:h('c'),
17
+ rootId:'core',projectRoot,maxSeconds:600};
18
+ const request=createR188OwnerUacRequest({...expected,now,expiresInMs:120000});
19
+ const pair=generateKeyPairSync('ed25519');
20
+ const privateKeyDer=pair.privateKey.export({format:'der',type:'pkcs8'});
21
+ const processes=['bridge','core','cloud','supervisor'].map((component,index)=>({
22
+ component,pid:1000+index,creation_time_utc:`2026-10-29T07:5${index}:00.000Z`,
23
+ alive:true,identity_verified:true,elevated:false,admin_enabled:false,
24
+ integrity_rid:8192,token_type:'primary'
25
+ }));
26
+ const boundary={schema:'deadbyte.r193-owner-runtime-boundary.v1',
27
+ broker_r89_trusted:true,key_acl_trusted:true,enable_uac:true,
28
+ secure_desktop:true,prompt_mode:'explicit_secure_desktop',
29
+ owner_gesture:'approved',live_epoch_verified:true,live_policies_verified:true,
30
+ live_release_verified:true,agent_processes:processes};
31
+ return {projectRoot,now,expected,request,pair,privateKeyDer,boundary};
32
+ }
33
+ const cleanup=f=>rm(f.projectRoot,{recursive:true,force:true});
34
+ const approve=(f,patch={})=>approveR193OwnerRequest({request:f.request,
35
+ expected:f.expected,privateKeyDer:f.privateKeyDer,
36
+ boundary:{...f.boundary,...patch},now:new Date(f.now.getTime()+1000)});
37
+
38
+ test('R193 protected UAC broker signs exact R70 approval only after a fully Limited live boundary',async()=>{
39
+ const f=await fixture();
40
+ try{
41
+ const out=approve(f);
42
+ assert.equal(out.status,'OWNER_APPROVED_FOR_SINGLE_USE_GRANT');
43
+ assert.equal(out.authority_granted,false);
44
+ assert.equal(out.owner_approved,true);
45
+ assert.equal(out.private_key_material_returned,false);
46
+ assert.equal(Object.hasOwn(out,'privateKeyDer'),false);
47
+ assert.equal(out.request_sha256,f.request.request_sha256);
48
+ const verified=await verifyProjectOwnerApproval({approval:out.approval,
49
+ ownerPublicKey:out.owner_public_key_pem,expected:{rootId:'core',
50
+ projectRoot:f.projectRoot,codingPolicySha256:h('b'),
51
+ autonomousPolicySha256:h('c'),reviewedArtifactSha256:h('a')},
52
+ now:new Date(f.now.getTime()+1000)});
53
+ assert.equal(verified.approved,true);
54
+ }finally{await cleanup(f);}
55
+ });
56
+
57
+ test('R193 broker refuses any elevated, unverified, missing, duplicate or dead agent process',async()=>{
58
+ const f=await fixture();
59
+ try{
60
+ for(const agent_processes of [
61
+ f.boundary.agent_processes.map((x,i)=>i?x:{...x,elevated:true,integrity_rid:12288}),
62
+ f.boundary.agent_processes.map((x,i)=>i?x:{...x,identity_verified:false}),
63
+ f.boundary.agent_processes.map((x,i)=>i?x:{...x,alive:false}),
64
+ f.boundary.agent_processes.slice(0,3),
65
+ [...f.boundary.agent_processes,f.boundary.agent_processes[0]]
66
+ ])assert.throws(()=>approve(f,{agent_processes}),/R193.*process|Limited|identity/i);
67
+ }finally{await cleanup(f);}
68
+ });
69
+
70
+ test('R193 broker refuses untrusted key/package, insecure UAC, absent gesture and live identity drift',async()=>{
71
+ const f=await fixture();
72
+ try{
73
+ for(const patch of [
74
+ {broker_r89_trusted:false},{key_acl_trusted:false},{enable_uac:false},
75
+ {secure_desktop:false},{prompt_mode:'silent'},{owner_gesture:'rejected'},
76
+ {live_epoch_verified:false},{live_policies_verified:false},{live_release_verified:false}
77
+ ])assert.throws(()=>approve(f,patch),/R193/);
78
+ assert.throws(()=>approveR193OwnerRequest({request:f.request,
79
+ expected:{...f.expected,machineEpoch:h('0')},privateKeyDer:f.privateKeyDer,
80
+ boundary:f.boundary,now:f.now}),/R188|R193/);
81
+ }finally{await cleanup(f);}
82
+ });
@@ -0,0 +1,36 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {fileURLToPath} from 'node:url';
6
+ import path from 'node:path';
7
+ import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
8
+ const root=fileURLToPath(new URL('..',import.meta.url));
9
+ test('R193 owner entrypoint is fixed-scope, protected and exposes no caller-selected key or state root',async()=>{
10
+ const cli=await readFile(path.join(root,'scripts','r193-owner-uac-broker.mjs'),'utf8');
11
+ const ps=await readFile(path.join(root,'scripts','r193-owner-uac-approve.ps1'),'utf8');
12
+ assert.match(cli,/const STATE='C:\\\\Users\\\\DEADBYTE\\\\\.deadbyte-mcp'/);
13
+ assert.match(cli,/const OWNER='C:\\\\Program Files\\\\DEADBYTE-OwnerAuthority'/);
14
+ assert.match(cli,/inspectTrustedInstallation/);
15
+ assert.match(cli,/verifyManifestTreeExact/);
16
+ assert.match(cli,/inspectR193OwnerSecretAcl/);
17
+ assert.match(cli,/exactOrWrite/);
18
+ assert.doesNotMatch(cli,/private-key|owner-dir|state-root/);
19
+ assert.match(ps,/#requires -RunAsAdministrator/);
20
+ assert.match(ps,/Read-Host/);
21
+ assert.doesNotMatch(ps,/Start-Process.*-Verb\s+RunAs/i);
22
+ assert.doesNotMatch(ps,/armautonomous|authority_granted\s*=\s*\$true/i);
23
+ });
24
+ test('R193 owner entrypoint fails closed before approval when its request is absent',async()=>{
25
+ let trusted=false;
26
+ try{
27
+ trusted=(await inspectTrustedInstallation(privilegedEpochInstallFiles(root,process.execPath))).trusted===true;
28
+ }catch{}
29
+ const cli=path.join(root,'scripts','r193-owner-uac-broker.mjs');
30
+ const result=spawnSync(process.execPath,[cli,'preflight','D:\\missing-request.json'],{
31
+ cwd:root,encoding:'utf8',windowsHide:true,timeout:20000,maxBuffer:512*1024});
32
+ assert.equal(result.status,2);
33
+ if(trusted)assert.match(result.stderr,/ENOENT.*missing-request\.json/i);
34
+ else assert.match(result.stderr,/R193_OWNER_UAC_BROKER_BLOCKED|R89 privileged installation not protected/i);
35
+ assert.equal(result.stdout,'');
36
+ });
@@ -0,0 +1,58 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {fileURLToPath} from 'node:url';
5
+ import path from 'node:path';
6
+
7
+ const root=fileURLToPath(new URL('..',import.meta.url));
8
+ const json=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
9
+ const parent={
10
+ version:'0.15.74',
11
+ release_id:'v0.15.74-e84bf4eed16e9443',
12
+ manifest_sha256:'e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47',
13
+ generation_manifest_sha256:'598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7'
14
+ };
15
+ const parentTarball='5da436a3f8068107e74caf6da58ff1e211d4a62f94266853136b601762f36f0c';
16
+
17
+ test('R206 package identity preserves the R193 owner boundary and separates R205 source from active R194 runtime',async()=>{
18
+ const [pkg,bridge,release,provenance,matrix,sbom,versionSource]=await Promise.all([
19
+ json('package.json'),json('bridge/package.json'),json('RELEASE.json'),json('PROVENANCE.json'),
20
+ json('TEST-MATRIX.json'),json('SBOM.cdx.json'),readFile(path.join(root,'src','version.mjs'),'utf8')]);
21
+ assert.equal(pkg.version,'0.15.75');
22
+ assert.equal(bridge.version,'0.15.75');
23
+ assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.75'/);
24
+ assert.equal(release.version,'0.15.75');
25
+ assert.equal(release.train,'R206');
26
+ for(const key of Object.keys(parent)){
27
+ assert.equal(release.parent[key],parent[key]);
28
+ assert.equal(release.runtime_predecessor[key],parent[key]);
29
+ }
30
+ assert.equal(release.source_predecessor.version,'0.15.75');
31
+ assert.equal(release.source_predecessor.train,'R205');
32
+ assert.equal(release.source_predecessor.manifest_sha256,'166eebde18d53a9ec46f738383c289733f9cd37918b3572b7515d1ab9b122c92');
33
+ assert.equal(release.source_predecessor.tarball_sha256,null);
34
+ assert.equal(provenance.version,'0.15.75');
35
+ assert.equal(provenance.parent_release_id,parent.release_id);
36
+ assert.equal(provenance.parent_manifest_sha256,parent.manifest_sha256);
37
+ assert.equal(provenance.parent_generation_manifest_sha256,parent.generation_manifest_sha256);
38
+ assert.equal(provenance.source_predecessor_version,'0.15.75');
39
+ assert.equal(provenance.source_predecessor_train,'R205');
40
+ assert.equal(provenance.source_predecessor_tarball_sha256,null);
41
+ assert.equal(matrix.version,'0.15.75');
42
+ assert.equal(matrix.train,'R206');
43
+ assert.equal(sbom.metadata.component.version,'0.15.75');
44
+ assert.match(sbom.serialNumber,/r206:0\.15\.75$/);
45
+ });
46
+
47
+ test('R193 manifest binds every protected owner-boundary implementation file',async()=>{
48
+ const manifest=await readFile(path.join(root,'MANIFEST.SHA256'),'utf8');
49
+ for(const file of [
50
+ 'bin/deadbyte-owner-token-r193.exe','native/deadbyte-owner-token-r193.cpp',
51
+ 'scripts/r193-migrate-runtime-limited.ps1','scripts/r193-owner-keygen.mjs',
52
+ 'scripts/r193-owner-request-live.mjs','scripts/r193-owner-uac-approve.ps1',
53
+ 'scripts/r193-owner-uac-broker.mjs','scripts/r193-owner-uac-enroll-and-approve.ps1',
54
+ 'src/r195-canonical-owner-observation.mjs',
55
+ 'src/r193-live-owner-subject.mjs','src/r193-owner-secret-acl.mjs',
56
+ 'src/r193-owner-uac-broker.mjs','test/r193-release-identity.test.mjs'
57
+ ]) assert.match(manifest,new RegExp(' '+file.replaceAll('.','\\.')+'(?:\\r?\\n|$)'));
58
+ });
@@ -0,0 +1,87 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import {fileURLToPath} from 'node:url';
9
+ import {assertR193NpmPublishReady,assertR193PackedIdentity} from '../scripts/npm-release-readiness-r193.mjs';
10
+
11
+ const sha256=value=>createHash('sha256').update(value).digest('hex');
12
+ const parent={
13
+ version:'0.15.72',train:'R192',release_id:'v0.15.72-0a5d9436e06e2d9e',
14
+ manifest_sha256:'0a5d9436e06e2d9e7cf9179417465955d8ca5ba88655b13dd019190243c1cc90',
15
+ generation_manifest_sha256:'aec7b998306a3bbbcc59088648e7bc46ca7e203d749d4550b5b5f372032f92b0'
16
+ };
17
+ const release={
18
+ version:'0.15.73',train:'R193',parent,
19
+ r193:{scope:'PROTECTED_OWNER_AUTHORIZATION_AND_LIMITED_RUNTIME_MIGRATION',public_tool_delta:0,authority_scope_delta:0}
20
+ };
21
+
22
+ async function fixture(){
23
+ const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r193-readiness-'));
24
+ const root=path.join(base,'development','candidate');
25
+ const evidence=path.join(base,'testing','r193-final-closure');
26
+ await mkdir(root,{recursive:true});await mkdir(evidence,{recursive:true});
27
+ const manifest=Buffer.from(`${'a'.repeat(64)} package.json\n`);
28
+ await writeFile(path.join(root,'MANIFEST.SHA256'),manifest);
29
+ const files={
30
+ 'protected-install-receipt.json':JSON.stringify({status:'PASS',package_version:'0.15.73',source_manifest_sha256:sha256(manifest),protected_manifest_sha256:sha256(manifest)}),
31
+ 'windows-focused.stdout.log':'# tests 61\n# suites 0\n# pass 60\n# fail 0\n# cancelled 0\n# skipped 1\nR193_STAGE_PASS=focused\n',
32
+ 'windows-full.stdout.log':'# tests 2187\n# suites 0\n# pass 2136\n# fail 0\n# cancelled 0\n# skipped 51\nR193_STAGE_PASS=full\n',
33
+ 'npm-verify.stdout.log':'Artifact isolation gate PASS\nVERIFIED_TARBALL {"filename":"deadbyte-mcp-0.15.73.tgz","bytes":123,"sha256":"'+ 'b'.repeat(64) +'","integrity":"sha512-test","shasum":"'+ 'c'.repeat(40) +'"}\nVERIFY-ONLY PIPELINE PASS\n'
34
+ };
35
+ for(const [name,content] of Object.entries(files))await writeFile(path.join(evidence,name),content);
36
+ const closure={schema:'deadbyte.r193-release-closure.v1',status:'RELEASE_READY',version:'0.15.73',train:'R193',
37
+ production_release_id:parent.release_id,production_manifest_sha256:parent.manifest_sha256,
38
+ production_generation_manifest_sha256:parent.generation_manifest_sha256,
39
+ manifest_sha256:sha256(manifest),compact_tools:43,full_tools:117,
40
+ owner_enrollment:'NOT_RUN',limited_runtime_migration:'NOT_RUN',production_activation:'NOT_RUN',
41
+ protected_receipt_sha256:sha256(files['protected-install-receipt.json']),
42
+ focused_stdout_sha256:sha256(files['windows-focused.stdout.log']),
43
+ full_stdout_sha256:sha256(files['windows-full.stdout.log']),
44
+ verify_stdout_sha256:sha256(files['npm-verify.stdout.log']),
45
+ tarball_sha256:'b'.repeat(64),tarball_integrity:'sha512-test',tarball_shasum:'c'.repeat(40)};
46
+ await writeFile(path.join(evidence,'closure.json'),JSON.stringify(closure));
47
+ return {base,root,evidence,closure,manifest};
48
+ }
49
+
50
+ test('R193 publication refuses absent external exact-byte closure',async()=>{
51
+ const f=await fixture();try{
52
+ await assert.rejects(assertR193NpmPublishReady(release,{root:f.root}),/PUBLISH_BLOCKED R193.*closure root absent/);
53
+ }finally{await rm(f.base,{recursive:true,force:true});}
54
+ });
55
+
56
+ test('R193 publication binds final manifest, protected gates, catalogs, and exact tarball',async()=>{
57
+ const f=await fixture();try{
58
+ const result=await assertR193NpmPublishReady(release,{root:f.root,closureRoot:f.evidence});
59
+ assert.equal(result.status,'R193_RELEASE_READY_EVIDENCE_BOUND');
60
+ assert.equal(result.tests,2187);assert.equal(result.passes,2136);assert.equal(result.skips,51);
61
+ assert.equal(result.tarball_sha256,'b'.repeat(64));
62
+ }finally{await rm(f.base,{recursive:true,force:true});}
63
+ });
64
+
65
+ test('R193 publication rejects final manifest drift and tampered gate evidence',async()=>{
66
+ const f=await fixture();try{
67
+ await writeFile(path.join(f.root,'MANIFEST.SHA256'),'changed');
68
+ await assert.rejects(assertR193NpmPublishReady(release,{root:f.root,closureRoot:f.evidence}),/PUBLISH_BLOCKED R193.*manifest drift/);
69
+ await writeFile(path.join(f.root,'MANIFEST.SHA256'),f.manifest);
70
+ await writeFile(path.join(f.evidence,'windows-full.stdout.log'),'tampered');
71
+ await assert.rejects(assertR193NpmPublishReady(release,{root:f.root,closureRoot:f.evidence}),/PUBLISH_BLOCKED R193.*evidence digest differs/);
72
+ }finally{await rm(f.base,{recursive:true,force:true});}
73
+ });
74
+
75
+ test('R193 publisher refuses a repacked artifact that differs from closure bytes',()=>{
76
+ assert.throws(()=>assertR193PackedIdentity({tarball_sha256:'a'.repeat(64),tarball_integrity:'sha512-expected',tarball_shasum:'b'.repeat(40),
77
+ observedSha256:'c'.repeat(64),observedIntegrity:'sha512-other',observedShasum:'d'.repeat(40)}),/PUBLISH_BLOCKED R193.*repacked tarball/);
78
+ });
79
+
80
+ test('canonical npm release entrypoint enforces current R195 closure before registry or pack work',async()=>{
81
+ const root=fileURLToPath(new URL('..',import.meta.url));
82
+ const env={...process.env};delete env.DEADBYTE_R193_CLOSURE_ROOT;
83
+ const result=spawnSync(process.execPath,[path.join(root,'scripts','npm-release-r48.mjs'),'--publish-only'],{cwd:root,env,encoding:'utf8',windowsHide:true});
84
+ assert.notEqual(result.status,0);
85
+ assert.match(`${result.stdout}\n${result.stderr}`,/PUBLISH_BLOCKED R206.*closure root absent/);
86
+ assert.doesNotMatch(`${result.stdout}\n${result.stderr}`,/npm publish|Packing artifact/);
87
+ });
@@ -0,0 +1,88 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createPublicKey,generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createR188OwnerUacRequest} from '../src/r188-owner-uac.mjs';
8
+ import {verifyProjectOwnerApproval} from '../src/r70-owner-approval.mjs';
9
+ import {signR193R70ApprovalFromOwnerReview} from '../src/r193-uac-r70-bridge.mjs';
10
+
11
+ const h=c=>c.repeat(64);
12
+ async function fixture(){
13
+ const projectRoot=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r193-owner-'));
14
+ const now=new Date('2026-10-29T12:00:00.000Z');
15
+ const expected={
16
+ machineEpoch:h('e'),releaseManifestSha256:h('a'),
17
+ codingPolicySha256:h('b'),autonomousPolicySha256:h('c'),
18
+ rootId:'core',projectRoot,maxSeconds:600
19
+ };
20
+ const request=createR188OwnerUacRequest({...expected,now,expiresInMs:120000});
21
+ const pair=generateKeyPairSync('ed25519');
22
+ const privateKeyDer=pair.privateKey.export({format:'der',type:'pkcs8'});
23
+ return {...expected,request,now,projectRoot,privateKeyDer,pair};
24
+ }
25
+ const cleanup=async x=>rm(x.projectRoot,{recursive:true,force:true});
26
+ const sign=x=>signR193R70ApprovalFromOwnerReview({request:x.request,expected:x,
27
+ privateKeyDer:x.privateKeyDer,now:new Date(x.now.getTime()+1000)});
28
+
29
+ test('R193 exact reviewed UAC request yields valid R70 signature without issuing a grant',async()=>{
30
+ const f=await fixture();
31
+ try{
32
+ const result=sign(f);
33
+ assert.equal(result.status,'PROTECTED_SIGNATURE_FORMAT_ONLY');
34
+ assert.equal(result.authority_granted,false);
35
+ assert.equal(result.owner_approved,false);
36
+ assert.equal(result.approval.subject.expires_at_utc,f.request.subject.expires_at_utc);
37
+ assert.equal(result.approval.subject.reviewed_artifact_sha256,f.releaseManifestSha256);
38
+ assert.equal(result.approval.subject.project_root,f.projectRoot);
39
+ const publicKey=createPublicKey({key:result.owner_public_key_spki_der,format:'der',type:'spki'});
40
+ const verified=await verifyProjectOwnerApproval({
41
+ approval:result.approval,ownerPublicKey:publicKey,
42
+ expected:{rootId:'core',projectRoot:f.projectRoot,
43
+ codingPolicySha256:f.codingPolicySha256,
44
+ autonomousPolicySha256:f.autonomousPolicySha256,
45
+ reviewedArtifactSha256:f.releaseManifestSha256},
46
+ now:new Date(f.now.getTime()+1000)
47
+ });
48
+ assert.equal(verified.approved,true);
49
+ assert.equal(verified.subject_sha256,result.approval.subject_sha256);
50
+ }finally{await cleanup(f);}
51
+ });
52
+
53
+ test('R193 signer refuses drift in machine epoch, release, policy, root and expiry',async()=>{
54
+ const f=await fixture();
55
+ try{
56
+ for(const patch of [
57
+ {machineEpoch:h('0')},{releaseManifestSha256:h('9')},
58
+ {codingPolicySha256:h('8')},{autonomousPolicySha256:h('7')},
59
+ {projectRoot:path.join(f.projectRoot,'nested')},
60
+ ])assert.throws(()=>signR193R70ApprovalFromOwnerReview({
61
+ request:f.request,expected:{...f,...patch},
62
+ privateKeyDer:f.privateKeyDer,now:f.now
63
+ }),/R188/);
64
+ assert.throws(()=>signR193R70ApprovalFromOwnerReview({
65
+ request:f.request,expected:f,privateKeyDer:f.privateKeyDer,
66
+ now:new Date(f.now.getTime()+120000)
67
+ }),/expired/i);
68
+ }finally{await cleanup(f);}
69
+ });
70
+
71
+ test('R193 signer refuses bad signing material and changed reviewed request',async()=>{
72
+ const f=await fixture();
73
+ try{
74
+ assert.throws(()=>signR193R70ApprovalFromOwnerReview({request:f.request,expected:f,privateKeyDer:Buffer.alloc(32),now:f.now}),
75
+ /R193.*key/);
76
+ assert.throws(()=>signR193R70ApprovalFromOwnerReview({
77
+ request:{...f.request,review_phrase:'APPROVE-BADBADBADBAD'},expected:f,privateKeyDer:f.privateKeyDer,now:f.now
78
+ }),/R188/);
79
+ const a=sign(f);
80
+ const wrong=generateKeyPairSync('ed25519').publicKey;
81
+ await assert.rejects(verifyProjectOwnerApproval({
82
+ approval:a.approval,ownerPublicKey:wrong,
83
+ expected:{rootId:'core',projectRoot:f.projectRoot,codingPolicySha256:f.codingPolicySha256,
84
+ autonomousPolicySha256:f.autonomousPolicySha256,reviewedArtifactSha256:f.releaseManifestSha256},
85
+ now:f.now
86
+ }),/identity mismatch/);
87
+ }finally{await cleanup(f);}
88
+ });
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {fileURLToPath} from 'node:url';
5
+ import path from 'node:path';
6
+
7
+ const root=fileURLToPath(new URL('..',import.meta.url));
8
+
9
+ test('R206 Windows gate is the canonical exact-identity prepublication gate',async()=>{
10
+ const [pkg,gate]=await Promise.all([
11
+ readFile(path.join(root,'package.json'),'utf8').then(JSON.parse),
12
+ readFile(path.join(root,'scripts','gate-windows-r206.ps1'),'utf8')
13
+ ]);
14
+ assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r206.ps1 -Stage all');
15
+ assert.match(gate,/0\.15\.75/);
16
+ assert.match(gate,/R206/);
17
+ assert.match(gate,/v0\.15\.74-e84bf4eed16e9443/);
18
+ assert.match(gate,/e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47/);
19
+ assert.match(gate,/598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7/);
20
+ assert.match(gate,/test\/r193-release-identity\.test\.mjs/);
21
+ assert.match(gate,/test\/r193-release-readiness\.test\.mjs/);
22
+ assert.match(gate,/test\/r193-owner-uac-broker\.test\.mjs/);
23
+ assert.match(gate,/test\/r70-owner-approval\.test\.mjs/);
24
+ assert.match(gate,/test\/r89-privileged-install-boundary\.test\.mjs/);
25
+ assert.match(gate,/DEADBYTE_TEST_CROSS_VOLUME_ROOT/);
26
+ assert.match(gate,/DEADBYTE_STATE_ROOT/);
27
+ assert.match(gate,/scripts\/release-manifest\.mjs','verify/);
28
+ assert.doesNotMatch(gate,/arm(process|coding|autonomous|desktop|host)/i);
29
+ assert.doesNotMatch(gate,/deploy|activate|npm publish/i);
30
+ });
@@ -0,0 +1,77 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {mkdtemp,readFile,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {reconcileNpmPublication} from '../scripts/npm-publication-r65.mjs';
8
+ import {classifyNpmPublishFailure} from '../scripts/npm-interactive-r194.mjs';
9
+
10
+ const bytes=Buffer.from('r194 immutable npm artifact');
11
+ const version='0.15.74';
12
+ const manifestSha256='d'.repeat(64);
13
+ const sha=name=>createHash(name).update(bytes).digest(name==='sha512'?'base64':'hex');
14
+ const published={version,dist:{integrity:`sha512-${sha('sha512')}`,shasum:sha('sha1'),tarball:`https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-${version}.tgz`}};
15
+
16
+ async function fixture(run){
17
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r194-auth-'));
18
+ try{return await run(path.join(root,'publication'));}finally{await rm(root,{recursive:true,force:true});}
19
+ }
20
+
21
+ function options(recordPath,overrides={}){
22
+ return {recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
23
+ publish:async()=>{},lookup:async()=>null,tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{},...overrides};
24
+ }
25
+
26
+ test('R194 classifies npm permission responses as authentication rejection without hiding transport ambiguity',()=>{
27
+ assert.equal(classifyNpmPublishFailure({exitCode:1,output:'npm error code E401\nnpm error 401 Unauthorized'}),'EAUTH');
28
+ assert.equal(classifyNpmPublishFailure({exitCode:1,output:'npm error 404 Not Found - PUT https://registry.npmjs.org/deadbyte-mcp - Not found\nThe requested resource could not be found or you do not have permission to access it.'}),'EAUTH');
29
+ assert.equal(classifyNpmPublishFailure({exitCode:1,output:'npm error code ECONNRESET\nnetwork socket disconnected'}),'OUTCOME_UNKNOWN');
30
+ });
31
+
32
+ test('R194 retries one definitively auth-rejected publication only after exact owner authentication',()=>fixture(async recordPath=>{
33
+ let publishes=0,authenticated=false,visible=false;
34
+ const base=options(recordPath,{
35
+ publish:async()=>{publishes++;if(!authenticated)throw Object.assign(new Error('permission rejected'),{code:'EAUTH'});visible=true;},
36
+ lookup:async()=>visible?published:null,
37
+ authorizeAuthRetry:async subject=>authenticated&&subject.version===version&&subject.tarball_sha256===sha('sha256')
38
+ });
39
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
40
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
41
+ assert.equal(publishes,1);
42
+ authenticated=true;
43
+ assert.equal((await reconcileNpmPublication(base)).state,'REGISTRY_IDENTITY_VERIFIED');
44
+ assert.equal(publishes,2);
45
+ const fs=await import('node:fs/promises');
46
+ const events=await Promise.all((await fs.readdir(recordPath)).filter(n=>n.startsWith('event-')).map(n=>fs.readFile(path.join(recordPath,n),'utf8').then(JSON.parse)));
47
+ assert.equal(events.filter(e=>e.state==='AUTH_RETRY_STARTED').length,1);
48
+ assert.equal(events.filter(e=>e.state==='SUBMISSION_ACCEPTED').length,1);
49
+ assert.match(await readFile(path.join(recordPath,'auth-retry-claim.json'),'utf8'),/0\.15\.74/);
50
+ }));
51
+
52
+ test('R194 never retries an ambiguous publication even after authentication becomes available',()=>fixture(async recordPath=>{
53
+ let publishes=0;
54
+ const base=options(recordPath,{publish:async()=>{publishes++;throw Object.assign(new Error('connection lost'),{code:'ECONNRESET'});},authorizeAuthRetry:async()=>true});
55
+ assert.equal((await reconcileNpmPublication(base)).state,'OUTCOME_UNKNOWN');
56
+ assert.equal((await reconcileNpmPublication(base)).state,'OUTCOME_UNKNOWN');
57
+ assert.equal(publishes,1);
58
+ }));
59
+
60
+ test('R194 auth retry is bounded to one dispatch even when authentication is rejected again',()=>fixture(async recordPath=>{
61
+ let publishes=0,allow=false;
62
+ const base=options(recordPath,{publish:async()=>{publishes++;throw Object.assign(new Error('authentication required'),{code:'EAUTH'});},authorizeAuthRetry:async()=>allow});
63
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
64
+ allow=true;
65
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
66
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
67
+ assert.equal(publishes,2);
68
+ }));
69
+
70
+ test('R194 concurrent authenticated resumes dispatch exactly one auth retry',()=>fixture(async recordPath=>{
71
+ let publishes=0,first=true,visible=false;
72
+ const base=options(recordPath,{publish:async()=>{publishes++;if(first){first=false;throw Object.assign(new Error('authentication required'),{code:'EAUTH'});}await new Promise(r=>setTimeout(r,10));visible=true;},lookup:async()=>visible?published:null,authorizeAuthRetry:async()=>true,timeoutMs:100,pollMs:1});
73
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
74
+ const results=await Promise.all([reconcileNpmPublication(base),reconcileNpmPublication(base)]);
75
+ assert.equal(publishes,2);
76
+ assert.ok(results.some(r=>r.state==='REGISTRY_IDENTITY_VERIFIED'));
77
+ }));
@@ -0,0 +1,57 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {mkdtemp,readFile,rm,stat} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {reconcileNpmPublication} from '../scripts/npm-publication-r65.mjs';
8
+
9
+ const buffer=Buffer.from('r194 no dispatch before owner npm authentication');
10
+ const version='0.15.74',manifestSha256='f'.repeat(64);
11
+ const hash=(alg,fmt='hex')=>createHash(alg).update(buffer).digest(fmt);
12
+ const published={version,dist:{integrity:'sha512-'+hash('sha512','base64'),shasum:hash('sha1'),
13
+ tarball:'https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-'+version+'.tgz'}};
14
+ const fixture=async fn=>{const tmp=await mkdtemp(path.join(os.tmpdir(),'r194-preauth-'));
15
+ try{return await fn(path.join(tmp,'publication'));}finally{await rm(tmp,{recursive:true,force:true});}};
16
+ const opts=(recordPath,over={})=>({recordPath,version,manifestSha256,localBytes:buffer,
17
+ timeoutMs:0,pollMs:1,sleep:async()=>{},publish:async()=>{throw Error('UNAUTHORIZED_DISPATCH');},
18
+ lookup:async()=>null,tags:async()=>({latest:version}),fetchTarball:async()=>buffer,...over});
19
+
20
+ test('R194 refuses initial publish without authenticated owner and creates no immutable claim',()=>fixture(async record=>{
21
+ let publishes=0;
22
+ const base=opts(record,{authorizePublish:async()=>false,publish:async()=>{publishes++;}});
23
+ assert.equal((await reconcileNpmPublication(base)).state,'AUTH_REQUIRED');
24
+ assert.equal(publishes,0);
25
+ await assert.rejects(stat(path.join(record,'claim.json')),{code:'ENOENT'});
26
+ }));
27
+
28
+ test('R194 authenticated initial publish uses one exact claim and verifies registry bytes',()=>fixture(async record=>{
29
+ let publishes=0,seen=false;
30
+ const base=opts(record,{authorizePublish:async subject=>{
31
+ assert.equal(subject.version,version);
32
+ assert.equal(subject.tarball_sha256,hash('sha256'));
33
+ assert.equal(subject.manifest_sha256,manifestSha256);
34
+ return true;
35
+ },publish:async()=>{publishes++;seen=true;},lookup:async()=>seen?published:null});
36
+ assert.equal((await reconcileNpmPublication(base)).state,'REGISTRY_IDENTITY_VERIFIED');
37
+ assert.equal((await reconcileNpmPublication(base)).state,'REGISTRY_IDENTITY_VERIFIED');
38
+ assert.equal(publishes,1);
39
+ assert.equal(JSON.parse(await readFile(path.join(record,'claim.json'),'utf8')).tarball_sha256,hash('sha256'));
40
+ }));
41
+
42
+ test('R194 owner-auth callback exception fails closed before immutable claim',()=>fixture(async record=>{
43
+ let publishes=0;
44
+ const result=await reconcileNpmPublication(opts(record,{authorizePublish:async()=>{throw Error('npm E401');},publish:async()=>{publishes++;}}));
45
+ assert.equal(result.state,'AUTH_REQUIRED');
46
+ assert.equal(publishes,0);
47
+ await assert.rejects(stat(path.join(record,'claim.json')),{code:'ENOENT'});
48
+ }));
49
+
50
+ test('R194 never replays claim with unknown original submission even after fresh authentication',()=>fixture(async record=>{
51
+ let publishes=0;
52
+ const base=opts(record,{publish:async()=>{publishes++;throw Object.assign(new Error('socket lost'),{code:'ECONNRESET'});}});
53
+ assert.equal((await reconcileNpmPublication(base)).state,'OUTCOME_UNKNOWN');
54
+ assert.equal((await reconcileNpmPublication({...base,authorizeAuthRetry:async()=>true})).state,'OUTCOME_UNKNOWN');
55
+ assert.equal(publishes,1);
56
+ await assert.rejects(stat(path.join(record,'auth-retry-claim.json')),{code:'ENOENT'});
57
+ }));