deadbyte-mcp 0.15.72 → 0.15.74

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 (76) hide show
  1. package/MANIFEST.SHA256 +75 -27
  2. package/PROVENANCE.json +42 -14
  3. package/README.txt +24 -18
  4. package/RELEASE.json +52 -18
  5. package/SBOM.cdx.json +14 -2
  6. package/TEST-MATRIX.json +30 -2
  7. package/bin/deadbyte-owner-token-r193.exe +0 -0
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/R184-package-lock.json +2 -2
  11. package/native/deadbyte-owner-token-r193.cpp +156 -0
  12. package/package.json +2 -2
  13. package/scripts/gate-windows-r193.ps1 +108 -0
  14. package/scripts/gate-windows-r194.ps1 +112 -0
  15. package/scripts/npm-interactive-r194.mjs +13 -0
  16. package/scripts/npm-publication-r65.mjs +64 -4
  17. package/scripts/npm-release-r48.mjs +58 -11
  18. package/scripts/npm-release-readiness-r193.mjs +80 -0
  19. package/scripts/npm-release-readiness-r194.mjs +80 -0
  20. package/scripts/r188-owner-uac-review.ps1 +91 -0
  21. package/scripts/r188-uac-review-preview.mjs +65 -0
  22. package/scripts/r193-compile-token.ps1 +22 -0
  23. package/scripts/r193-evidence-relocate.ps1 +12 -0
  24. package/scripts/r193-focused-gate.mjs +20 -0
  25. package/scripts/r193-import-r188-scripts.ps1 +14 -0
  26. package/scripts/r193-limited-subject-probe.ps1 +25 -0
  27. package/scripts/r193-migrate-runtime-limited.ps1 +103 -0
  28. package/scripts/r193-owner-keygen.mjs +43 -0
  29. package/scripts/r193-owner-request-live.mjs +50 -0
  30. package/scripts/r193-owner-uac-approve.ps1 +42 -0
  31. package/scripts/r193-owner-uac-broker.mjs +139 -0
  32. package/scripts/r193-owner-uac-enroll-and-approve.ps1 +62 -0
  33. package/scripts/r193-test-limited-token.ps1 +49 -0
  34. package/scripts/r193-toolchain-inventory.ps1 +7 -0
  35. package/scripts/r193-uac-bridge-focused.mjs +21 -0
  36. package/src/r188-owner-uac.mjs +105 -0
  37. package/src/r188-uac-boundary.mjs +21 -0
  38. package/src/r193-live-owner-subject.mjs +110 -0
  39. package/src/r193-owner-secret-acl.mjs +67 -0
  40. package/src/r193-owner-uac-broker.mjs +85 -0
  41. package/src/r193-uac-r70-bridge.mjs +54 -0
  42. package/src/version.mjs +1 -1
  43. package/test/process-policy-refresh.test.mjs +1 -1
  44. package/test/r130-release-security-contract.test.mjs +8 -8
  45. package/test/r132-release-identity.test.mjs +7 -7
  46. package/test/r153-prepublish-identity.test.mjs +1 -1
  47. package/test/r160-release-contract.test.mjs +3 -3
  48. package/test/r165-live-cli-skew.test.mjs +1 -1
  49. package/test/r167-release-contract.test.mjs +11 -11
  50. package/test/r168-release-contract.test.mjs +4 -4
  51. package/test/r172-release-contract.test.mjs +6 -6
  52. package/test/r173-release-contract.test.mjs +16 -16
  53. package/test/r179-release-contract.test.mjs +11 -11
  54. package/test/r188-owner-uac.test.mjs +48 -0
  55. package/test/r188-uac-boundary.test.mjs +42 -0
  56. package/test/r190-release-contract.test.mjs +10 -10
  57. package/test/r191-release-contract.test.mjs +12 -12
  58. package/test/r192-release-identity.test.mjs +10 -10
  59. package/test/r193-limited-migration-contract.test.mjs +26 -0
  60. package/test/r193-live-owner-subject.test.mjs +61 -0
  61. package/test/r193-native-process-observer.test.mjs +36 -0
  62. package/test/r193-native-token.test.mjs +43 -0
  63. package/test/r193-owner-enrollment-entrypoint.test.mjs +38 -0
  64. package/test/r193-owner-secret-acl.test.mjs +23 -0
  65. package/test/r193-owner-uac-broker.test.mjs +82 -0
  66. package/test/r193-owner-uac-entrypoint.test.mjs +36 -0
  67. package/test/r193-release-identity.test.mjs +56 -0
  68. package/test/r193-release-readiness.test.mjs +87 -0
  69. package/test/r193-uac-r70-bridge.test.mjs +88 -0
  70. package/test/r193-windows-gate.test.mjs +30 -0
  71. package/test/r194-npm-auth-recovery.test.mjs +77 -0
  72. package/test/r194-npm-preauth.test.mjs +57 -0
  73. package/test/r194-release-identity.test.mjs +43 -0
  74. package/test/r194-release-readiness.test.mjs +87 -0
  75. package/test/r194-windows-gate.test.mjs +21 -0
  76. package/test/r59-tool-surface-compat.test.mjs +1 -1
