deadbyte-mcp 0.15.38 → 0.15.39

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,45 @@
1
+ param(
2
+ [ValidateSet('all','preflight','focused','full','manifest','doctor')]
3
+ [string]$Stage='all'
4
+ )
5
+
6
+ $ErrorActionPreference='Stop'
7
+ Set-StrictMode -Version 3.0
8
+ $root=Split-Path $PSScriptRoot -Parent
9
+ Set-Location -LiteralPath $root
10
+
11
+ function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
12
+ Write-Host "==== $Name ===="
13
+ & npm.cmd @NpmArgs
14
+ if($LASTEXITCODE -ne 0){ throw "R62 prepublish gate failed at $Name exit=$LASTEXITCODE" }
15
+ }
16
+
17
+ $stateRoot=if(-not [string]::IsNullOrWhiteSpace($env:DEADBYTE_STATE_ROOT)){
18
+ [IO.Path]::GetFullPath($env:DEADBYTE_STATE_ROOT)
19
+ }else{ Join-Path $env:USERPROFILE '.deadbyte-mcp' }
20
+ $pointer=Join-Path $stateRoot 'active-release.json'
21
+ if(-not (Test-Path -LiteralPath $pointer -PathType Leaf)){ throw "R62 predecessor pointer missing: $pointer" }
22
+ $active=Get-Content -LiteralPath $pointer -Raw | ConvertFrom-Json
23
+ if($active.release_id -ne 'v0.15.38-917901b3fcb2a77f'){ throw "R62 predecessor release mismatch: $($active.release_id)" }
24
+ if($active.manifest_sha256 -ne '917901b3fcb2a77f696fedc539bd97c567d9dc59345779906fc633d11ea4bcfb'){ throw 'R62 predecessor manifest mismatch' }
25
+ if($active.generation_manifest_sha256 -ne '9d320a8af1ba8e9e6a23552f35263da2670c00af1355464820552d849d9033c6'){ throw 'R62 predecessor generation mismatch' }
26
+ if($Stage -eq 'preflight'){Write-Host '==== R62 WINDOWS GATE: PASS preflight ====';return}
27
+
28
+ if($Stage -eq 'all' -or $Stage -eq 'focused'){
29
+ Invoke-Npm 'r62-focused' @('run','r62:focused:test')
30
+ if($Stage -eq 'focused'){Write-Host '==== R62 WINDOWS GATE: PASS focused ====';return}
31
+ }
32
+ if($Stage -eq 'all' -or $Stage -eq 'full'){
33
+ Invoke-Npm 'full-regression' @('test')
34
+ if($Stage -eq 'full'){Write-Host '==== R62 WINDOWS GATE: PASS full ====';return}
35
+ }
36
+ if($Stage -eq 'all' -or $Stage -eq 'manifest'){
37
+ Invoke-Npm 'manifest-write' @('run','manifest:write')
38
+ Invoke-Npm 'manifest-verify' @('run','manifest:verify')
39
+ if($Stage -eq 'manifest'){Write-Host '==== R62 WINDOWS GATE: PASS manifest ====';return}
40
+ }
41
+ if($Stage -eq 'all' -or $Stage -eq 'doctor'){
42
+ Invoke-Npm 'doctor' @('run','doctor')
43
+ if($Stage -eq 'doctor'){Write-Host '==== R62 WINDOWS GATE: PASS doctor ====';return}
44
+ }
45
+ Write-Host '==== R62 PREPUBLISH WINDOWS GATE: PASS ===='
@@ -0,0 +1,9 @@
1
+ $ErrorActionPreference='Stop'
2
+ $root=Split-Path -Parent $PSScriptRoot
3
+ Push-Location $root
4
+ try {
5
+ & npm.cmd run r62:focused:test
6
+ if($LASTEXITCODE -ne 0){ exit $LASTEXITCODE }
7
+ } finally {
8
+ Pop-Location
9
+ }
@@ -0,0 +1,9 @@
1
+ $ErrorActionPreference='Stop'
2
+ $root=Split-Path -Parent $PSScriptRoot
3
+ Push-Location $root
4
+ try {
5
+ & npm.cmd test
6
+ $code=$LASTEXITCODE
7
+ Write-Host ("R62_FULL_EXIT="+$code)
8
+ exit $code
9
+ } finally { Pop-Location }
@@ -0,0 +1,17 @@
1
+ import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
2
+
3
+ const result=await inspectDoctorTransactions();
4
+ const summary=result.findings.find(item=>item.evidence?.receipt_verification_performed===true)?.evidence??null;
5
+ const unresolved=result.findings
6
+ .filter(item=>item.level==='WARN'&&item.evidence?.recorded_phase)
7
+ .map(item=>({schema:item.evidence.schema,phase:item.evidence.recorded_phase,state_sha256:item.evidence.state_sha256}));
8
+ const phases={};
9
+ for(const item of unresolved) phases[item.phase]=(phases[item.phase]??0)+1;
10
+ console.log(JSON.stringify({
11
+ status:result.status,
12
+ summary,
13
+ unresolved_count:unresolved.length,
14
+ unresolved_phases:phases,
15
+ fail_count:result.findings.filter(item=>item.level==='FAIL').length,
16
+ inventory_warning:result.findings.some(item=>item.evidence?.inventory_complete===false)
17
+ },null,2));
@@ -0,0 +1,54 @@
1
+ import {opendir,lstat,readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import {createHash} from 'node:crypto';
5
+
6
+ const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
7
+ const root=path.join(state,'evidence','updates');
8
+ const sha=b=>createHash('sha256').update(b).digest('hex');
9
+ const terminal=new Set(['completed','superseded','failed_receipted']);
10
+ const rows=[];
11
+ for await(const entry of await opendir(root)){
12
+ if(!entry.isDirectory()||entry.isSymbolicLink()) continue;
13
+ const dir=path.join(root,entry.name),stateFile=path.join(dir,'state.json');
14
+ let stateBytes,body;
15
+ try{
16
+ const info=await lstat(stateFile);
17
+ if(!info.isFile()||info.isSymbolicLink()||info.size>1048576) continue;
18
+ stateBytes=await readFile(stateFile);
19
+ body=JSON.parse(stateBytes.toString('utf8').replace(/^\uFEFF/,''));
20
+ }catch{continue;}
21
+ if(!['deadbyte.r27-slot-update-state.v1','deadbyte.r31-slot-rollback-state.v1'].includes(body?.schema)) continue;
22
+ const has=async name=>{try{const st=await lstat(path.join(dir,name));return st.isFile()&&!st.isSymbolicLink();}catch{return false;}};
23
+ if(terminal.has(body.phase)||await has('receipt.json')||await has('failure-receipt.json')) continue;
24
+ const handoff=await has('handoff.json');
25
+ let handoffSummary=null;
26
+ if(handoff){
27
+ try{
28
+ const h=JSON.parse((await readFile(path.join(dir,'handoff.json'),'utf8')).replace(/^\uFEFF/,''));
29
+ handoffSummary={schema:h?.body?.schema??h?.schema??null,body:h?.body??h};
30
+ }catch(error){handoffSummary={parse_error:String(error?.message??error)};}
31
+ }
32
+ rows.push({
33
+ entry:entry.name,
34
+ entry_sha256:sha(Buffer.from(entry.name,'utf8')),
35
+ state_sha256:sha(stateBytes),
36
+ schema:body.schema,
37
+ phase:body.phase,
38
+ update_id:body.update_id??null,
39
+ rollback_id:body.rollback_id??null,
40
+ prior_pointer:body.prior_pointer??null,
41
+ new_pointer:body.new_pointer??null,
42
+ active_pointer:body.active_pointer??null,
43
+ old_runtime:body.old_runtime??null,
44
+ new_runtime:body.new_runtime??null,
45
+ error:body.error??null,
46
+ recovery:body.recovery??null,
47
+ has_handoff:handoff,
48
+ handoff:handoffSummary
49
+ });
50
+ }
51
+ rows.sort((a,b)=>a.entry.localeCompare(b.entry));
52
+ let active=null;
53
+ try{active=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));}catch{}
54
+ console.log(JSON.stringify({state_root:state,active_pointer:active,unresolved_count:rows.length,rows},null,2));
@@ -1,10 +1,37 @@
1
1
  import {lstat,opendir,readFile} from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import {createHash} from 'node:crypto';
