deadbyte-mcp 0.15.41 → 0.15.43

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 (89) hide show
  1. package/DEADBYTE-MCP.cmd +8 -4
  2. package/MANIFEST.SHA256 +88 -43
  3. package/PROVENANCE.json +66 -15
  4. package/README.txt +27 -18
  5. package/RELEASE.json +55 -15
  6. package/SBOM.cdx.json +2 -2
  7. package/TEST-MATRIX.json +117 -14
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-controller.ps1 +132 -12
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
  12. package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
  13. package/package.json +6 -4
  14. package/scripts/deploy-r48.mjs +123 -7
  15. package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
  16. package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
  17. package/scripts/npm-publication-r65.mjs +139 -0
  18. package/scripts/npm-release-r48.mjs +56 -37
  19. package/scripts/r65-canonical-full.mjs +45 -0
  20. package/scripts/r65-focused.mjs +37 -0
  21. package/scripts/verify-r65-parent-source.mjs +75 -0
  22. package/src/activation-continuity-engine-r65.mjs +225 -0
  23. package/src/activation-lock-r65.mjs +155 -0
  24. package/src/activation-preflight-r65.mjs +197 -0
  25. package/src/authority-continuity-r65.mjs +541 -0
  26. package/src/authority-epoch-cli-r65.mjs +10 -0
  27. package/src/autonomous-policy.mjs +7 -3
  28. package/src/coding-plane.mjs +12 -3
  29. package/src/desktop-policy-r40.mjs +6 -3
  30. package/src/doctor-runtime-r58.mjs +3 -1
  31. package/src/doctor-transactions-r58.mjs +1 -1
  32. package/src/host-plane.mjs +9 -3
  33. package/src/process-policy.mjs +7 -4
  34. package/src/remote-authority.mjs +10 -1
  35. package/src/runtime-update.mjs +15 -5
  36. package/src/version.mjs +1 -1
  37. package/test/audit-context.test.mjs +15 -4
  38. package/test/autonomous-local-supervisor.test.mjs +7 -6
  39. package/test/autonomous-supervisor-integration.test.mjs +8 -7
  40. package/test/capability-resolver.test.mjs +11 -5
  41. package/test/codex-autonomous-r44.test.mjs +7 -6
  42. package/test/coding-observation.test.mjs +4 -3
  43. package/test/commander-real-parity-r47.test.mjs +5 -4
  44. package/test/commander-torture-r47.test.mjs +5 -4
  45. package/test/desktop-policy-r40.test.mjs +6 -4
  46. package/test/desktop-runtime-r40.test.mjs +8 -3
  47. package/test/host-inline-r45.test.mjs +4 -3
  48. package/test/machine-fs-runtime.test.mjs +4 -3
  49. package/test/process-longrun-r36.test.mjs +3 -2
  50. package/test/process-policy.test.mjs +10 -6
  51. package/test/process-runtime.test.mjs +9 -6
  52. package/test/r52-host-revocation.test.mjs +1 -0
  53. package/test/r58-doctor-runtime.test.mjs +18 -0
  54. package/test/r59-tool-surface-compat.test.mjs +1 -1
  55. package/test/r64-release-contract.test.mjs +2 -1
  56. package/test/r65-activation-lock-recovery.test.mjs +72 -0
  57. package/test/r65-activation-lock.test.mjs +59 -0
  58. package/test/r65-activation-orchestration.test.mjs +179 -0
  59. package/test/r65-activation-preflight.test.mjs +35 -0
  60. package/test/r65-authority-continuity-decision.test.mjs +58 -0
  61. package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
  62. package/test/r65-authority-epoch-cli.test.mjs +125 -0
  63. package/test/r65-authority-epoch-races.test.mjs +87 -0
  64. package/test/r65-authority-epoch-recovery.test.mjs +82 -0
  65. package/test/r65-authority-epoch-store.test.mjs +94 -0
  66. package/test/r65-authority-fixture.mjs +41 -0
  67. package/test/r65-authority-grant-capture.test.mjs +37 -0
  68. package/test/r65-authority-observation-epoch.test.mjs +25 -0
  69. package/test/r65-authority-policy-envelope.test.mjs +46 -0
  70. package/test/r65-authority-restored-safe.test.mjs +75 -0
  71. package/test/r65-authority-runtime-epoch.test.mjs +115 -0
  72. package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
  73. package/test/r65-authority-test-trust.mjs +20 -0
  74. package/test/r65-cli-invocation.test.mjs +38 -0
  75. package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
  76. package/test/r65-controller-authority-epoch.test.mjs +37 -0
  77. package/test/r65-deploy-preflight.test.mjs +55 -0
  78. package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
  79. package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
  80. package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
  81. package/test/r65-legacy-authority-migration.test.mjs +63 -0
  82. package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
  83. package/test/r65-npm-publication.test.mjs +114 -0
  84. package/test/r65-parent-source-verifier.test.mjs +63 -0
  85. package/test/r65-release-contract.test.mjs +75 -0
  86. package/test/r65-transaction-precondition.test.mjs +36 -0
  87. package/test/r66-release-contract.test.mjs +56 -0
  88. package/test/remote-session-cli.test.mjs +6 -6
  89. package/test/standing-grants.test.mjs +20 -4
