deadbyte-mcp 0.15.62 → 0.15.64

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.
@@ -0,0 +1,57 @@
1
+ #requires -Version 5.1
2
+ [CmdletBinding()]
3
+ param([ValidateSet('preflight','focused','full','manifest','doctor','all')][string]$Stage='preflight')
4
+ Set-StrictMode -Version Latest
5
+ $ErrorActionPreference='Stop'
6
+ $repo=Split-Path -Parent $PSScriptRoot
7
+ $state=Join-Path $env:USERPROFILE '.deadbyte-mcp'
8
+ $parentId='v0.15.61-055d1ff0aa99e60b'
9
+ $parentSha='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf'
10
+ $parentGen='f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4'
11
+ $nativeNode='C:\Program Files\nodejs\node.exe'
12
+ $node=if(Test-Path -LiteralPath $nativeNode -PathType Leaf){$nativeNode}else{(Get-Command node -ErrorAction Stop).Source}
13
+ function Sha([string]$file){
14
+ $sha=[Security.Cryptography.SHA256]::Create()
15
+ try{$stream=[IO.File]::OpenRead($file);try{return ([BitConverter]::ToString($sha.ComputeHash($stream))).Replace('-','').ToLowerInvariant()}finally{$stream.Dispose()}}finally{$sha.Dispose()}
16
+ }
17
+ function Run-Node([string]$label,[string[]]$nodeArgs){
18
+ if($null -eq $nodeArgs -or $nodeArgs.Count -lt 1){throw "R166_${label}_NO_NODE_ARGUMENTS"}
19
+ Write-Output "R166_STAGE_START=$label"
20
+ Write-Output "R166_STAGE_ARGS=$($nodeArgs.Count)"
21
+ $global:LASTEXITCODE=0
22
+ & $node @nodeArgs
23
+ $exit=$global:LASTEXITCODE
24
+ if($exit -ne 0){throw "R166_${label}_FAILED exit=$exit"}
25
+ Write-Output "R166_STAGE_PASS=$label"
26
+ }
27
+ Push-Location $repo
28
+ try{
29
+ $pkg=Get-Content -LiteralPath 'package.json' -Raw | ConvertFrom-Json
30
+ $release=Get-Content -LiteralPath 'RELEASE.json' -Raw | ConvertFrom-Json
31
+ if($pkg.version -ne '0.15.64' -or $release.version -ne '0.15.64' -or $release.train -ne 'R166'){throw 'R166_CANDIDATE_VERSION_DRIFT'}
32
+ if($release.runtime_predecessor.release_id -ne $parentId -or $release.runtime_predecessor.manifest_sha256 -ne $parentSha -or $release.runtime_predecessor.generation_manifest_sha256 -ne $parentGen){throw 'R166_RUNTIME_PREDECESSOR_IDENTITY_DRIFT'}
33
+ $active=Get-Content -LiteralPath (Join-Path $state 'active-release.json') -Raw | ConvertFrom-Json
34
+ if($active.version -ne '0.15.61' -or $active.release_id -ne $parentId -or $active.manifest_sha256 -ne $parentSha -or $active.generation_manifest_sha256 -ne $parentGen){throw 'R166_ACTIVE_RUNTIME_DRIFT'}
35
+ if((Sha (Join-Path $active.release_root 'MANIFEST.SHA256')) -ne $parentSha){throw 'R166_ACTIVE_MANIFEST_MISMATCH'}
36
+ Write-Output 'R166_PREDECESSOR_PREFLIGHT=PASS'
37
+ if($Stage -eq 'preflight'){return}
38
+ if($Stage -in @('focused','all')){
39
+ Run-Node -label 'focused' -nodeArgs @('--test','--test-reporter=tap','--test-concurrency=1','test/r89-privileged-install-boundary.test.mjs','test/r92-acl-ace-applicability.test.mjs','test/r153-clean-activation-recovery.test.mjs','test/r155-commander-live-log.test.mjs','test/r155-default-live-log.test.mjs','test/r156-supervisor-pacing.test.mjs','test/r157-supervisor-singleflight.test.mjs','test/r158-epoch-bootstrap.test.mjs','test/r159-legacy-marker-evidence.test.mjs','test/r160-marker-proof.test.mjs','test/r160-package-decision-manifest.test.mjs','test/r160-stable-launcher.test.mjs','test/r160-release-contract.test.mjs','test/r59-tool-surface-compat.test.mjs')
40
+ if($Stage -eq 'focused'){return}
41
+ }
42
+ if($Stage -in @('full','all')){
43
+ Run-Node -label 'full' -nodeArgs @('--test','--test-reporter=tap','--test-concurrency=4','test/*.test.mjs')
44
+ if($Stage -eq 'full'){return}
45
+ }
46
+ if($Stage -in @('manifest','all')){
47
+ Run-Node -label 'manifest' -nodeArgs @('scripts/release-manifest.mjs','verify')
48
+ if($Stage -eq 'manifest'){return}
49
+ }
50
+ if($Stage -in @('doctor','all')){
51
+ $previous=$env:DEADBYTE_STATE_ROOT
52
+ $isolated=Join-Path $env:TEMP ('deadbyte-r160-doctor-'+[guid]::NewGuid().ToString('N'))
53
+ try{[IO.Directory]::CreateDirectory($isolated)|Out-Null;$env:DEADBYTE_STATE_ROOT=$isolated;Run-Node -label 'isolated_doctor' -nodeArgs @('src/deadbyte-cli.mjs','doctor')}
54
+ finally{$env:DEADBYTE_STATE_ROOT=$previous;Remove-Item -LiteralPath $isolated -Recurse -Force -ErrorAction SilentlyContinue}
55
+ }
56
+ Write-Output 'R166_WINDOWS_CANDIDATE_GATE=PASS_PREPUBLICATION'
57
+ }finally{Pop-Location}
@@ -8,6 +8,8 @@ import {assertR71NpmPublishReady} from './npm-release-readiness-r71.mjs';
8
8
  import {assertR72NpmPublishReady} from './npm-release-readiness-r72.mjs';