@@ -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 R193 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 R194.*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('R194 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-r194.ps1'),'utf8')
13
+ ]);
14
+ assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r194.ps1 -Stage all');
15
+ assert.match(gate,/0\.15\.74/);
16
+ assert.match(gate,/R194/);
17
+ assert.match(gate,/v0\.15\.72-0a5d9436e06e2d9e/);
18
+ assert.match(gate,/0a5d9436e06e2d9e7cf9179417465955d8ca5ba88655b13dd019190243c1cc90/);
19
+ assert.match(gate,/aec7b998306a3bbbcc59088648e7bc46ca7e203d749d4550b5b5f372032f92b0/);
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
+ }));
@@ -0,0 +1,43 @@
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
+ const root=fileURLToPath(new URL('..',import.meta.url));
7
+ const json=async n=>JSON.parse(await readFile(path.join(root,n),'utf8'));
8
+ test('R194 exact release version and predecessor bind active R192 but frozen local R193 source',async()=>{
9
+ const [pkg,bridge,release,provenance,matrix,sbom,lock,ver]=await Promise.all([
10
+ json('package.json'),json('bridge/package.json'),json('RELEASE.json'),json('PROVENANCE.json'),
11
+ json('TEST-MATRIX.json'),json('SBOM.cdx.json'),json('docs/R184-package-lock.json'),
12
+ readFile(path.join(root,'src','version.mjs'),'utf8')]);
13
+ for(const v of [pkg.version,bridge.version,release.version,provenance.version,matrix.version,
14
+ sbom.metadata.component.version,lock.version,lock.packages[''].version])assert.equal(v,'0.15.74');
15
+ assert.match(ver,/DEADBYTE_VERSION = '0\.15\.74'/);
16
+ assert.equal(release.train,'R194');assert.equal(matrix.train,'R194');
17
+ assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r194:0.15.74');
18
+ assert.equal(release.parent.release_id,'v0.15.72-0a5d9436e06e2d9e');
19
+ assert.equal(release.runtime_predecessor.release_id,release.parent.release_id);
20
+ assert.equal(release.source_predecessor.version,'0.15.73');
21
+ assert.equal(release.source_predecessor.train,'R193');
22
+ assert.equal(release.source_predecessor.release_status,'FROZEN_R193_RELEASE_READY_NOT_PUBLISHED');
23
+ assert.equal(release.source_predecessor.manifest_sha256,'e0b3f8b333f1c1260a97b178d1a8beff5742355296a9fe7daba378612604a3bd');
24
+ assert.equal(release.source_predecessor.tarball_sha256,'13c3dd23a535609aa53173b328c7d9299d5dcada684f3253a88af6a4b300c534');
25
+ assert.equal(provenance.source_predecessor_version,'0.15.73');
26
+ assert.equal(provenance.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
27
+ assert.equal(release.r194.scope,'NPM_AUTH_RECOVERY_AND_OWNER_AUTHORIZATION');
28
+ assert.equal(release.r194.npm_publish,'NOT_RUN');
29
+ assert.equal(release.r194.production_activation,'NOT_RUN');
30
+ });
31
+ test('R194 canonical publisher binds R194 readiness before registry/pack effects',async()=>{
32
+ const [entry,readiness]=await Promise.all([
33
+ readFile(path.join(root,'scripts/npm-release-r48.mjs'),'utf8'),
34
+ readFile(path.join(root,'scripts/npm-release-readiness-r194.mjs'),'utf8')]);
35
+ assert.match(entry,/assertR194NpmPublishReady/);
36
+ assert.match(entry,/if\(r194Readiness\)assertR194PackedIdentity/);
37
+ assert.match(entry,/authorizePublish:ownerAuth,authorizeAuthRetry:ownerAuth/);
38
+ assert.match(entry,/version==='0\.15\.74'/);
39
+ assert.match(entry,/if \(!publishOnly\)/);
40
+ assert.match(readiness,/PUBLISH_BLOCKED R194/);
41
+ assert.match(readiness,/DEADBYTE_R194_CLOSURE_ROOT/);
42
+ assert.match(readiness,/production_activation/);
43
+ });
@@ -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 {assertR194NpmPublishReady,assertR194PackedIdentity} from '../scripts/npm-release-readiness-r194.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.74',train:'R194',parent,
19
+ r194:{scope:'NPM_AUTH_RECOVERY_AND_OWNER_AUTHORIZATION',public_tool_delta:0,authority_scope_delta:0}
20
+ };
21
+
22
+ async function fixture(){
23
+ const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r194-readiness-'));
24
+ const root=path.join(base,'development','candidate');
25
+ const evidence=path.join(base,'testing','r194-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.74',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\nR194_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\nR194_STAGE_PASS=full\n',
33
+ 'npm-verify.stdout.log':'Artifact isolation gate PASS\nVERIFIED_TARBALL {"filename":"deadbyte-mcp-0.15.74.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.r194-release-closure.v1',status:'RELEASE_READY',version:'0.15.74',train:'R194',
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('R194 publication refuses absent external exact-byte closure',async()=>{
51
+ const f=await fixture();try{
52
+ await assert.rejects(assertR194NpmPublishReady(release,{root:f.root}),/PUBLISH_BLOCKED R194.*closure root absent/);
53
+ }finally{await rm(f.base,{recursive:true,force:true});}
54
+ });
55
+
56
+ test('R194 publication binds final manifest, protected gates, catalogs, and exact tarball',async()=>{
57
+ const f=await fixture();try{
58
+ const result=await assertR194NpmPublishReady(release,{root:f.root,closureRoot:f.evidence});
59
+ assert.equal(result.status,'R194_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('R194 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(assertR194NpmPublishReady(release,{root:f.root,closureRoot:f.evidence}),/PUBLISH_BLOCKED R194.*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(assertR194NpmPublishReady(release,{root:f.root,closureRoot:f.evidence}),/PUBLISH_BLOCKED R194.*evidence digest differs/);
72
+ }finally{await rm(f.base,{recursive:true,force:true});}
73
+ });
74
+
75
+ test('R194 publisher refuses a repacked artifact that differs from closure bytes',()=>{
76
+ assert.throws(()=>assertR194PackedIdentity({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 R194.*repacked tarball/);
78
+ });
79
+
80
+ test('canonical npm release entrypoint enforces R194 closure before registry or pack work',async()=>{
81
+ const root=fileURLToPath(new URL('..',import.meta.url));
82
+ const env={...process.env};delete env.DEADBYTE_R194_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 R194.*closure root absent/);
86
+ assert.doesNotMatch(`${result.stdout}\n${result.stderr}`,/npm publish|Packing artifact/);
87
+ });
@@ -0,0 +1,21 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {fileURLToPath} from 'node:url';
6
+ const root=fileURLToPath(new URL('..',import.meta.url));
7
+ test('R194 prepublication Windows gate keeps parent pinned and current R194 identity',async()=>{
8
+ const [pkg,gate]=await Promise.all([
9
+ readFile(path.join(root,'package.json'),'utf8').then(JSON.parse),
10
+ readFile(path.join(root,'scripts/gate-windows-r194.ps1'),'utf8')]);
11
+ assert.equal(pkg.scripts['gate:windows'],
12
+ 'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r194.ps1 -Stage all');
13
+ assert.match(gate,/0\.15\.74/);assert.match(gate,/R194/);
14
+ assert.match(gate,/v0\.15\.72-0a5d9436e06e2d9e/);
15
+ assert.match(gate,/aec7b998306a3bbbcc59088648e7bc46ca7e203d749d4550b5b5f372032f92b0/);
16
+ assert.match(gate,/test\/r194-release-identity\.test\.mjs/);
17
+ assert.match(gate,/test\/r194-npm-auth-recovery\.test\.mjs/);
18
+ assert.match(gate,/test\/r193-owner-uac-broker\.test\.mjs/);
19
+ assert.doesNotMatch(gate,/arm(process|coding|autonomous|desktop|host)/i);
20
+ assert.doesNotMatch(gate,/deploy|activate|npm publish/i);
21
+ });
@@ -83,7 +83,7 @@ test('R72 compact baseline permits only R64 read offset and authorized coding-on
83
83
  assert.deepEqual(diff.added,[]);
84
84
  assert.deepEqual(diff.removed,[]);
85
85
  const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
86
- if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61','0.15.62','0.15.63','0.15.64','0.15.65','0.15.66','0.15.67','0.15.68','0.15.69','0.15.70','0.15.71','0.15.72'].includes(packageVersion)){
86
+ if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61','0.15.62','0.15.63','0.15.64','0.15.65','0.15.66','0.15.67','0.15.68','0.15.69','0.15.70','0.15.71','0.15.72','0.15.73','0.15.74'].includes(packageVersion)){
87
87
  assert.deepEqual([...diff.schema_changed].sort(),['machine_fs','native_diff_preview']);
88
88
  assert.equal(diff.unchanged.length,41);
89
89
  const prior=baseline.tools.find(tool=>tool.name==='machine_fs');