3
+ import {createHash,createPublicKey} from 'node:crypto';
4
4
  import {resolveStateRoot} from './state-root.mjs';
5
+ import {verifySlotUpdateEnvelope} from './runtime-update.mjs';
5
6
 
6
- export async function inspectDoctorTransactions(explicitRoot,{maxEntries=256}={}){
7
- if(!Number.isSafeInteger(maxEntries)||maxEntries<1||maxEntries>256)throw new Error('doctor transaction budget invalid');
7
+ const UPDATE_RECEIPT='deadbyte.r27-slot-update-receipt.v1';
8
+ const FAILURE_RECEIPT='deadbyte.r27-slot-update-failure.v1';
9
+ const ROLLBACK_RECEIPT='deadbyte.r31-slot-rollback-receipt.v1';
10
+ const ROLLBACK_FAILURE_RECEIPT='deadbyte.r31-slot-rollback-failure.v1';
11
+
12
+ async function readReceipt(file){
13
+ const info=await lstat(file);
14
+ if(!info.isFile()||info.isSymbolicLink()||info.size>1048576)throw new Error('unsafe transaction receipt');
15
+ const bytes=await readFile(file);
16
+ if(bytes.length>1048576)throw new Error('transaction receipt budget');
17
+ return JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
18
+ }
19
+
20
+ function receiptSubjectMatches(state,envelope,kind){
21
+ const body=envelope?.body;
22
+ if(state.schema==='deadbyte.r27-slot-update-state.v1'){
23
+ if(kind==='success')return body?.schema===UPDATE_RECEIPT&&body?.update_id===state.update_id&&body?.rollback_performed===false;
24
+ return body?.schema===FAILURE_RECEIPT&&body?.update_id===state.update_id;
25
+ }
26
+ if(state.schema==='deadbyte.r31-slot-rollback-state.v1'){
27
+ if(kind==='success')return body?.schema===ROLLBACK_RECEIPT&&body?.rollback_id===state.rollback_id&&body?.rollback_performed===true;
28
+ return body?.schema===ROLLBACK_FAILURE_RECEIPT&&body?.rollback_id===state.rollback_id;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ export async function inspectDoctorTransactions(explicitRoot,{maxEntries=1024}={}){
34
+ if(!Number.isSafeInteger(maxEntries)||maxEntries<1||maxEntries>1024)throw new Error('doctor transaction budget invalid');
8
35
  const state=resolveStateRoot(explicitRoot),root=path.join(state,'evidence','updates'),findings=[];
9
36
  const next='Retain transaction evidence; inspect the exact signed receipt/handoff and current pointer before any supported recovery. Doctor never launches recovery.';
10
37
  const add=(level,what,evidence)=>findings.push({id:'transaction_residue',level,what,evidence,next_action:level==='PASS'?null:next});
@@ -17,24 +44,68 @@ export async function inspectDoctorTransactions(explicitRoot,{maxEntries=256}={}
17
44
  else add('FAIL','Update evidence directory cannot be safely inspected.',{error_code:error?.code??'VALIDATION_FAILED'});
18
45
  return {status:findings[0].level,findings};
19
46
  }
20
- let entries=0,checked=0,terminal=0;
47
+ let publicKey=null;
48
+ const loadPublicKey=async()=>{
49
+ if(publicKey)return publicKey;
50
+ const keyFile=path.join(state,'trust','ed25519-public.pem');
51
+ const info=await lstat(keyFile);
52
+ if(!info.isFile()||info.isSymbolicLink()||info.size>65536)throw new Error('unsafe transaction trust key');
53
+ publicKey=createPublicKey(await readFile(keyFile));
54
+ if(publicKey.asymmetricKeyType!=='ed25519')throw new Error('transaction trust key must be Ed25519');
55
+ return publicKey;
56
+ };
57
+ let entries=0,checked=0,terminal=0,verifiedTerminalReceipts=0,verifiedSuccessReceipts=0,verifiedFailureReceipts=0,unresolved=0,inventoryComplete=true;
21
58
  try{
22
59
  for await(const entry of directory){
23
- if(++entries>maxEntries){add('WARN','Transaction inventory budget reached; unobserved entries remain.',{max_entries:maxEntries,states_checked:checked,inventory_complete:false});break;}
60
+ if(++entries>maxEntries){inventoryComplete=false;add('WARN','Transaction inventory budget reached; unobserved entries remain.',{max_entries:maxEntries,states_checked:checked,inventory_complete:false});break;}
24
61
  if(entry.isSymbolicLink()){add('FAIL','Transaction evidence contains a link; it was not followed.',{entry_name_sha256:createHash('sha256').update(entry.name).digest('hex')});continue;}
25
62
  if(!entry.isDirectory())continue;
26
- const file=path.join(root,entry.name,'state.json');let bytes;
63
+ const dir=path.join(root,entry.name),file=path.join(dir,'state.json');let bytes;
27
64
  try{
28
65
  const info=await lstat(file);if(!info.isFile()||info.isSymbolicLink()||info.size>1048576)throw new Error('unsafe transaction state');
29
66
  bytes=await readFile(file);if(bytes.length>1048576)throw new Error('transaction state budget');
30
67
  const body=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));checked++;
31
68
  const digest=createHash('sha256').update(bytes).digest('hex');
32
69
  if(!['deadbyte.r27-slot-update-state.v1','deadbyte.r31-slot-rollback-state.v1'].includes(body?.schema)||typeof body.phase!=='string'||!/^[a-z_]{1,64}$/.test(body.phase)){add('FAIL','Transaction state schema or phase is invalid.',{state_sha256:digest,check:'transaction_schema'});continue;}
33
- if(['completed','superseded','failed_receipted'].includes(body.phase)){terminal++;continue;}
70
+ const receiptPath=path.join(dir,'receipt.json'),failurePath=path.join(dir,'failure-receipt.json');
71
+ let receiptExists=false,failureExists=false;
72
+ try{const st=await lstat(receiptPath);receiptExists=st.isFile()&&!st.isSymbolicLink();}catch(error){if(error?.code!=='ENOENT')throw error;}
73
+ try{const st=await lstat(failurePath);failureExists=st.isFile()&&!st.isSymbolicLink();}catch(error){if(error?.code!=='ENOENT')throw error;}
74
+ if(receiptExists&&failureExists){add('FAIL','Transaction contains conflicting terminal receipts.',{state_sha256:digest,check:'terminal_receipt_conflict'});continue;}
75
+ const terminalPath=receiptExists?receiptPath:failureExists?failurePath:null;
76
+ if(terminalPath){
77
+ const kind=receiptExists?'success':'failure';
78
+ const envelope=await readReceipt(terminalPath);
79
+ const key=await loadPublicKey();
80
+ if(!verifySlotUpdateEnvelope(envelope,key)||!receiptSubjectMatches(body,envelope,kind)){
81
+ add('FAIL','Transaction terminal receipt is invalid or not subject-bound to its journal.',{state_sha256:digest,check:'terminal_receipt_verification',receipt_kind:kind});
82
+ continue;
83
+ }
84
+ terminal++;verifiedTerminalReceipts++;
85
+ if(kind==='success')verifiedSuccessReceipts++;else verifiedFailureReceipts++;
86
+ continue;
87
+ }
88
+
89
+ if(body.phase==='superseded'){terminal++;continue;}
90
+ if(body.phase==='failed_receipted'){
91
+ add('FAIL','Transaction journal claims a signed failure receipt but the receipt is missing.',{state_sha256:digest,check:'missing_failure_receipt',recorded_phase:body.phase});
92
+ continue;
93
+ }
94
+ unresolved++;
34
95
  add('WARN','Nonterminal or unresolved transaction record remains; liveness/outcome is not inferred from its age or PID.',{state_sha256:digest,schema:body.schema,recorded_phase:body.phase});
35
96
  }catch(error){if(error?.code==='ENOENT')continue;add('FAIL','Transaction state cannot be safely parsed or inspected.',{error_code:error?.code??'VALIDATION_FAILED'});}
36
97
  }
37
98
  }catch(error){add('FAIL','Transaction inventory observation failed.',{error_code:error?.code??'OBSERVATION_FAILED'});}
38
- add('PASS','Recorded terminal transaction phases counted; signatures and desired outcomes are not established by this inventory.',{states_checked:checked,recorded_terminal_states:terminal,receipt_verification_performed:false});
99
+ add('PASS','Recorded terminal transaction outcomes counted; signed receipts are verified when they reconcile stale journal phases.',{
100
+ states_checked:checked,
101
+ recorded_terminal_states:terminal,
102
+ verified_terminal_receipts:verifiedTerminalReceipts,
103
+ verified_success_receipts:verifiedSuccessReceipts,
104
+ verified_failure_receipts:verifiedFailureReceipts,
105
+ unresolved_transactions:unresolved,
106
+ inventory_complete:inventoryComplete,
107
+ max_entries:maxEntries,
108
+ receipt_verification_performed:true
109
+ });
39
110
  return {status:findings.some(f=>f.level==='FAIL')?'FAIL':findings.some(f=>f.level==='WARN')?'WARN':'PASS',findings};
40
111
  }
@@ -26,6 +26,7 @@ const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
26
26
  const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
27
27
  const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
28
28
  const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
29
+ const ROLLBACK_FAILURE_SCHEMA='deadbyte.r31-slot-rollback-failure.v1';
29
30
  const DEFAULT_STATE_ROOT=undefined;
30
31
  const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
31
32
  const RUNTIME_STABILITY_MS=12000;
@@ -1762,7 +1763,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1762
1763
  const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
1763
1764
  const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
1764
1765
  const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
1765
- const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json');
1766
+ const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
1766
1767
  await mkdir(evidenceDir,{recursive:true});
1767
1768
  const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
1768
1769
  activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
@@ -1807,8 +1808,19 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1807
1808
  }