9
9
  import {assertR74NpmPublishReady} from './npm-release-readiness-r74.mjs';
10
10
  import {assertR163NpmPublishReady} from './npm-release-readiness-r163.mjs';
11
+ import {assertR165NpmPublishReady} from './npm-release-readiness-r165.mjs';
12
+ import {assertR166NpmPublishReady} from './npm-release-readiness-r166.mjs';
11
13
  import { existsSync } from 'node:fs';
12
14
  import { resolveStateRoot } from '../src/state-root.mjs';
13
15
  import path from 'node:path';
@@ -100,6 +102,14 @@ async function main() {
100
102
  if (!publishOnly) throw new Error('PUBLISH_BLOCKED R163 0.15.62 requires --publish-only; production activation is a separate authority boundary');
101
103
  await assertR163NpmPublishReady(release,{root});
102
104
  }
105
+ if (!isVerifyOnly && version==='0.15.63') {
106
+ if (!publishOnly) throw new Error('PUBLISH_BLOCKED R165 0.15.63 requires --publish-only; production activation is a separate authority boundary');
107
+ await assertR165NpmPublishReady(release,{root});
108
+ }
109
+ if (!isVerifyOnly && version==='0.15.64') {
110
+ if (!publishOnly) throw new Error('PUBLISH_BLOCKED R166 requires --publish-only; activation is separate');
111
+ await assertR166NpmPublishReady(release,{root});
112
+ }
103
113
  if (release.parent?.release_id !== provenance.parent_release_id ||
104
114
  release.parent?.manifest_sha256 !== provenance.parent_manifest_sha256 ||
105
115
  release.parent?.generation_manifest_sha256 !== provenance.parent_generation_manifest_sha256) {
@@ -0,0 +1,68 @@
1
+ /** R165 0.15.63 publication fence. No evidence generation or authority mutation. */
2
+ import {createHash} from 'node:crypto';
3
+ import {readFile,lstat} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ const sha=b=>createHash('sha256').update(b).digest('hex');
7
+ const parent='v0.15.61-055d1ff0aa99e60b';
8
+ const parentManifest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
9
+ const parentGen='f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4';
10
+ const reject=(m)=>{throw new Error('PUBLISH_BLOCKED R165 '+m);};
11
+ const hex=x=>typeof x==='string'&&/^[0-9a-f]{64}$/.test(x);
12
+ const readJson=async f=>JSON.parse(await readFile(f,'utf8'));
13
+ async function checkedEvidenceFile(file,digest,{allowEmpty=false}={}){
14
+ if(!hex(digest))reject('missing exact evidence digest');
15
+ const info=await lstat(file).catch(()=>reject('evidence file missing '+file));
16
+ if(!info.isFile()||info.isSymbolicLink()||(!allowEmpty&&info.size<1)||info.size>16*1024*1024)reject('invalid evidence file');
17
+ const bytes=await readFile(file);
18
+ if(sha(bytes)!==digest)reject('evidence SHA-256 mismatch '+file);
19
+ return bytes;
20
+ }
21
+ function boundedEvidence(root,relative){
22
+ if(typeof relative!=='string'||!relative||relative.includes('\\'))reject('invalid evidence relative path');
23
+ const p=path.resolve(root,relative),r=path.relative(root,p);
24
+ if(!r||r.startsWith('..')||path.isAbsolute(r))reject('evidence path escape');
25
+ return p;
26
+ }
27
+ export async function assertR165NpmPublishReady(release,{root,evidenceRoot=process.env.DEADBYTE_R165_CLOSURE_ROOT}={}){
28
+ if(release?.name!=='deadbyte-mcp'||release?.version!=='0.15.63'||release?.train!=='R165'||release?.r165?.publication_mode!=='PUBLISH_ONLY')reject('release metadata not R165-closed');
29
+ if(release?.parent?.release_id!==parent||release?.parent?.manifest_sha256!==parentManifest||release?.parent?.generation_manifest_sha256!==parentGen||release?.runtime_predecessor?.release_id!==parent)reject('active predecessor lineage mismatch');
30
+ if(!root||!path.isAbsolute(root))reject('candidate root missing');
31
+ if(!evidenceRoot||!path.isAbsolute(evidenceRoot))reject('closure evidence path missing');
32
+ const testRoot=path.resolve(root,'..','..','testing','r165-release-closure');
33
+ const evidence=path.resolve(evidenceRoot);
34
+ const relative=path.relative(testRoot,evidence);
35
+ if(!relative||relative.startsWith('..')||path.isAbsolute(relative))reject('evidence outside bounded R165 testing root');
36
+ for(const p of [testRoot,evidence]){const st=await lstat(p).catch(()=>reject('evidence directory absent'));if(!st.isDirectory()||st.isSymbolicLink())reject('evidence directory is reparse/symlink');}
37
+ const closurePath=path.join(evidence,'closure.json');
38
+ const closureInfo=await lstat(closurePath).catch(()=>reject('closure missing'));
39
+ if(!closureInfo.isFile()||closureInfo.isSymbolicLink()||closureInfo.size<1||closureInfo.size>1024*1024)reject('closure file not regular/bounded');
40
+ const closure=await readJson(closurePath).catch(()=>reject('closure missing/invalid'));
41
+ if(closure?.schema!=='deadbyte.r165-release-closure.v1'||closure?.version!=='0.15.63'||closure?.status!=='PASS'||closure?.production_release_id!==parent)reject('closure subject/status mismatch');
42
+ const manifestSha=sha(await readFile(path.join(root,'MANIFEST.SHA256')));
43
+ if(closure.source_manifest_sha256!==manifestSha)reject('final manifest differs from closure');
44
+ const files=closure?.files;
45
+ if(!files||!['protected_receipt','protected_stdout','protected_stderr','verify_receipt','verify_stdout','verify_stderr'].every(k=>files[k]&&typeof files[k].path==='string'&&hex(files[k].sha256)))reject('required receipts missing');
46
+ const data={};
47
+ for(const [key,info] of Object.entries(files)) data[key]=await checkedEvidenceFile(boundedEvidence(testRoot,info.path),info.sha256,{allowEmpty:key.endsWith('_stderr')});
48
+ let protectedReceipt,verifyReceipt;
49
+ try{protectedReceipt=JSON.parse(data.protected_receipt);verifyReceipt=JSON.parse(data.verify_receipt);}
50
+ catch{reject('gate receipts invalid JSON');}
51
+ if(protectedReceipt?.schema!=='deadbyte.r165-protected-windows-gate.v1'||protectedReceipt?.stage!=='full'||protectedReceipt?.exit_code!==0||protectedReceipt?.source_manifest_sha256!==manifestSha||protectedReceipt?.active_release_id!==parent)reject('protected gate missing or unbound');
52
+ if(protectedReceipt.stdout_sha256!==sha(data.protected_stdout)||protectedReceipt.stderr_sha256!==sha(data.protected_stderr))reject('protected log mismatch');
53
+ const stdout=data.protected_stdout.toString('utf8');
54
+ const counts=Object.fromEntries(['tests','pass','fail','skipped'].map(k=>[k,Number(new RegExp('^# '+k+' (\\d+)\\s*$','m').exec(stdout)?.[1]??NaN)]));
55
+ if(counts.tests<1955||counts.pass<1906||counts.fail!==0||counts.skipped!==49||counts.pass+counts.skipped!==counts.tests||/^not ok\s+\d+/m.test(stdout))reject('full regression totals mismatch');
56
+ if(!/^ok\s+\d+\s+-\s+R165 isolated immutable slot activates, rolls back, and restores 0\.15\.63/m.test(stdout))reject('R165 live rollback proof missing');
57
+ if(!/^ok\s+\d+\s+-\s+R165 executable CLI passes completed R159 history/m.test(stdout))reject('R165 executable startup proof missing');
58
+ const skips=stdout.split(/\r?\n/).filter(x=>/^ok\s+\d+\s+-.*# SKIP/.test(x));
59
+ if(skips.length!==49||skips.some(x=>!/historical|R35 external semantic backend|POSIX|R100 POSIX/i.test(x)))reject('unexpected unclassified skipped test');
60
+ if(verifyReceipt?.schema!=='deadbyte.r165-release-verify.v1'||verifyReceipt?.status!=='PASS'||verifyReceipt?.version!=='0.15.63'||verifyReceipt?.source_manifest_sha256!==manifestSha||verifyReceipt?.production_release_id!==parent||verifyReceipt?.exit_code!==0)reject('verify-only receipt subject/status mismatch');
61
+ if(verifyReceipt.stdout_sha256!==sha(data.verify_stdout)||verifyReceipt.stderr_sha256!==sha(data.verify_stderr))reject('verify-only transcript hash mismatch');
62
+ const vout=data.verify_stdout.toString('utf8');
63
+ if(!vout.includes('VERIFY-ONLY PIPELINE PASS')||!vout.includes('Artifact isolation gate PASS')||!vout.includes('VERIFIED_TARBALL '))reject('npm verify-only output missing');
64
+ const tarLine=vout.split(/\r?\n/).find(x=>x.startsWith('VERIFIED_TARBALL '));
65
+ try{const p=JSON.parse(tarLine.slice('VERIFIED_TARBALL '.length));if(p.filename!=='deadbyte-mcp-0.15.63.tgz'||!hex(p.sha256)||p.bytes<1)reject('npm tarball identity invalid');}
66
+ catch{reject('npm tarball evidence invalid');}
67
+ return Object.freeze({status:'R165_RELEASE_READY_EVIDENCE_BOUND',version:'0.15.63',manifest_sha256:manifestSha,checks:6});
68
+ }
@@ -0,0 +1,37 @@
1
+ import {readFile,lstat} from 'node:fs/promises';
2
+ import {createHash} from 'node:crypto';
3
+ import path from 'node:path';
4
+ const sha=b=>createHash('sha256').update(b).digest('hex');
5
+ const required=(v,m)=>{if(!v)throw new Error('PUBLISH_BLOCKED R166 '+m)};
6
+ const hex=s=>typeof s==='string'&&/^[a-f0-9]{64}$/.test(s);
7
+ async function checkedFile(f,expected){
8
+ required(hex(expected),'missing log hash');
9
+ const s=await lstat(f);
10
+ required(s.isFile()&&!s.isSymbolicLink()&&s.size>0&&s.size<32*1024*1024,'invalid log');
11
+ const b=await readFile(f);required(sha(b)===expected,'log digest differs');return b.toString('utf8');
12
+ }
13
+ export async function assertR166NpmPublishReady(release,{root}={}){
14
+ required(release?.version==='0.15.64'&&release?.train==='R166'&&release?.r166?.scope==='WINDOWS_GATE_AND_SIGNED_PRECONDITION_RECOVERY_HOTFIX','candidate metadata invalid');
15
+ required(release?.parent?.release_id==='v0.15.61-055d1ff0aa99e60b'&&release?.source_predecessor?.version==='0.15.63','lineage mismatch');
16
+ const evidence=process.env.DEADBYTE_R166_CLOSURE_ROOT;
17
+ required(root&&path.isAbsolute(root)&&evidence&&path.isAbsolute(evidence),'closure root absent');
18
+ const allowed=path.resolve(root,'..','..','testing','r166-final-gate');
19
+ required(path.resolve(evidence).toLowerCase()===allowed.toLowerCase(),'closure root not in scoped testing folder');
20
+ const data=JSON.parse(await readFile(path.join(evidence,'closure.json'),'utf8'));
21
+ required(data?.schema==='deadbyte.r166-release-closure.v1'&&data.status==='PASS'&&data.version==='0.15.64','closure status mismatch');
22
+ required(data?.production_release_id===release.parent.release_id&&hex(data.manifest_sha256),'predecessor or manifest invalid');
23
+ required(data.manifest_sha256===sha(await readFile(path.join(root,'MANIFEST.SHA256'))),'final manifest drift');
24
+ const gate=await checkedFile(path.join(evidence,'windows-gate.protected.stdout.log'),data.gate_stdout_sha256);
25
+ const verify=await checkedFile(path.join(evidence,'npm-verify.stdout.log'),data.verify_stdout_sha256);
26
+ required(gate.includes('R166_WINDOWS_CANDIDATE_GATE=PASS_PREPUBLICATION')&&gate.includes('MANIFEST_VERIFY_OK')&&gate.includes('R166_STAGE_PASS=full'),'Windows full gate transcript incomplete');
27
+ const matches=[...gate.matchAll(/^# tests (\d+)\s*\r?\n# suites (\d+)\s*\r?\n# pass (\d+)\s*\r?\n# fail (\d+)\s*\r?\n# cancelled (\d+)\s*\r?\n# skipped (\d+)/gm)];
28
+ required(matches.length>0,'full TAP summary absent');
29
+ const full=matches.at(-1);
30
+ const tests=Number(full[1]),passes=Number(full[3]),fails=Number(full[4]),skips=Number(full[6]);
31
+ required(tests>=1960&&passes>=1908&&fails===0&&skips<=55&&passes+skips===tests,'full regression count mismatch');
32
+ required(verify.includes('VERIFY-ONLY PIPELINE PASS')&&verify.includes('Artifact isolation gate PASS')&&verify.includes('VERIFIED_TARBALL '),'exact npm tarball gate missing');
33
+ const tar=verify.split(/\r?\n/).find(x=>x.startsWith('VERIFIED_TARBALL '));
34
+ let identity;try{identity=JSON.parse(tar.slice('VERIFIED_TARBALL '.length));}catch{required(false,'tarball evidence malformed');}
35
+ required(identity.filename==='deadbyte-mcp-0.15.64.tgz'&&hex(identity.sha256)&&identity.bytes>0,'tarball subject invalid');
36
+ return Object.freeze({status:'R166_RELEASE_READY_EVIDENCE_BOUND',manifest_sha256:data.manifest_sha256,tests,passes,skips});
37
+ }
@@ -114,7 +114,7 @@ async function isSafeSignedPreconditionReject(outcome,lock,stateRoot){
114
114
  body.result!=='PRECONDITION_REJECTED'||body.phase!=='initial'||
115
115
  body.attempted_pointer!==null||body.rollback?.attempted!==false||
116
116
  !Array.isArray(body.rollback?.errors)||body.rollback.errors.length!==0||
117
- !/^host command 'powershell_script' executable pin drift detected;/.test(String(body.error?.message??''))||
117
+ !(/^(?:host command 'powershell_script' executable pin drift detected;|windows release gate package script invalid$)/.test(String(body.error?.message??'')))||
118
118
  !Number.isFinite(failedTime)||failedTime<lockTime||failedTime>observedTime||
119
119
  !samePointerIdentity(body.prior_pointer,current))return false;
120
120
  return true;
@@ -547,13 +547,33 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
547
547
  const historyAllowed=activePointer?.version===DEADBYTE_VERSION&&
548
548
  activePointer?.manifest_sha256===currentManifestSha256&&
549
549
  activePointer?.release_id===('v'+DEADBYTE_VERSION+'-'+currentManifestSha256.slice(0,16));
550
+ // R164: a different CLI version is not sufficient reason to replay a
551
+ // completed receipt. Only a verified ONLINE active predecessor may ignore
552
+ // completed history; pending, locator conflicts, and tampering still fail.
553
+ let onlinePredecessorReady=false;
554
+ if(!historyAllowed&&activePointer){
555
+ try{
556
+ const runtime=observedAuthority(ctx);
557
+ if(runtime.state==='ONLINE'){
558
+ assertRuntimeReadyObservation(runtime,{coreRoot:activePointer.release_root});
559
+ onlinePredecessorReady=true;
560
+ }
561
+ }catch{onlinePredecessorReady=false;}
562
+ }
550
563
  const selected=await selectLegacyMarkerEvidence({evidenceDir,activePointer,
551
- allowCompletedHistory:historyAllowed});
564
+ allowCompletedHistory:historyAllowed||onlinePredecessorReady});
552
565
  if(selected.kind==='none')return null;
553
566
  if(selected.kind==='completed_history_ambiguous'){
554
- if(!historyAllowed)throw new Error('R159 completed history cannot be reused across a release change');
555
- // Nothing is pending: do not select an arbitrary completed receipt.
556
- // A future upgrade still requires its own exact signed migration proof.
567
+ if(!historyAllowed){
568
+ if(!onlinePredecessorReady)throw new Error('R164 completed history requires a verified ONLINE predecessor');
569
+ const readback=await readActiveRuntimeBinding(ctx.root);
570
+ for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
571
+ if(readback?.[key]!==activePointer?.[key])
572
+ throw new Error('R164 predecessor changed during completed-history classification');
573
+ assertRuntimeReadyObservation(observedAuthority(ctx),{coreRoot:readback.release_root});
574
+ }
575
+ // Every completed receipt was validated by R159 and no pending intent
576
+ // exists. Do not adopt any receipt as an upgrade/migration authority.
557
577
  return null;
558
578
  }
559
579
  if(selected.kind==='located'){
@@ -87,7 +87,7 @@ async function currentWindowsGateScript(rootPath){
87
87
  try{ pkg=JSON.parse(await readFile(packagePath,'utf8')); }
88
88
  catch{ throw new Error('windows release gate package.json malformed'); }
89
89
  const command=String(pkg?.scripts?.['gate:windows']??'').trim();
90
- const match=/^powershell(?:\.exe)?\s+-NoProfile\s+-ExecutionPolicy\s+Bypass\s+-File\s+(scripts\/[A-Za-z0-9._\/-]+\.ps1)$/i.exec(command);
90
+ const match=/^powershell(?:\.exe)?\s+-NoProfile\s+-ExecutionPolicy\s+Bypass\s+-File\s+(scripts\/[A-Za-z0-9._\/-]+\.ps1)(?:\s+-Stage\s+all)?$/i.exec(command);
91
91
  if(!match) throw new Error('windows release gate package script invalid');
92
92
  return safeRel(match[1]);
93
93
  }
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.62';
1
+ export const DEADBYTE_VERSION = '0.15.64';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -13,13 +13,13 @@ test('R130 security patch has a new immutable package identity and truthful sour
13
13
  read('SBOM.cdx.json'),read('package-lock.json'),read('TEST-MATRIX.json')]);
14
14
  assert.equal(pkg.name,'deadbyte-mcp');
15
15
  for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
16
- assert.equal(actual,'0.15.62');
17
- assert.equal(release.train,'R160');
18
- assert.equal(matrix.train,'R160');
19
- assert.equal(release.source_predecessor.version,'0.15.61');
20
- assert.equal(release.source_predecessor.train,'R159');
21
- assert.match(release.source_predecessor.source_path,/r159-marker-reconcile/i);
22
- assert.equal(release.source_predecessor.manifest_status,'STALE_REJECTED');
16
+ assert.equal(actual,'0.15.64');
17
+ assert.equal(release.train,'R166');
18
+ assert.equal(matrix.train,'R166');
19
+ assert.equal(release.source_predecessor.version,'0.15.63');
20
+ assert.equal(release.source_predecessor.train,'R165');
21
+ assert.match(release.source_predecessor.source_path,/0\.15\.63-r165-remote-recovery/i);
22
+ assert.equal(release.source_predecessor.manifest_status,'VERIFIED_FINAL_BYTES');
23
23
  assert.equal(release.runtime_predecessor.version,'0.15.61');
24
24
  assert.equal(provenance.r160.source_predecessor_manifest_status,'STALE_REJECTED');
25
25
  assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
@@ -8,19 +8,23 @@ import {DEADBYTE_VERSION} from '../src/version.mjs';
8
8
  const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
9
9
  const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
10
10
 
11
- test('R160 package identities retain R132 historic evidence and separate the development predecessor',async()=>{
11
+ test('R166 package identity preserves R132 evidence and published source predecessor',async()=>{
12
12
  const [pkg,lock,release,prov,bom,matrix]=await Promise.all([
13
13
  read('package.json'),read('package-lock.json'),read('RELEASE.json'),
14
14
  read('PROVENANCE.json'),read('SBOM.cdx.json'),read('TEST-MATRIX.json')]);
15
15
  for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
16
16
  assert.equal(value,pkg.version);
17
17
  }
18
- assert.equal(release.train,'R160');
19
- assert.equal(matrix.train,'R160');
20
- assert.equal(release.source_predecessor.version,'0.15.61');
21
- assert.equal(release.source_predecessor.train,'R159');
22
- assert.match(release.source_predecessor.source_path,/r159-marker-reconcile/i);
18
+ assert.equal(release.train,'R166');
19
+ assert.equal(matrix.train,'R166');
20
+ assert.equal(release.source_predecessor.version,'0.15.63');
21
+ assert.equal(release.source_predecessor.train,'R165');
22
+ assert.match(release.source_predecessor.source_path,/0\.15\.63-r165-remote-recovery/i);
23
23
  assert.equal(prov.r160.source_predecessor_manifest_status,'STALE_REJECTED');
24
+ assert.equal(release.r165.published_source_manifest_sha256,'4adf7d94682fbb71c4a91eb3147ecc48076bbc5d79cb67fcb875cde6a1bb4805');
25
+ assert.equal(release.r165.published_source_tarball_sha256,'812e97609b7c51353ed25f2202129737e43bb4fbe0778c3adec43ec065352641');
26
+ assert.equal(release.source_predecessor.tarball_sha256,'3bcd89c00fca98428aab6791b545bcfd0c62323349ee9de819aae4041d170406');
27
+ assert.equal(prov.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
24
28
  assert.equal(release.runtime_predecessor.version,'0.15.61');
25
29
  assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
26
30
  assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
@@ -26,7 +26,7 @@ test('R153 historical verifier refuses current R160 package identity',async()=>{
26
26
  const state={pkg:JSON.parse(await readFile(path.join(root,'package.json'),'utf8')),
27
27
  release:JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8')),
28
28
  bridge:JSON.parse(await readFile(path.join(root,'bridge/package.json'),'utf8'))};
29
- assert.equal(state.pkg.version,'0.15.62');
29
+ assert.equal(state.pkg.version,'0.15.64');
30
30
  assert.throws(()=>verifyR153Lineage(state),/package version/);
31
31
  await assert.rejects(verifyR153IdentityTree(root),/package version/);
32
32
  });
@@ -72,7 +72,10 @@ test('R159 two unlocated completed current receipts remain ambiguous',async t=>{
72
72
  });
73
73
  test('R159 remote foreground code routes historical locators through subject selection',async()=>{
74
74
  const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
75
- assert.match(cli,/selectLegacyMarkerEvidence\(\{evidenceDir,activePointer,\s*allowCompletedHistory:historyAllowed\}\)/);
75
+ assert.match(cli,/selectLegacyMarkerEvidence\(\{evidenceDir,activePointer,\s*allowCompletedHistory:historyAllowed\|\|onlinePredecessorReady\}\)/);
76
+ assert.match(cli,/assertRuntimeReadyObservation\(runtime,\{coreRoot:activePointer\.release_root\}\)/);
77
+ assert.match(cli,/R164 predecessor changed during completed-history classification/);
78
+ assert.match(cli,/assertRuntimeReadyObservation\(observedAuthority\(ctx\),\{coreRoot:readback\.release_root\}\)/);
76
79
  assert.match(cli,/R159 active release changed during marker recovery/);
77
80
  assert.match(cli,/marker-'\+reconciled\.marker_sha256\+'\.locator\.json'/);
78
81
  assert.doesNotMatch(cli,/publishExclusiveJson\(locatorPath,locator\)/);
@@ -13,15 +13,15 @@ test('R160 package identity separates source and live runtime predecessors',asyn
13
13
  read('package.json'),read('package-lock.json'),read('bridge/package.json'),read('RELEASE.json'),
14
14
  read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
15
15
  for(const version of [pkg.version,lock.version,lock.packages[''].version,bridge.version,release.version,
16
- provenance.version,matrix.version,bom.metadata.component.version,DEADBYTE_VERSION]) assert.equal(version,'0.15.62');
17
- assert.equal(release.train,'R160');
18
- assert.equal(matrix.train,'R160');
16
+ provenance.version,matrix.version,bom.metadata.component.version,DEADBYTE_VERSION]) assert.equal(version,'0.15.64');
17
+ assert.equal(release.train,'R166');
18
+ assert.equal(matrix.train,'R166');
19
19
  assert.equal(release.runtime_predecessor.version,'0.15.61');
20
20
  assert.equal(release.runtime_predecessor.release_id,'v0.15.61-055d1ff0aa99e60b');
21
21
  assert.equal(release.runtime_predecessor.manifest_sha256,'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf');
22
22
  assert.equal(release.runtime_predecessor.generation_manifest_sha256,'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4');
23
- assert.equal(release.source_predecessor.train,'R159');
24
- assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.62-r159-marker-reconcile/i);
23
+ assert.equal(release.source_predecessor.train,'R165');
24
+ assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.63-r165-remote-recovery/i);
25
25
  assert.equal(release.r160.public_tool_delta,0);
26
26
  assert.equal(release.r160.authority_scope_delta,0);
27
27
  assert.equal(release.r160.production_activation,'NOT_RUN');
@@ -8,7 +8,7 @@ import {fileURLToPath} from 'node:url';
8
8
  import {atomicWriteActivePointer,materializeReleaseSlot,parseActivePointer,parseManifest,renderStableLauncherPs1,verifyManifestTreeExact} from '../src/release-slot.mjs';
9
9
 
10
10
  const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
11
- const version='0.15.62';
11
+ const version='0.15.64';
12
12
  const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.61-055d1ff0aa99e60b');
13
13
  const parentDigest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
14
14
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
@@ -21,7 +21,7 @@ async function generation(state,label){
21
21
  await writeFile(path.join(root,'GENERATION.SHA256'),manifest,{flag:'wx'});
22
22
  return {root,manifest_sha256:sha(manifest),config_path:path.join(root,'controller-config.psd1')};
23
23
  }
24
- test('R163 isolated immutable slot activates, rolls back, and restores 0.15.62 without authority or production mutation',async()=>{
24
+ test('R166 isolated immutable slot activates, rolls back, and restores 0.15.64 without authority or production mutation',async()=>{
25
25
  const stateBase=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r163-isolated-'));
26
26
  const state=path.join(stateBase,'state');
27
27
  try{
@@ -33,8 +33,8 @@ test('R163 isolated immutable slot activates, rolls back, and restores 0.15.62 w
33
33
  const nextDigest=sha(nextManifest);
34
34
  const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.61',manifestSha256:parentDigest});
35
35
  const nextSlot=await materializeReleaseSlot({source:candidate,stateRoot:state,version,manifestSha256:nextDigest});
36
- const pGen=await generation(state,'r163-parent');
37
- const nGen=await generation(state,'r163-next');
36
+ const pGen=await generation(state,'r165-parent');
37
+ const nGen=await generation(state,'r165-next');
38
38
  const launcherSha=sha(Buffer.from(renderStableLauncherPs1(),'utf8'));
39
39
  const pointerPath=path.join(state,'active-release.json');
40
40
  const ptr=(slot,ver,g)=>({schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:ver,
@@ -0,0 +1,13 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+
5
+ test('R164 current ONLINE predecessor permits completed-history classification, not receipt adoption',async()=>{
6
+ const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
7
+ assert.match(cli,/assertRuntimeReadyObservation\(runtime,\{coreRoot:activePointer\.release_root\}\)/);
8
+ assert.match(cli,/allowCompletedHistory:historyAllowed\|\|onlinePredecessorReady/);
9
+ assert.match(cli,/R164 predecessor changed during completed-history classification/);
10
+ assert.match(cli,/assertRuntimeReadyObservation\(observedAuthority\(ctx\),\{coreRoot:readback\.release_root\}\)/);
11
+ assert.match(cli,/if\(!onlinePredecessorReady\)throw new Error/);
12
+ assert.match(cli,/do not adopt any receipt as an upgrade\/migration authority/i);
13
+ });
@@ -0,0 +1,90 @@
1
+ // Executable foreground CLI proof. All runtime state is created under a fresh OS temp dir.
2
+ // The fake controller intentionally rejects disarmhost, BEFORE any production deployment.
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+ import {createHash} from 'node:crypto';
6
+ import {mkdtemp,mkdir,readFile,writeFile,readdir,rm} from 'node:fs/promises';
7
+ import {spawnSync} from 'node:child_process';
8
+ import path from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ import os from 'node:os';
11
+ const sha=b=>createHash('sha256').update(b).digest('hex');
12
+ const envRoot=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
13
+ const expectedManifest=null;
14
+ const predecessor={version:'0.15.61',release_id:'v0.15.61-055d1ff0aa99e60b',manifest_sha256:'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf',generation_manifest_sha256:'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4'};
15
+ const gate=process.platform==='win32'?test:test.skip;
16
+ const hashMarker=c=>c.repeat(64);
17
+ async function fixture(t,kind){
18
+ assert.equal(typeof envRoot,'string'); assert.ok(path.isAbsolute(envRoot));
19
+ assert.match(sha(await readFile(path.join(envRoot,'MANIFEST.SHA256'))),/^[0-9a-f]{64}$/);
20
+ assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.64');
21
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r164-skew-e2e-'));
22
+ t.after(async()=>{await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:150});});
23
+ const state=path.join(dir,'state'),bin=path.join(state,'bin');
24
+ const legacy=path.join(state,'evidence','legacy-migration');
25
+ const trace=path.join(dir,'controller.trace');
26
+ const releaseRoot=path.join(state,'releases','predecessor');
27
+ const generationRoot=path.join(state,'generations','predecessor');
28
+ await mkdir(bin,{recursive:true});await mkdir(legacy,{recursive:true});
29
+ const pointer={schema:'deadbyte.active-release.v1',...predecessor,release_root:releaseRoot,generation_root:generationRoot,launcher_sha256:hashMarker('9'),config_path:path.join(generationRoot,'controller-config.psd1')};
30
+ await writeFile(path.join(state,'active-release.json'),JSON.stringify(pointer)+'\n','utf8');
31
+ // The true CLI calls this local PowerShell stub; a hard stop precedes deploy.
32
+ const ps=`param([string]$Command,[switch]$Json)\n$ErrorActionPreference='Stop'\n[IO.File]::AppendAllText($env:DEADBYTE_SKEW_E2E_TRACE,($Command+[Environment]::NewLine),[Text.Encoding]::UTF8)\nif($Command -eq 'status' -and $Json){\n $auth=@{};foreach($id in @('process','coding','autonomous','desktop','host')){$auth[$id]=@{configured=$true;armed=$false;reason='disarmed'}}\n @{schema='deadbyte.controller-observation.v1';state=$env:DEADBYTE_SKEW_E2E_RUNTIME_STATE;local_ready=$true;core_root=$env:DEADBYTE_SKEW_E2E_CORE_ROOT;bridge=@{alive=$true;reason='verified'};core=@{alive=$true;reason='verified'};cloud=@{alive=$true;reason='verified'};supervisor=@{running=$true};authority=$auth}|ConvertTo-Json -Depth 6 -Compress;exit 0\n}\nif($Command -eq 'disarmhost'){[Console]::Error.WriteLine('R164_INTENTIONAL_PREDEPLOY_STOP');exit 93}\nWrite-Error ('R164_UNEXPECTED_CONTROLLER_OPERATION '+$Command);exit 99\n`;
33
+ await writeFile(path.join(bin,'DEADBYTE-MCP.ps1'),ps,'utf8');
34
+ const currentIds=['a','b'];
35
+ for(const id of currentIds){
36
+ const marker=hashMarker(id),intent={schema:'deadbyte.r153-marker-reconciliation-intent.v1',state:'RECONCILIATION_CLAIMED',marker_sha256:marker,predecessor,subject_sha256:hashMarker('7')};
37
+ const intentBytes=Buffer.from(JSON.stringify(intent)+'\n');
38
+ const terminal={schema:'deadbyte.r153-marker-reconciliation.v1',state:'MARKER_RECONCILED',marker_sha256:marker,predecessor,intent_sha256:sha(intentBytes),subject_sha256:hashMarker('8')};
39
+ await writeFile(path.join(legacy,`marker-${marker}.intent.json`),intentBytes,{flag:'wx'});
40
+ await writeFile(path.join(legacy,`marker-${marker}.terminal.json`),JSON.stringify(terminal)+'\n',{flag:'wx'});
41
+ }
42
+ if(kind==='pending'){
43
+ const marker=hashMarker('c'),intent={schema:'deadbyte.r153-marker-reconciliation-intent.v1',state:'RECONCILIATION_CLAIMED',marker_sha256:marker,predecessor,subject_sha256:hashMarker('7')};
44
+ await writeFile(path.join(legacy,`marker-${marker}.intent.json`),JSON.stringify(intent)+'\n',{flag:'wx'});
45
+ }
46
+ if(kind==='tampered'){
47
+ const p=path.join(legacy,`marker-${hashMarker('b')}.terminal.json`);
48
+ const terminal=JSON.parse(await readFile(p,'utf8'));
49
+ terminal.intent_sha256=hashMarker('0');
50
+ await writeFile(p,JSON.stringify(terminal)+'\n');
51
+ }
52
+ const before=await Promise.all((await readdir(legacy)).sort().map(async name=>[name,sha(await readFile(path.join(legacy,name)))]));
53
+ const env={...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_SKEW_E2E_TRACE:trace,DEADBYTE_SKEW_E2E_CORE_ROOT:releaseRoot,DEADBYTE_SKEW_E2E_RUNTIME_STATE:kind==='offline'?'OFFLINE':'ONLINE'};
54
+ return {dir,state,legacy,trace,env,before};
55
+ }
56
+ async function run(fx){
57
+ const cli=path.join(envRoot,'src','deadbyte-cli.mjs');
58
+ const result=spawnSync(process.execPath,[cli,'remote'],{env:fx.env,cwd:fx.dir,windowsHide:true,shell:false,encoding:'utf8',timeout:45000,maxBuffer:2*1024*1024});
59
+ if(result.error)throw result.error;
60
+ assert.equal(result.status,1,'expected hard stop BEFORE deploy');
61
+ const after=await Promise.all((await readdir(fx.legacy)).sort().map(async name=>[name,sha(await readFile(path.join(fx.legacy,name)))]));
62
+ assert.deepEqual(after,fx.before,'reconciliation evidence mutated');
63
+ await assert.rejects(readFile(path.join(fx.state,'runtime','remote-session.json')),{code:'ENOENT'});
64
+ let trace='';try{trace=await readFile(fx.trace,'utf8');}catch(e){if(e.code!=='ENOENT')throw e;}
65
+ assert.doesNotMatch(trace,/\b(?:start|restart|armprocess|armcoding|armautonomous|deploy)\b/i,'unexpected runtime operation');
66
+ return {output:(result.stderr||'')+'\n'+(result.stdout||''),trace};
67
+ }
68
+ gate('R165 executable CLI passes completed R159 history, reaches 0.15.61 -> 0.15.64 synchronization safely',async t=>{
69
+ const fx=await fixture(t,'completed');
70
+ const r=await run(fx);
71
+ assert.match(r.output,/runtime coherence 'disarmhost' failed exit=93/i,r.output);
72
+ assert.doesNotMatch(r.output,/R159 ambiguous multiple completed/i);
73
+ assert.match(r.trace,/disarmhost/);
74
+ assert.doesNotMatch(r.trace,/disarmdesktop/);
75
+ });
76
+ gate('R165 executable CLI rejects pending current reconciliation before upgrade',async t=>{
77
+ const fx=await fixture(t,'pending'),r=await run(fx);
78
+ assert.match(r.output,/pending marker reconciliation requires an offline runtime/i,r.output);
79
+ assert.doesNotMatch(r.trace,/disarmhost/);
80
+ });
81
+ gate('R165 executable CLI rejects tampered terminal before upgrade',async t=>{
82
+ const fx=await fixture(t,'tampered'),r=await run(fx);
83
+ assert.match(r.output,/terminal intent\/subject mismatch|terminal.*mismatch/i,r.output);
84
+ assert.doesNotMatch(r.trace,/disarmhost/);
85
+ });
86
+ gate('R165 executable CLI keeps ambiguous history fail-closed when predecessor is not ONLINE',async t=>{
87
+ const fx=await fixture(t,'offline'),r=await run(fx);
88
+ assert.match(r.output,/R159 ambiguous multiple completed reconciliation receipts/i,r.output);
89
+ assert.doesNotMatch(r.trace,/disarmhost/);
90
+ });
@@ -0,0 +1,13 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {assertR165NpmPublishReady} from '../scripts/npm-release-readiness-r165.mjs';
6
+ const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
7
+ const release={name:'deadbyte-mcp',version:'0.15.63',train:'R165',r165:{publication_mode:'PUBLISH_ONLY'},parent:{release_id:'v0.15.61-055d1ff0aa99e60b',manifest_sha256:'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf',generation_manifest_sha256:'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4'},runtime_predecessor:{release_id:'v0.15.61-055d1ff0aa99e60b'}};
8
+ test('R165 publish gate fails closed with missing evidence',async()=>{
9
+ await assert.rejects(()=>assertR165NpmPublishReady(release,{root,evidenceRoot:null}),/PUBLISH_BLOCKED R165/);
10
+ });
11
+ test('R165 publish gate rejects changed parent identity before evidence read',async()=>{
12
+ await assert.rejects(()=>assertR165NpmPublishReady({...release,parent:{...release.parent,release_id:'v0.15.60-wrong'}},{root}),/predecessor lineage mismatch/);
13
+ });
@@ -0,0 +1,27 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ const release = await readFile(new URL('../src/release-generation.mjs',import.meta.url),'utf8');
5
+ const gate = await readFile(new URL('../scripts/gate-windows-r166.ps1',import.meta.url),'utf8');
6
+ const pkg = JSON.parse(await readFile(new URL('../package.json',import.meta.url),'utf8'));
7
+ const lock = await readFile(new URL('../src/activation-lock-r65.mjs',import.meta.url),'utf8');
8
+ test('exact Windows gate grammar supports only optional Stage all',()=>{
9
+ const line = release.split('\n').find(x=>x.includes('const match=/^powershell'));
10
+ assert.ok(line);
11
+ const regexSource=line.slice(line.indexOf('const match=')+12,line.lastIndexOf('.exec(command)'));
12
+ const regex=Function(`return ${regexSource}`)();
13
+ assert.ok(regex.test(pkg.scripts['gate:windows']));
14
+ assert.equal(regex.test(pkg.scripts['gate:windows']+'; echo injected'),false);
15
+ assert.equal(regex.test(pkg.scripts['gate:windows'].replace('all','focused')),false);
16
+ });
17
+ test('corrected gate matches candidate identity',()=>{
18
+ assert.match(gate,/R166_CANDIDATE_VERSION_DRIFT/);
19
+ assert.match(gate,/\$pkg.version -ne '0.15.64'/);
20
+ assert.match(gate,/\$release.train -ne 'R166'/);
21
+ });
22
+ test('recovery is restricted to exact recognized signed preconditions',()=>{
23
+ assert.match(lock,/windows release gate package script invalid\$/);
24
+ assert.match(lock,/verifySlotUpdateEnvelope\(signed,publicKey\)/);
25
+ assert.match(lock,/body.phase!=='initial'/);
26
+ assert.match(lock,/body.attempted_pointer!==null/);
27
+ });
@@ -0,0 +1,10 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {assertR166NpmPublishReady} from '../scripts/npm-release-readiness-r166.mjs';
5
+ test('R166 publication refuses absent external closure',async()=>{
6
+ const release=JSON.parse(await readFile(new URL('../RELEASE.json',import.meta.url)));
7
+ const old=process.env.DEADBYTE_R166_CLOSURE_ROOT;
8
+ try{delete process.env.DEADBYTE_R166_CLOSURE_ROOT;await assert.rejects(assertR166NpmPublishReady(release,{root:new URL('..',import.meta.url).pathname}),/PUBLISH_BLOCKED R166/);}
9
+ finally{if(old===undefined)delete process.env.DEADBYTE_R166_CLOSURE_ROOT;else process.env.DEADBYTE_R166_CLOSURE_ROOT=old;}
10
+ });
@@ -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'].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'].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');