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.
- package/DEADBYTE-MCP.cmd +8 -4
- package/MANIFEST.SHA256 +88 -43
- package/PROVENANCE.json +66 -15
- package/README.txt +27 -18
- package/RELEASE.json +55 -15
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +117 -14
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +132 -12
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
- package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
- package/package.json +6 -4
- package/scripts/deploy-r48.mjs +123 -7
- package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
- package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
- package/scripts/npm-publication-r65.mjs +139 -0
- package/scripts/npm-release-r48.mjs +56 -37
- package/scripts/r65-canonical-full.mjs +45 -0
- package/scripts/r65-focused.mjs +37 -0
- package/scripts/verify-r65-parent-source.mjs +75 -0
- package/src/activation-continuity-engine-r65.mjs +225 -0
- package/src/activation-lock-r65.mjs +155 -0
- package/src/activation-preflight-r65.mjs +197 -0
- package/src/authority-continuity-r65.mjs +541 -0
- package/src/authority-epoch-cli-r65.mjs +10 -0
- package/src/autonomous-policy.mjs +7 -3
- package/src/coding-plane.mjs +12 -3
- package/src/desktop-policy-r40.mjs +6 -3
- package/src/doctor-runtime-r58.mjs +3 -1
- package/src/doctor-transactions-r58.mjs +1 -1
- package/src/host-plane.mjs +9 -3
- package/src/process-policy.mjs +7 -4
- package/src/remote-authority.mjs +10 -1
- package/src/runtime-update.mjs +15 -5
- package/src/version.mjs +1 -1
- package/test/audit-context.test.mjs +15 -4
- package/test/autonomous-local-supervisor.test.mjs +7 -6
- package/test/autonomous-supervisor-integration.test.mjs +8 -7
- package/test/capability-resolver.test.mjs +11 -5
- package/test/codex-autonomous-r44.test.mjs +7 -6
- package/test/coding-observation.test.mjs +4 -3
- package/test/commander-real-parity-r47.test.mjs +5 -4
- package/test/commander-torture-r47.test.mjs +5 -4
- package/test/desktop-policy-r40.test.mjs +6 -4
- package/test/desktop-runtime-r40.test.mjs +8 -3
- package/test/host-inline-r45.test.mjs +4 -3
- package/test/machine-fs-runtime.test.mjs +4 -3
- package/test/process-longrun-r36.test.mjs +3 -2
- package/test/process-policy.test.mjs +10 -6
- package/test/process-runtime.test.mjs +9 -6
- package/test/r52-host-revocation.test.mjs +1 -0
- package/test/r58-doctor-runtime.test.mjs +18 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +2 -1
- package/test/r65-activation-lock-recovery.test.mjs +72 -0
- package/test/r65-activation-lock.test.mjs +59 -0
- package/test/r65-activation-orchestration.test.mjs +179 -0
- package/test/r65-activation-preflight.test.mjs +35 -0
- package/test/r65-authority-continuity-decision.test.mjs +58 -0
- package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
- package/test/r65-authority-epoch-cli.test.mjs +125 -0
- package/test/r65-authority-epoch-races.test.mjs +87 -0
- package/test/r65-authority-epoch-recovery.test.mjs +82 -0
- package/test/r65-authority-epoch-store.test.mjs +94 -0
- package/test/r65-authority-fixture.mjs +41 -0
- package/test/r65-authority-grant-capture.test.mjs +37 -0
- package/test/r65-authority-observation-epoch.test.mjs +25 -0
- package/test/r65-authority-policy-envelope.test.mjs +46 -0
- package/test/r65-authority-restored-safe.test.mjs +75 -0
- package/test/r65-authority-runtime-epoch.test.mjs +115 -0
- package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
- package/test/r65-authority-test-trust.mjs +20 -0
- package/test/r65-cli-invocation.test.mjs +38 -0
- package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
- package/test/r65-controller-authority-epoch.test.mjs +37 -0
- package/test/r65-deploy-preflight.test.mjs +55 -0
- package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
- package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
- package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
- package/test/r65-legacy-authority-migration.test.mjs +63 -0
- package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
- package/test/r65-npm-publication.test.mjs +114 -0
- package/test/r65-parent-source-verifier.test.mjs +63 -0
- package/test/r65-release-contract.test.mjs +75 -0
- package/test/r65-transaction-precondition.test.mjs +36 -0
- package/test/r66-release-contract.test.mjs +56 -0
- package/test/remote-session-cli.test.mjs +6 -6
- 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 {
|
|
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
|
-
|
|
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
|
|
112
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
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
|
|
208
|
-
const published
|
|
209
|
-
const
|
|
210
|
-
|
|
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 (
|
|
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
|
|
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
|
+
}
|