1808
1809
  try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
1809
1810
  }
1810
- await writeState(statePath,journal,'failed',{error:{name:error?.name??'Error',message:String(error?.message??error)},recovery}).catch(()=>{});
1811
- const wrapped=new Error(`R31 slot rollback failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath; throw wrapped;
1811
+ const failure={schema:ROLLBACK_FAILURE_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
1812
+ failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),
1813
+ error:{name:error?.name??'Error',message:String(error?.message??error)},recovery};
1814
+ await writeState(statePath,journal,'failed',{error:failure.error,recovery}).catch(()=>{});
1815
+ let failureReceipt=null;
1816
+ try{
1817
+ failureReceipt=await writeEnvelope(failurePath,failure,key);
1818
+ await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
1819
+ }catch(receiptError){
1820
+ recovery.errors.push('failure-receipt:'+receiptError.message);
1821
+ await writeState(statePath,journal,'failed_unreceipted',{recovery,failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{});
1822
+ }
1823
+ const wrapped=new Error(`R31 slot rollback failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?failurePath:null; throw wrapped;
1812
1824
  }
1813
1825
  }
1814
1826
 
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.38';
1
+ export const DEADBYTE_VERSION = '0.15.39';
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';
@@ -10,12 +10,12 @@ test('R58 bounded transaction inventory flags residue without recovering or over
10
10
  assert.equal((await inspectDoctorTransactions(root)).status,'PASS');assert.deepEqual(await readdir(root),[]);
11
11
  const transactions=path.join(root,'evidence','updates');await mkdir(transactions,{recursive:true});
12
12
  const write=async(name,body)=>{const dir=path.join(transactions,name);await mkdir(dir);const file=path.join(dir,'state.json'),bytes=JSON.stringify(body);await writeFile(file,bytes);return {file,bytes};};
13
- const done=await write('done',{schema:'deadbyte.r27-slot-update-state.v1',phase:'completed'});
13
+ const done=await write('done',{schema:'deadbyte.r27-slot-update-state.v1',phase:'superseded'});
14
14
  assert.equal((await inspectDoctorTransactions(root)).status,'PASS');
15
15
  const pending=await write('pending',{schema:'deadbyte.r31-slot-rollback-state.v1',phase:'prior_pointer_restored',supervisor_pid:process.pid});
16
16
  const result=await inspectDoctorTransactions(root);assert.equal(result.status,'WARN');assert.ok(result.findings.some(f=>f.evidence.recorded_phase==='prior_pointer_restored'));
17
17
  for(const item of [done,pending])assert.equal(await readFile(item.file,'utf8'),item.bytes);
18
18
  const bounded=await inspectDoctorTransactions(root,{maxEntries:1});assert.equal(bounded.status,'WARN');assert.ok(bounded.findings.some(f=>f.evidence.inventory_complete===false));
19
19
  const corrupt=await write('corrupt',{schema:'INVALID',phase:'completed'});assert.equal((await inspectDoctorTransactions(root)).status,'FAIL');assert.equal(await readFile(corrupt.file,'utf8'),corrupt.bytes);
20
- await assert.rejects(inspectDoctorTransactions(root,{maxEntries:257}),/budget invalid/);
20
+ await assert.rejects(inspectDoctorTransactions(root,{maxEntries:1025}),/budget invalid/);
21
21
  });