@@ -0,0 +1,65 @@
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 "R66 prepublish gate failed at $Name exit=$LASTEXITCODE" }
15
+ }
16
+
17
+ function Get-FileSha256([string]$File){
18
+ $hasher=[Security.Cryptography.SHA256]::Create()
19
+ try{
20
+ $bytes=[IO.File]::ReadAllBytes($File)
21
+ return (($hasher.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') }) -join '')
22
+ }finally{ $hasher.Dispose() }
23
+ }
24
+
25
+ $sourceRoot='D:\Projects\deadbyte-mcp\development\deadbyte-mcp-0.15.42-r65'
26
+ $sourceManifest='1b4754103dd4ca43eb81f8e33cf188b36eee3051cb182396ccc6abe2a6e6c250'
27
+ if(-not (Test-Path -LiteralPath $sourceRoot -PathType Container)){ throw 'R66 source predecessor missing' }
28
+ $observedSourceManifest=Get-FileSha256 (Join-Path $sourceRoot 'MANIFEST.SHA256')
29
+ if($observedSourceManifest -ne $sourceManifest){ throw 'R66 source predecessor manifest identity mismatch' }
30
+ & node (Join-Path $root 'scripts\verify-r65-parent-source.mjs') --root $sourceRoot --expected-manifest-sha256 $sourceManifest
31
+ if($LASTEXITCODE -ne 0){ throw 'R66 source predecessor declared-manifest verification failed' }
32
+
33
+ $stateRoot=Join-Path $env:USERPROFILE '.deadbyte-mcp'
34
+ $pointer=Join-Path $stateRoot 'active-release.json'
35
+ if(-not (Test-Path -LiteralPath $pointer -PathType Leaf)){ throw "R66 runtime predecessor pointer missing: $pointer" }
36
+ $active=Get-Content -LiteralPath $pointer -Raw | ConvertFrom-Json
37
+ if($active.release_id -ne 'v0.15.42-1b4754103dd4ca43'){ throw "R66 runtime predecessor release mismatch: $($active.release_id)" }
38
+ if($active.manifest_sha256 -ne $sourceManifest){ throw 'R66 runtime predecessor manifest mismatch' }
39
+ if($active.generation_manifest_sha256 -ne '62dc68be55ad627f8d872b8be8c1369ecca0edf08fdedc8336c9ccee2d2809fb'){ throw 'R66 runtime predecessor generation mismatch' }
40
+ if($Stage -eq 'preflight'){Write-Host '==== R66 WINDOWS GATE: PASS preflight ====';return}
41
+
42
+ if($Stage -eq 'all' -or $Stage -eq 'focused'){
43
+ Invoke-Npm 'r66-focused' @('run','r66:focused:test')
44
+ if($Stage -eq 'focused'){Write-Host '==== R66 WINDOWS GATE: PASS focused ====';return}
45
+ }
46
+ if($Stage -eq 'all' -or $Stage -eq 'full'){
47
+ Invoke-Npm 'full-regression' @('test')
48
+ if($Stage -eq 'full'){Write-Host '==== R66 WINDOWS GATE: PASS full ====';return}
49
+ }
50
+ if($Stage -eq 'all' -or $Stage -eq 'manifest'){
51
+ Invoke-Npm 'manifest-write' @('run','manifest:write')
52
+ Invoke-Npm 'manifest-verify' @('run','manifest:verify')
53
+ if($Stage -eq 'manifest'){Write-Host '==== R66 WINDOWS GATE: PASS manifest ====';return}
54
+ }
55
+ if($Stage -eq 'all' -or $Stage -eq 'doctor'){
56
+ $doctorRoot='D:\Projects\deadbyte-mcp\testing\0.15.43-r66\doctor-isolated-empty'
57
+ if(Test-Path -LiteralPath (Join-Path $doctorRoot 'active-release.json')){ throw 'R66 isolated doctor root unexpectedly contains a release pointer' }
58
+ $priorStateRoot=$env:DEADBYTE_STATE_ROOT
59
+ try{
60
+ $env:DEADBYTE_STATE_ROOT=$doctorRoot
61
+ Invoke-Npm 'doctor-isolated-empty-state' @('run','doctor')
62
+ }finally{ $env:DEADBYTE_STATE_ROOT=$priorStateRoot }
63
+ if($Stage -eq 'doctor'){Write-Host '==== R66 WINDOWS GATE: PASS doctor ====';return}
64
+ }
65
+ Write-Host '==== R66 PREPUBLISH WINDOWS GATE: PASS ===='
@@ -0,0 +1,139 @@
1
+ import {createHash, randomBytes} from 'node:crypto';
2
+ import {mkdir, open, readFile, readdir, link, unlink} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {performance} from 'node:perf_hooks';
5
+ import {verifyPublishedTarballIdentity} from './npm-artifact-identity-r61.mjs';
6
+
7
+ const HEX64=/^[0-9a-f]{64}$/;
8
+ const digest=bytes=>createHash('sha256').update(bytes).digest('hex');
9
+ const pause=ms=>new Promise(resolve=>setTimeout(resolve,ms));
10
+
11
+ async function writeExclusive(file,value){
12
+ const handle=await open(file,'wx',0o600);
13
+ try{await handle.writeFile(JSON.stringify(value)+'\n');await handle.sync();}
14
+ finally{await handle.close();}
15
+ }
16
+
17
+ async function publishClaim(file,value){
18
+ const temporary=`${file}.private-${process.pid}-${randomBytes(8).toString('hex')}`;
19
+ try{
20
+ await writeExclusive(temporary,value);
21
+ await link(temporary,file); // Atomic, no-clobber visibility of complete bytes.
22
+ }finally{
23
+ await unlink(temporary).catch(error=>{if(error?.code!=='ENOENT')throw error;});
24
+ }
25
+ }
26
+
27
+ async function readClaim(file){
28
+ try{return JSON.parse(await readFile(file,'utf8'));}
29
+ catch(error){
30
+ if(error?.code==='ENOENT')return null;
31
+ if(error instanceof SyntaxError)return {incomplete:true};
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ async function event(recordPath,state,detail={}){
37
+ const file=path.join(recordPath,`event-${Date.now()}-${randomBytes(8).toString('hex')}.json`);
38
+ await writeExclusive(file,{schema:'deadbyte.npm-publication-event.r65.v1',state,at_utc:new Date().toISOString(),...detail});
39
+ }
40
+
41
+ async function priorState(recordPath){
42
+ const names=(await readdir(recordPath)).filter(name=>/^event-\d+-[0-9a-f]{16}\.json$/.test(name));
43
+ if(names.length>1024)throw new Error('npm publication evidence event budget exceeded');
44
+ const states=[];
45
+ for(const name of names){
46
+ const value=JSON.parse(await readFile(path.join(recordPath,name),'utf8'));
47
+ if(value.schema!=='deadbyte.npm-publication-event.r65.v1')throw new Error('npm publication event schema invalid');
48
+ states.push(value);
49
+ }
50
+ return states;
51
+ }
52
+
53
+ function result(state,extra={}){return Object.freeze({state,...extra});}
54
+
55
+ // The no-clobber claim is the dispatch fence: once it exists, no invocation may
56
+ // submit the same immutable npm version again, even if the first process dies.
57
+ export async function reconcileNpmPublication({recordPath,version,manifestSha256,localBytes,publish,lookup,tags,fetchTarball,
58
+ timeoutMs=180000,pollMs=1500,sleep=pause,now=()=>performance.now()}){
59
+ if(!path.isAbsolute(recordPath)||!/^\d+\.\d+\.\d+$/.test(version)||!HEX64.test(String(manifestSha256))||!Buffer.isBuffer(localBytes)||
60
+ !Number.isSafeInteger(timeoutMs)||timeoutMs<0||timeoutMs>900000||!Number.isSafeInteger(pollMs)||pollMs<1||pollMs>30000||
61
+ [publish,lookup,tags,fetchTarball,sleep,now].some(fn=>typeof fn!=='function'))throw new Error('npm publication reconciliation inputs invalid');
62
+ const tarballSha256=digest(localBytes);
63
+ if(!HEX64.test(tarballSha256))throw new Error('npm tarball digest invalid');
64
+ await mkdir(recordPath,{recursive:true});
65
+ const claimFile=path.join(recordPath,'claim.json');
66
+ let claim=await readClaim(claimFile);
67
+ if(claim?.incomplete)return result('OUTCOME_UNKNOWN',{reason:'publication claim incomplete; manual reconciliation required'});
68
+ if(claim&&(claim.schema!=='deadbyte.npm-publication-claim.r65.v1'||claim.version!==version||claim.manifest_sha256!==manifestSha256||claim.tarball_sha256!==tarballSha256))
69
+ return result('REGISTRY_REJECTED',{reason:'local artifact differs from existing immutable publication claim'});
70
+
71
+ const observe=async()=>{
72
+ let published;
73
+ try{published=await lookup();}
74
+ catch(error){if(error?.code==='E404'||error?.status===404)return null;throw error;}
75
+ if(!published)return null;
76
+ let registryBytes;
77
+ try{registryBytes=await fetchTarball(published);}
78
+ catch(error){if(error?.status===404||error?.code==='E404')throw Object.assign(new Error('registry tarball is still processing'),{code:'REGISTRY_PROCESSING'});throw error;}
79
+ const distTags=await tags();
80
+ const identity=verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags:distTags});
81
+ return {identity,registry:{version:published.version,integrity:published.dist?.integrity,shasum:published.dist?.shasum,
82
+ tarball:published.dist?.tarball,latest:distTags.latest}};
83
+ };
84
+ const check=async()=>{
85
+ try{const evidence=await observe();return evidence?result('REGISTRY_IDENTITY_VERIFIED',evidence):null;}
86
+ catch(error){
87
+ if(error?.code==='REGISTRY_PROCESSING')return result('REGISTRY_PROCESSING');
88
+ if(/metadata does not match|bytes differ|latest tag mismatch/.test(String(error?.message)))return result('REGISTRY_REJECTED',{reason:String(error.message)});
89
+ return result('OUTCOME_UNKNOWN',{reason:String(error?.code??error?.message??error)});
90
+ }
91
+ };
92
+
93
+ let observed=await check();
94
+ if(observed?.state==='REGISTRY_IDENTITY_VERIFIED'||observed?.state==='REGISTRY_REJECTED'){
95
+ if(claim)await event(recordPath,observed.state,{tarball_sha256:tarballSha256,registry:observed.registry??null});
96
+ return observed;
97
+ }
98
+ if(!claim){
99
+ if(observed)return observed; // Registry outage: do not submit while identity is unknown.
100
+ let created=false;
101
+ try{
102
+ await publishClaim(claimFile,{schema:'deadbyte.npm-publication-claim.r65.v1',version,manifest_sha256:manifestSha256,tarball_sha256:tarballSha256,created_at_utc:new Date().toISOString()});
103
+ claim={version,manifest_sha256:manifestSha256,tarball_sha256:tarballSha256};created=true;
104
+ }catch(error){
105
+ if(error?.code!=='EEXIST')throw error;
106
+ claim=await readClaim(claimFile);
107
+ if(claim?.incomplete||!claim)return result('OUTCOME_UNKNOWN',{reason:'concurrent publication claim unavailable or incomplete'});
108
+ if(claim.version!==version||claim.manifest_sha256!==manifestSha256||claim.tarball_sha256!==tarballSha256)return result('REGISTRY_REJECTED',{reason:'concurrent publication claim differs'});
109
+ }
110
+ // Only the creator of the claim may dispatch publish.
111
+ if(created){
112
+ await event(recordPath,'SUBMISSION_STARTED',{tarball_sha256:tarballSha256});
113
+ try{
114
+ await publish();
115
+ await event(recordPath,'SUBMISSION_ACCEPTED',{tarball_sha256:tarballSha256});
116
+ }catch(error){
117
+ const auth=/E401|E403|EOTP|authentication|one-time password|2FA/i.test(String(error?.code??'')+' '+String(error?.message??''));
118
+ await event(recordPath,auth?'AUTH_REQUIRED':'OUTCOME_UNKNOWN',{error_code:String(error?.code??'UNKNOWN').slice(0,64)});
119
+ return result(auth?'AUTH_REQUIRED':'OUTCOME_UNKNOWN',{reason:String(error?.code??error?.message??error)});
120
+ }
121
+ }
122
+ }
123
+ const states=await priorState(recordPath);
124
+ if(states.some(item=>item.state==='AUTH_REQUIRED'))return result('AUTH_REQUIRED');
125
+ const accepted=states.some(item=>item.state==='SUBMISSION_ACCEPTED');
126
+ const started=now();
127
+ do{
128
+ observed=await check();
129
+ if(observed?.state==='REGISTRY_IDENTITY_VERIFIED'||observed?.state==='REGISTRY_REJECTED'){
130
+ await event(recordPath,observed.state,{tarball_sha256:tarballSha256,registry:observed.registry??null});return observed;
131
+ }
132
+ if(observed?.state==='OUTCOME_UNKNOWN')return observed;
133
+ const remaining=timeoutMs-(now()-started);
134
+ if(remaining<=0)break;
135
+ await sleep(Math.min(pollMs,remaining));
136
+ }while(true);
137
+ await event(recordPath,accepted?'REGISTRY_PROCESSING':'OUTCOME_UNKNOWN',{tarball_sha256:tarballSha256});
138
+ return result(accepted?'REGISTRY_PROCESSING':'OUTCOME_UNKNOWN');
139
+ }
@@ -1,7 +1,9 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
- import { verifyPublishedTarballIdentity } from './npm-artifact-identity-r61.mjs';
4
- import { mkdtemp, readFile, rm, unlink } from 'node:fs/promises';
3
+ import { reconcileNpmPublication } from './npm-publication-r65.mjs';
4
+ import { mkdtemp, readFile, rm, unlink, mkdir, rename } from 'node:fs/promises';
5
+ import { existsSync } from 'node:fs';
6
+ import { resolveStateRoot } from '../src/state-root.mjs';
5
7
  import path from 'node:path';
6
8
  import os from 'node:os';
7
9
  import { fileURLToPath } from 'node:url';
@@ -11,7 +13,7 @@ const node = process.execPath;
11
13
  const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
12
14
 
13
15
  function run(command, args, options = {}) {
14
- const { cwd = root, timeout = 180000 } = options;
16
+ const { cwd = root, timeout = 180000, env=process.env } = options;
15
17
  const r = spawnSync(command, args, {
16
18
  cwd,
17
19
  encoding: 'utf8',
@@ -19,6 +21,7 @@ function run(command, args, options = {}) {
19
21
  stdio: ['ignore', 'pipe', 'pipe'],
20
22
  shell: false,
21
23
  windowsHide: true,
24
+ env,
22
25
  });
23
26
  if (r.error) throw r.error;
24
27
  if (r.status !== 0) {
@@ -95,30 +98,34 @@ async function main() {
95
98
  const existingText = (existing.stdout || '').trim();
96
99
  const isAlreadyPublished = existing.status === 0 && existingText && existingText !== 'null';
97
100
 
98
- if (isAlreadyPublished) {
99
- if (!isVerifyOnly) {
100
- throw new Error(`deadbyte-mcp@${version} is already published on registry: ${existingText}`);
101
- } else {
102
- console.log(`Notice: deadbyte-mcp@${version} is already published on registry (${existingText}), proceeding with pre-publish verification only.`);
103
- }
104
- }
101
+ if (isAlreadyPublished) console.log(`Notice: deadbyte-mcp@${version} exists on registry (${existingText}); exact-byte reconciliation is required.`);
105
102
 
106
103
  console.log('[2/7] Pre-pack gates: verifying working tree manifest and doctor...');
107
104
  run(node, [path.join(root, 'scripts', 'release-manifest.mjs'), 'verify']);
108
- run(node, [path.join(root, 'src', 'deadbyte-cli.mjs'), 'doctor']);
105
+ const doctorState=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-release-doctor-'));
106
+ try{run(node,[path.join(root,'src','deadbyte-cli.mjs'),'doctor'],{env:{...process.env,DEADBYTE_STATE_ROOT:doctorState}});}
107
+ finally{await rm(doctorState,{recursive:true,force:true});}
109
108
 
110
109
  console.log('[3/7] Packing artifact with npm pack...');
111
- const packed = parseJson(runNpm(['pack', '--json'], { timeout: 180000 }));
112
- const packRecord = Array.isArray(packed) ? packed[0] : packed?.[pkg.name];
110
+ const publicationDir=isVerifyOnly?null:path.join(resolveStateRoot(),'evidence','npm-publication',version);
111
+ const preservedTarball=publicationDir?path.join(publicationDir,`deadbyte-mcp-${version}.tgz`):null;
112
+ const resuming=Boolean(preservedTarball&&existsSync(path.join(publicationDir,'claim.json')));
113
+ if(publicationDir)await mkdir(publicationDir,{recursive:true});
114
+ if(resuming&&!existsSync(preservedTarball))throw new Error('immutable npm publication claim exists but exact local tarball is missing; manual artifact reconciliation required');
115
+ if(!resuming&&preservedTarball&&existsSync(preservedTarball))throw new Error('unclaimed preserved npm tarball exists; inspect artifact evidence before another pack');
116
+ const packed=resuming?null:parseJson(runNpm(['pack', '--json'], { timeout: 180000 }));
117
+ const packRecord=resuming?{filename:path.basename(preservedTarball)}:(Array.isArray(packed) ? packed[0] : packed?.[pkg.name]);
113
118
  const filename = packRecord?.filename;
114
119
  if (!filename) throw new Error('npm pack did not return a tarball filename');
115
- const tarball = path.join(root, filename);
120
+ const tarball = resuming?preservedTarball:path.join(root, filename);
116
121
  let releaseSucceeded = false;
117
122
  let publishAttempted = false;
123
+ let releasePending = false;
118
124
 
119
125
  try {
120
126
  console.log('[4/7] Verifying packed artifact file surface against MANIFEST.SHA256 and packaging contract...');
121
127
  const manifestText = await readFile(path.join(root, 'MANIFEST.SHA256'), 'utf8');
128
+ const manifestSha256=createHash('sha256').update(manifestText).digest('hex');
122
129
  const manifestEntries = new Set();
123
130
  for (const line of manifestText.split(/\r?\n/)) {
124
131
  if (!line) continue;
@@ -169,7 +176,7 @@ async function main() {
169
176
  }
170
177
 
171
178
  run(node, [path.join(extractedPackage, 'src', 'deadbyte-cli.mjs'), 'doctor'], {
172
- cwd: extractedPackage,
179
+ cwd: extractedPackage,env:{...process.env,DEADBYTE_STATE_ROOT:path.join(sandbox,'state')},
173
180
  });
174
181
 
175
182
  console.log('Artifact isolation gate PASS (clean doctor and version check on unpacked tarball in sandbox)');
@@ -189,30 +196,37 @@ async function main() {
189
196
  return;
190
197
  }
191
198
 
192
- const localBytes = await readFile(tarball);
199
+ if(!resuming)await rename(tarball,preservedTarball);
200
+ const publishTarball=preservedTarball;
201
+ const localBytes = await readFile(publishTarball);
202
+ const localIntegrity=`sha512-${createHash('sha512').update(localBytes).digest('base64')}`;
203
+ if(packRecord.integrity&&packRecord.integrity!==localIntegrity)throw new Error('npm packed artifact integrity changed before publication');
193
204
 
194
205
  console.log('[6/7] Publishing artifact to npm registry...');
195
206
  publishAttempted = true;
196
- let publishOut;
197
- try {
198
- publishOut = runNpmInteractive(['publish', tarball, '--access', 'public', '--auth-type=web'], { timeout: 300000 });
199
- } catch (publishError) {
200
- const detail = String(publishError?.message || publishError);
201
- if (/EOTP|one-time password|two-factor|2FA|security.?key|authentication/i.test(detail)) {
202
- console.error('Publish requires interactive npm authentication (Security Key/WebAuthn).');
203
- console.error('Tarball preserved for retry: ' + tarball);
204
- }
205
- throw publishError;
207
+ const reconciliation=await reconcileNpmPublication({recordPath:publicationDir,version,manifestSha256,localBytes,
208
+ publish:async()=>runNpmInteractive(['publish',publishTarball,'--access','public','--auth-type=web'],{timeout:300000}),
209
+ lookup:async()=>{
210
+ try{
211
+ const viewed=parseJson(runNpm(['view',`deadbyte-mcp@${version}`,'version','dist.integrity','dist.shasum','dist.tarball','--json'],{timeout:120000}));
212
+ return Array.isArray(viewed)?viewed[0]:viewed;
213
+ }catch(error){if(/E404|404 Not Found/.test(String(error?.message)))return null;throw error;}
214
+ },
215
+ tags:async()=>parseJson(runNpm(['view','deadbyte-mcp','dist-tags','--json'],{timeout:120000})),
216
+ fetchTarball:async published=>{
217
+ const response=await fetch(published?.dist?.tarball,{signal:AbortSignal.timeout(120000)});
218
+ if(!response.ok)throw Object.assign(new Error(`registry tarball fetch failed status=${response.status}`),{status:response.status});
219
+ return Buffer.from(await response.arrayBuffer());
220
+ }});
221
+ if(reconciliation.state!=='REGISTRY_IDENTITY_VERIFIED'){
222
+ console.log('RELEASE_PENDING '+JSON.stringify({state:reconciliation.state,version,tarball:publishTarball,record_path:publicationDir,reason:reconciliation.reason??null}));
223
+ releasePending=true;
224
+ process.exitCode=2;return;
206
225
  }
207
- const viewed = parseJson(runNpm(['view', `deadbyte-mcp@${version}`, 'version', 'dist.integrity', 'dist.shasum', 'dist.tarball', '--json'], { timeout: 120000 }));
208
- const published = Array.isArray(viewed) ? viewed[0] : viewed;
209
- const dist = published?.dist ?? published;
210
- if(typeof dist?.tarball !== 'string')throw new Error('published registry tarball URL missing');
211
- const registryResponse = await fetch(dist.tarball,{signal:AbortSignal.timeout(120000)});
212
- if(!registryResponse.ok) throw new Error(`registry tarball fetch failed status=${registryResponse.status}`);
213
- const registryBytes = Buffer.from(await registryResponse.arrayBuffer());
214
- const tags = parseJson(runNpm(['view', 'deadbyte-mcp', 'dist-tags', '--json'], { timeout: 120000 }));
215
- const identity=verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags});
226
+ const identity=reconciliation.identity;
227
+ const published={version,dist:{integrity:identity.npm_integrity,shasum:identity.shasum}};
228
+ const tags={latest:version};
229
+ const publishOut='registry identity verified by durable reconciliation';
216
230
 
217
231
  console.log('[7/7] Post-publish external registry smoke test (executed outside repo in temp)...');
218
232
  const tempDir = os.tmpdir();
@@ -227,6 +241,7 @@ async function main() {
227
241
  const externalDoctor = runNpm(['exec', '--yes', `--package=deadbyte-mcp@${version}`, '--', 'deadbyte-mcp', 'doctor'], {
228
242
  cwd: tempDir,
229
243
  timeout: 300000,
244
+ env:{...process.env,DEADBYTE_STATE_ROOT:path.join(publicationDir,'external-doctor-state')},
230
245
  });
231
246
 
232
247
  if(!publishOnly){
@@ -253,10 +268,14 @@ async function main() {
253
268
  }, null, 2));
254
269
 
255
270
  } finally {
256
- if ((releaseSucceeded && !publishOnly) || !publishAttempted) {
271
+ if (isVerifyOnly || (!publishAttempted&&!publicationDir)) {
257
272
  await unlink(tarball).catch(() => {});
273
+ } else if(releasePending){
274
+ console.log('Publication processing or reconciliation pending; exact tarball preserved: '+preservedTarball);
275
+ } else if(releaseSucceeded){
276
+ console.log('Verified release tarball retained for audit: '+preservedTarball);
258
277
  } else {
259
- console.error('Release artifact preserved after failure: ' + tarball);
278
+ console.error('Release artifact preserved for reconciliation: ' + preservedTarball);
260
279
  }
261
280
  }
262
281
  }
@@ -0,0 +1,45 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {mkdirSync,readdirSync,writeFileSync} from 'node:fs';
3
+ import {spawnSync} from 'node:child_process';
4
+ import path from 'node:path';
5
+
6
+ const runId=process.argv[2]??'manual';
7
+ if(!/^[A-Za-z0-9._-]{1,96}$/.test(runId))throw new Error('invalid run id');
8
+ const testDir=path.join(process.cwd(),'test');
9
+ const tests=readdirSync(testDir,{withFileTypes:true})
10
+ .filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs'))
11
+ .map(entry=>path.join('test',entry.name))
12
+ .sort((a,b)=>a.localeCompare(b,'en'));
13
+ const startedAt=new Date().toISOString();
14
+ const run=spawnSync(process.execPath,['--test','--test-concurrency=4',...tests],{
15
+ cwd:process.cwd(),
16
+ encoding:'utf8',
17
+ maxBuffer:128*1024*1024,
18
+ windowsHide:true,
19
+ shell:false
20
+ });
21
+ const stdout=run.stdout||'',stderr=run.stderr||'';
22
+ const evidenceDir=path.resolve(process.cwd(),'..','..','testing','0.15.42-r65','full-runs',runId);
23
+ mkdirSync(evidenceDir,{recursive:true});
24
+ writeFileSync(path.join(evidenceDir,'stdout.log'),stdout,'utf8');
25
+ writeFileSync(path.join(evidenceDir,'stderr.log'),stderr,'utf8');
26
+ const grab=(name)=>{const m=stdout.match(new RegExp('(?:ℹ|#)\\s*'+name+'\\s+(\\d+)','i'));return m?Number(m[1]):null;};
27
+ const result={
28
+ schema:'deadbyte.r65-full-run.v1',
29
+ run_id:runId,
30
+ started_at_utc:startedAt,
31
+ finished_at_utc:new Date().toISOString(),
32
+ command:[process.execPath,'--test','--test-concurrency=4',...tests],
33
+ test_files:tests.length,
34
+ exit_code:run.status??null,
35
+ signal:run.signal??null,
36
+ error:run.error?String(run.error.stack||run.error):null,
37
+ tests:grab('tests'),pass:grab('pass'),fail:grab('fail'),skipped:grab('skipped'),
38
+ stdout_sha256:createHash('sha256').update(stdout).digest('hex'),
39
+ stderr_sha256:createHash('sha256').update(stderr).digest('hex')
40
+ };
41
+ writeFileSync(path.join(evidenceDir,'result.json'),JSON.stringify(result,null,2)+'\n','utf8');
42
+ process.stdout.write(stdout);
43
+ process.stderr.write(stderr);
44
+ if(run.error)process.stderr.write(String(run.error.stack||run.error)+'\n');
45
+ process.exit(run.status??1);
@@ -0,0 +1,37 @@
1
+ import {spawnSync} from 'node:child_process';
2
+
3
+ const tests=[
4
+ 'test/r65-activation-lock.test.mjs',
5
+ 'test/r65-activation-lock-recovery.test.mjs',
6
+ 'test/r65-activation-orchestration.test.mjs',
7
+ 'test/r65-activation-preflight.test.mjs',
8
+ 'test/r65-authority-continuity-decision.test.mjs',
9
+ 'test/r65-authority-durable-policy-binding.test.mjs',
10
+ 'test/r65-authority-epoch-cli.test.mjs',
11
+ 'test/r65-authority-epoch-races.test.mjs',
12
+ 'test/r65-authority-epoch-recovery.test.mjs',
13
+ 'test/r65-authority-epoch-store.test.mjs',
14
+ 'test/r65-authority-grant-capture.test.mjs',
15
+ 'test/r65-authority-observation-epoch.test.mjs',
16
+ 'test/r65-authority-policy-envelope.test.mjs',
17
+ 'test/r65-authority-runtime-epoch.test.mjs',
18
+ 'test/r65-authority-runtime-policy-binding.test.mjs',
19
+ 'test/r65-authority-restored-safe.test.mjs',
20
+ 'test/r65-controller-authority-epoch.test.mjs',
21
+ 'test/r65-controller-authority-epoch-live.test.mjs',
22
+ 'test/r65-deploy-preflight.test.mjs',
23
+ 'test/r65-epoch-native-activation-integration.test.mjs',
24
+ 'test/r65-epoch-native-continuity-engine.test.mjs',
25
+ 'test/r65-epoch-native-cross-plane.test.mjs',
26
+ 'test/r65-legacy-authority-migration.test.mjs',
27
+ 'test/r65-legacy-auto-restore-rejection.test.mjs',
28
+ 'test/r65-parent-source-verifier.test.mjs',
29
+ 'test/r65-release-contract.test.mjs',
30
+ 'test/r65-transaction-precondition.test.mjs'
31
+ ];
32
+ const run=spawnSync(process.execPath,['--test','--test-concurrency=2',...tests],{
33
+ cwd:process.cwd(),encoding:'utf8',maxBuffer:64*1024*1024,windowsHide:true
34
+ });
35
+ process.stdout.write(run.stdout||'');
36
+ process.stderr.write(run.stderr||'');
37
+ process.exit(run.status??1);
@@ -0,0 +1,75 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {lstat,readFile,realpath} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+
6
+ const HEX64=/^[0-9a-f]{64}$/;
7
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
8
+
9
+ function parseArgs(argv){
10
+ const out={root:null,expectedManifestSha256:null};
11
+ for(let i=0;i<argv.length;i++){
12
+ const arg=argv[i];
13
+ if(arg==='--root')out.root=argv[++i]??null;
14
+ else if(arg==='--expected-manifest-sha256')out.expectedManifestSha256=argv[++i]??null;
15
+ else throw new Error('unknown argument '+arg);
16
+ }
17
+ if(typeof out.root!=='string'||out.root.length<1)throw new Error('--root required');
18
+ if(!HEX64.test(String(out.expectedManifestSha256)))throw new Error('--expected-manifest-sha256 must be lowercase hex');
19
+ return out;
20
+ }
21
+
22
+ function parseManifest(text){
23
+ const entries=[];
24
+ const seen=new Set();
25
+ for(const raw of text.split(/\r?\n/)){
26
+ if(raw==='')continue;
27
+ const match=/^([0-9a-f]{64}) (.+)$/.exec(raw);
28
+ if(!match)throw new Error('manifest line invalid');
29
+ const rel=match[2];
30
+ if(rel.includes('\\')||path.posix.isAbsolute(rel)||rel==='.'||rel.split('/').includes('..')||path.posix.normalize(rel)!==rel)
31
+ throw new Error('manifest path invalid: '+rel);
32
+ if(seen.has(rel))throw new Error('manifest duplicate path: '+rel);
33
+ seen.add(rel);
34
+ entries.push({sha256:match[1],rel});
35
+ }
36
+ if(entries.length<1)throw new Error('manifest has no entries');
37
+ return entries;
38
+ }
39
+
40
+ export async function verifyDeclaredParentSource({root,expectedManifestSha256}){
41
+ const absolute=path.resolve(root);
42
+ const rootReal=await realpath(absolute);
43
+ const manifestPath=path.join(absolute,'MANIFEST.SHA256');
44
+ const manifestBytes=await readFile(manifestPath);
45
+ const actualManifestSha=sha(manifestBytes);
46
+ if(actualManifestSha!==expectedManifestSha256)
47
+ throw new Error(`manifest SHA-256 mismatch expected=${expectedManifestSha256} actual=${actualManifestSha}`);
48
+ const entries=parseManifest(manifestBytes.toString('utf8'));
49
+ for(const entry of entries){
50
+ const target=path.resolve(absolute,...entry.rel.split('/'));
51
+ const info=await lstat(target).catch(error=>{
52
+ if(error?.code==='ENOENT')throw new Error('listed file missing: '+entry.rel);
53
+ throw error;
54
+ });
55
+ if(!info.isFile()||info.isSymbolicLink())throw new Error('listed path is not a regular file: '+entry.rel);
56
+ const targetReal=await realpath(target);
57
+ const prefix=rootReal.endsWith(path.sep)?rootReal:rootReal+path.sep;
58
+ if(targetReal!==rootReal&&!targetReal.startsWith(prefix))throw new Error('listed path escapes root: '+entry.rel);
59
+ const actual=sha(await readFile(target));
60
+ if(actual!==entry.sha256)throw new Error(`hash mismatch ${entry.rel} expected=${entry.sha256} actual=${actual}`);
61
+ }
62
+ return Object.freeze({files:entries.length,manifest_sha256:actualManifestSha,root:rootReal});
63
+ }
64
+
65
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===path.resolve(fileURLToPath(import.meta.url));
66
+ if(invoked){
67
+ try{
68
+ const opts=parseArgs(process.argv.slice(2));
69
+ const result=await verifyDeclaredParentSource(opts);
70
+ console.log(`R65_PARENT_SOURCE_VERIFY_OK files=${result.files} manifest_sha256=${result.manifest_sha256}`);
71
+ }catch(error){
72
+ console.error('R65_PARENT_SOURCE_VERIFY_FAIL '+String(error?.stack??error));
73
+ process.exitCode=1;
74
+ }
75
+ }