@@ -4,13 +4,14 @@ import {readFile} from 'node:fs/promises';
4
4
 
5
5
  const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
6
6
  const json=rel=>read(rel).then(raw=>JSON.parse(raw.replace(/^\uFEFF/,'')));
7
+ const currentPackage=await json('package.json');
7
8
  const PARENT={
8
9
  release_id:'v0.15.37-1cfecd7889419da7',
9
10
  manifest_sha256:'1cfecd7889419da7800814e3a2a4ffa93d48dfdb0c344a5b7b91fe8d093230aa',
10
11
  generation_manifest_sha256:'54842021c75f01bf7ecbd5b10ab8a482ecf107765b45c46f9d39b12bab65eac4'
11
12
  };
12
13
 
13
- test('R61 identity binds exact sealed 0.15.37 predecessor without a public surface expansion',async()=>{
14
+ test('R61 identity binds exact sealed 0.15.37 predecessor without a public surface expansion',{skip:currentPackage.version!=='0.15.38'},async()=>{
14
15
  const [pkg,lock,bridge,release,provenance,matrix,sbom,version,gate]=await Promise.all([
15
16
  json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
16
17
  json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),
@@ -0,0 +1,50 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdir,mkdtemp,writeFile} from 'node:fs/promises';
4
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
8
+
9
+ const sha=b=>createHash('sha256').update(b).digest('hex');
10
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
11
+ ?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
12
+ const canonicalJson=value=>JSON.stringify(canonical(value));
13
+ const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0');
14
+ function envelope(body,keys){
15
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
16
+ const unsigned={body,body_sha256:sha(bodyBytes),attestation:{
17
+ algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',
18
+ key_id:sha(keys.publicKey.export({type:'spki',format:'der'})),
19
+ signature_base64:sign(null,Buffer.concat([domain,bodyBytes]),keys.privateKey).toString('base64')
20
+ }};
21
+ return {...unsigned,receipt_sha256:sha(Buffer.from(canonicalJson(unsigned),'utf8'))};
22
+ }
23
+
24
+ test('R62 doctor rejects signed success receipts whose rollback semantics contradict their schema',async()=>{
25
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-receipt-semantics-'));
26
+ const trust=path.join(root,'trust'),updates=path.join(root,'evidence','updates');
27
+ await mkdir(trust,{recursive:true});await mkdir(updates,{recursive:true});
28
+ const keys=generateKeyPairSync('ed25519');
29
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
30
+
31
+ const updateDir=path.join(updates,'bad-update-success');await mkdir(updateDir);
32
+ await writeFile(path.join(updateDir,'state.json'),JSON.stringify({
33
+ schema:'deadbyte.r27-slot-update-state.v1',update_id:'bad-update-success',phase:'completed'
34
+ }));
35
+ await writeFile(path.join(updateDir,'receipt.json'),JSON.stringify(envelope({
36
+ schema:'deadbyte.r27-slot-update-receipt.v1',update_id:'bad-update-success',rollback_performed:true
37
+ },keys)));
38
+
39
+ const rollbackDir=path.join(updates,'bad-rollback-success');await mkdir(rollbackDir);
40
+ await writeFile(path.join(rollbackDir,'state.json'),JSON.stringify({
41
+ schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:'bad-rollback-success',phase:'completed'
42
+ }));
43
+ await writeFile(path.join(rollbackDir,'receipt.json'),JSON.stringify(envelope({
44
+ schema:'deadbyte.r31-slot-rollback-receipt.v1',rollback_id:'bad-rollback-success',rollback_performed:false
45
+ },keys)));
46
+
47
+ const result=await inspectDoctorTransactions(root);
48
+ assert.equal(result.status,'FAIL');
49
+ assert.equal(result.findings.filter(f=>f.evidence?.check==='terminal_receipt_verification').length,2);
50
+ });
@@ -0,0 +1,208 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdir,mkdtemp,writeFile,readFile} from 'node:fs/promises';
4
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
8
+
9
+ const sha=b=>createHash('sha256').update(b).digest('hex');
10
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
11
+ ?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
12
+ const canonicalJson=value=>JSON.stringify(canonical(value));
13
+ const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0');
14
+
15
+ function envelope(body,keys){
16
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
17
+ const unsigned={
18
+ body,
19
+ body_sha256:sha(bodyBytes),
20
+ attestation:{
21
+ algorithm:'ed25519',
22
+ domain:'DEADBYTE-R27-SLOT-UPDATE-V1',
23
+ key_id:sha(keys.publicKey.export({type:'spki',format:'der'})),
24
+ signature_base64:sign(null,Buffer.concat([domain,bodyBytes]),keys.privateKey).toString('base64')
25
+ }
26
+ };
27
+ return {...unsigned,receipt_sha256:sha(Buffer.from(canonicalJson(unsigned),'utf8'))};
28
+ }
29
+
30
+ test('R62 doctor reconciles signed terminal receipts instead of warning on stale nonterminal journal phases',async()=>{
31
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-reconcile-'));
32
+ const trust=path.join(root,'trust');
33
+ const updates=path.join(root,'evidence','updates');
34
+ await mkdir(trust,{recursive:true});
35
+ await mkdir(updates,{recursive:true});
36
+ const keys=generateKeyPairSync('ed25519');
37
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
38
+
39
+ const writeTransaction=async(name,state,receiptName,receiptBody)=>{
40
+ const dir=path.join(updates,name);
41
+ await mkdir(dir);
42
+ const statePath=path.join(dir,'state.json');
43
+ const stateBytes=JSON.stringify(state);
44
+ await writeFile(statePath,stateBytes);
45
+ await writeFile(path.join(dir,receiptName),JSON.stringify(envelope(receiptBody,keys)));
46
+ return {statePath,stateBytes};
47
+ };
48
+
49
+ const success=await writeTransaction(
50
+ 'success',
51
+ {schema:'deadbyte.r27-slot-update-state.v1',update_id:'success',phase:'pointer_committed'},
52
+ 'receipt.json',
53
+ {schema:'deadbyte.r27-slot-update-receipt.v1',update_id:'success',rollback_performed:false}
54
+ );
55
+ const failure=await writeTransaction(
56
+ 'failure',
57
+ {schema:'deadbyte.r27-slot-update-state.v1',update_id:'failure',phase:'candidate_prepared'},
58
+ 'failure-receipt.json',
59
+ {schema:'deadbyte.r27-slot-update-failure.v1',update_id:'failure',failed_at_utc:'2026-10-04T00:00:00.000Z'}
60
+ );
61
+
62
+ const result=await inspectDoctorTransactions(root);
63
+ assert.equal(result.status,'PASS');
64
+ assert.equal(result.findings.some(f=>f.level==='WARN'),false);
65
+ const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
66
+ assert.ok(summary,'doctor must report signed receipt verification');
67
+ assert.equal(summary.evidence.verified_terminal_receipts,2);
68
+ assert.equal(summary.evidence.unresolved_transactions,0);
69
+ assert.equal(await readFile(success.statePath,'utf8'),success.stateBytes);
70
+ assert.equal(await readFile(failure.statePath,'utf8'),failure.stateBytes);
71
+ });
72
+
73
+
74
+ test('R62 doctor default transaction budget covers more than the legacy 256-entry ceiling while remaining bounded',async()=>{
75
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-budget-'));
76
+ const updates=path.join(root,'evidence','updates');
77
+ await mkdir(updates,{recursive:true});
78
+ for(let index=0;index<300;index++){
79
+ const dir=path.join(updates,`tx-${String(index).padStart(3,'0')}`);
80
+ await mkdir(dir);
81
+ await writeFile(path.join(dir,'state.json'),JSON.stringify({
82
+ schema:'deadbyte.r27-slot-update-state.v1',
83
+ update_id:`tx-${index}`,
84
+ phase:'superseded'
85
+ }));
86
+ }
87
+ const result=await inspectDoctorTransactions(root);
88
+ assert.equal(result.status,'PASS');
89
+ assert.equal(result.findings.some(f=>f.evidence?.inventory_complete===false),false);
90
+ const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
91
+ assert.ok(summary);
92
+ assert.equal(summary.evidence.states_checked,300);
93
+ await assert.rejects(inspectDoctorTransactions(root,{maxEntries:1025}),/budget invalid/);
94
+ });
95
+
96
+
97
+ test('R62 doctor verifies signed receipts even when the journal already claims a terminal phase',async()=>{
98
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-terminal-proof-'));
99
+ const trust=path.join(root,'trust');
100
+ const updates=path.join(root,'evidence','updates');
101
+ await mkdir(trust,{recursive:true});
102
+ await mkdir(updates,{recursive:true});
103
+ const keys=generateKeyPairSync('ed25519');
104
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
105
+
106
+ const provenDir=path.join(updates,'proven');
107
+ await mkdir(provenDir);
108
+ await writeFile(path.join(provenDir,'state.json'),JSON.stringify({
109
+ schema:'deadbyte.r27-slot-update-state.v1',
110
+ update_id:'proven',
111
+ phase:'completed'
112
+ }));
113
+ await writeFile(path.join(provenDir,'receipt.json'),JSON.stringify(envelope({
114
+ schema:'deadbyte.r27-slot-update-receipt.v1',
115
+ update_id:'proven',
116
+ rollback_performed:false
117
+ },keys)));
118
+
119
+ const proven=await inspectDoctorTransactions(root);
120
+ assert.equal(proven.status,'PASS');
121
+ const provenSummary=proven.findings.find(f=>f.evidence?.receipt_verification_performed===true);
122
+ assert.equal(provenSummary.evidence.verified_terminal_receipts,1);
123
+
124
+ const unprovenDir=path.join(updates,'unproven');
125
+ await mkdir(unprovenDir);
126
+ await writeFile(path.join(unprovenDir,'state.json'),JSON.stringify({
127
+ schema:'deadbyte.r27-slot-update-state.v1',
128
+ update_id:'unproven',
129
+ phase:'completed'
130
+ }));
131
+ const unproven=await inspectDoctorTransactions(root);
132
+ assert.equal(unproven.status,'WARN');
133
+ assert.ok(unproven.findings.some(f=>f.evidence?.recorded_phase==='completed'));
134
+ });
135
+
136
+
137
+ test('R62 doctor fails closed on tampered or missing claimed terminal receipts without mutating journals',async()=>{
138
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-adversarial-'));
139
+ const trust=path.join(root,'trust');
140
+ const updates=path.join(root,'evidence','updates');
141
+ await mkdir(trust,{recursive:true});
142
+ await mkdir(updates,{recursive:true});
143
+ const keys=generateKeyPairSync('ed25519');
144
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
145
+
146
+ const tamperedDir=path.join(updates,'tampered');
147
+ await mkdir(tamperedDir);
148
+ const tamperedStatePath=path.join(tamperedDir,'state.json');
149
+ const tamperedState=JSON.stringify({
150
+ schema:'deadbyte.r27-slot-update-state.v1',
151
+ update_id:'tampered',
152
+ phase:'completed'
153
+ });
154
+ await writeFile(tamperedStatePath,tamperedState);
155
+ const good=envelope({
156
+ schema:'deadbyte.r27-slot-update-receipt.v1',
157
+ update_id:'tampered',
158
+ rollback_performed:false
159
+ },keys);
160
+ const bad={...good,attestation:{...good.attestation,signature_base64:Buffer.alloc(64).toString('base64')}};
161
+ await writeFile(path.join(tamperedDir,'receipt.json'),JSON.stringify(bad));
162
+
163
+ const missingDir=path.join(updates,'missing');
164
+ await mkdir(missingDir);
165
+ const missingStatePath=path.join(missingDir,'state.json');
166
+ const missingState=JSON.stringify({
167
+ schema:'deadbyte.r27-slot-update-state.v1',
168
+ update_id:'missing',
169
+ phase:'failed_receipted'
170
+ });
171
+ await writeFile(missingStatePath,missingState);
172
+
173
+ const result=await inspectDoctorTransactions(root);
174
+ assert.equal(result.status,'FAIL');
175
+ assert.ok(result.findings.some(f=>f.evidence?.check==='terminal_receipt_verification'));
176
+ assert.ok(result.findings.some(f=>f.evidence?.check==='missing_failure_receipt'));
177
+ assert.equal(await readFile(tamperedStatePath,'utf8'),tamperedState);
178
+ assert.equal(await readFile(missingStatePath,'utf8'),missingState);
179
+ });
180
+
181
+
182
+ test('R62 doctor reconciles a signed R31 rollback failure receipt by rollback_id',async()=>{
183
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-r31-failure-proof-'));
184
+ const trust=path.join(root,'trust');
185
+ const dir=path.join(root,'evidence','updates','rollback-failure');
186
+ await mkdir(trust,{recursive:true});
187
+ await mkdir(dir,{recursive:true});
188
+ const keys=generateKeyPairSync('ed25519');
189
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
190
+ const rollbackId='r31-rollback-r62-fixture';
191
+ await writeFile(path.join(dir,'state.json'),JSON.stringify({
192
+ schema:'deadbyte.r31-slot-rollback-state.v1',
193
+ rollback_id:rollbackId,
194
+ phase:'failed'
195
+ }));
196
+ await writeFile(path.join(dir,'failure-receipt.json'),JSON.stringify(envelope({
197
+ schema:'deadbyte.r31-slot-rollback-failure.v1',
198
+ rollback_id:rollbackId,
199
+ failed_at_utc:'2026-10-04T00:00:00.000Z',
200
+ error:{name:'Error',message:'fixture'}
201
+ },keys)));
202
+ const result=await inspectDoctorTransactions(root);
203
+ assert.equal(result.status,'PASS');
204
+ const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
205
+ assert.equal(summary.evidence.verified_terminal_receipts,1);
206
+ assert.equal(summary.evidence.verified_failure_receipts,1);
207
+ assert.equal(summary.evidence.unresolved_transactions,0);
208
+ });