deadbyte-mcp 0.15.33 → 0.15.35
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/MANIFEST.SHA256 +71 -22
- package/PROVENANCE.json +57 -28
- package/README.txt +22 -14
- package/RELEASE.json +41 -29
- package/SBOM.cdx.json +25 -3
- package/TEST-MATRIX.json +135 -43
- package/bridge/package.json +11 -0
- package/bridge/src/bridge.mjs +412 -0
- package/bridge/src/node-http-adapter.mjs +56 -0
- package/bridge/src/public-tool-definitions.mjs +14 -0
- package/bridge/src/r40-governor.mjs +160 -0
- package/controller/deadbyte-controller.ps1 +84 -8
- package/controller/deadbyte-process-policy.json +4 -4
- package/package.json +6 -3
- package/scripts/bridge-benchmark-r58.mjs +67 -0
- package/scripts/deploy-r48.mjs +2 -3
- package/scripts/gate-windows-r57.ps1 +27 -0
- package/scripts/gate-windows-r58.ps1 +33 -0
- package/scripts/owner-probe-r58.mjs +31 -0
- package/scripts/performance-r58.mjs +57 -0
- package/scripts/public-baseline-r58.mjs +72 -0
- package/scripts/public-surface-r58.mjs +84 -0
- package/scripts/r58-cross-plane.ps1 +6 -0
- package/scripts/r58-focused.ps1 +6 -0
- package/scripts/windows-gate-evidence-r57.mjs +81 -0
- package/scripts/windows-gate-evidence-r58.mjs +93 -0
- package/src/deadbyte-cli.mjs +40 -12
- package/src/doctor-foreground-r58.mjs +44 -0
- package/src/doctor-installation-r58.mjs +63 -0
- package/src/doctor-r45.mjs +21 -4
- package/src/doctor-runtime-r58.mjs +82 -0
- package/src/doctor-transactions-r58.mjs +40 -0
- package/src/foreground-process-identity.mjs +27 -0
- package/src/foreground-session-lock.mjs +20 -0
- package/src/foreground-session-lock.ps1 +15 -0
- package/src/release-generation.mjs +1 -0
- package/src/release-slot.mjs +2 -1
- package/src/remote-session-guardian.mjs +8 -1
- package/src/runtime-update.mjs +117 -3
- package/src/version.mjs +1 -1
- package/test/controller-r211.test.mjs +4 -1
- package/test/controller.test.mjs +2 -1
- package/test/helpers/r58-doctor-fixture.mjs +26 -0
- package/test/r49-windows-gate.test.mjs +14 -10
- package/test/r56-release-contract.test.mjs +5 -3
- package/test/r57-core-startup-race.test.mjs +85 -0
- package/test/r57-release-contract.test.mjs +67 -0
- package/test/r58-bridge-dependency-binding.test.mjs +62 -0
- package/test/r58-bundled-bridge.test.mjs +22 -0
- package/test/r58-cleanup-retention.test.mjs +45 -0
- package/test/r58-completed-update-receipt.test.mjs +76 -0
- package/test/r58-concurrent-recovery.test.mjs +54 -0
- package/test/r58-controller-core-observation.test.mjs +36 -0
- package/test/r58-controller-json.test.mjs +48 -0
- package/test/r58-controller-live-local.test.mjs +85 -0
- package/test/r58-controller-readonly.test.mjs +23 -0
- package/test/r58-controller-state-coherence.test.mjs +45 -0
- package/test/r58-doctor-cli.test.mjs +20 -0
- package/test/r58-doctor-foreground.test.mjs +50 -0
- package/test/r58-doctor-installation.test.mjs +55 -0
- package/test/r58-doctor-pointer.test.mjs +41 -0
- package/test/r58-doctor-runtime.test.mjs +45 -0
- package/test/r58-doctor-transactions.test.mjs +21 -0
- package/test/r58-guardian-channel.test.mjs +26 -0
- package/test/r58-launcher-shell.test.mjs +28 -0
- package/test/r58-owner-identity.test.mjs +20 -0
- package/test/r58-release-contract.test.mjs +49 -0
- package/test/r58-session-lock.test.mjs +26 -0
- package/test/r58-stale-owner.test.mjs +71 -0
- package/test/r58-windows-gate.test.mjs +31 -0
- package/test/release-generation.test.mjs +4 -1
- package/test/remote-session-cli.test.mjs +55 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
6
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
7
|
+
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
|
+
|
|
9
|
+
// Stage-one stdio measurements only. Never substitutes for bridge/public,
|
|
10
|
+
// machine-bound, mutating or durable-job release acceptance measurements.
|
|
11
|
+
const [targetArg,runArg]=process.argv.slice(2);
|
|
12
|
+
if(!targetArg||!runArg) throw new Error('usage: performance-r58.mjs <package-root> <new-D-testing-run-root>');
|
|
13
|
+
const target=path.resolve(targetArg), run=path.resolve(runArg);
|
|
14
|
+
const allowed=path.resolve('D:/Projects/deadbyte-mcp/testing/0.15.35');
|
|
15
|
+
const relative=path.relative(allowed,run);
|
|
16
|
+
if(!relative||relative.startsWith('..')||path.isAbsolute(relative)) throw new Error('benchmark run must be inside R58 D: testing root');
|
|
17
|
+
await mkdir(run); // Exclusive run identity; never rerun into existing evidence.
|
|
18
|
+
const state=path.join(run,'state'),workspace=path.join(run,'workspace'),demo=path.join(workspace,'demo');
|
|
19
|
+
await mkdir(demo,{recursive:true});await mkdir(state,{recursive:true});
|
|
20
|
+
const fixture='alpha benchmark fixture\n'.repeat(100);
|
|
21
|
+
await writeFile(path.join(demo,'input.txt'),fixture,{flag:'wx'});
|
|
22
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(run,'private.pem'),publicKeyPath:path.join(run,'public.pem')});
|
|
23
|
+
const policyPath=path.join(run,'coding-policy.json');
|
|
24
|
+
await writeFile(policyPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:path.join(state,'coding-grant.json'),evidence_dir:path.join(state,'evidence'),roots:{bench:{path:demo,read:true,write:false,exec:false,deny_paths:[]}},commands:{},max_read_bytes:65536,max_write_bytes:65536}),{flag:'wx'});
|
|
25
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
26
|
+
const sourceHashes={};
|
|
27
|
+
for(const file of ['package.json','MANIFEST.SHA256','src/mcp-server.mjs','src/coding-plane.mjs','src/compact-gateway-mcp-tools.mjs']) sourceHashes[file]=sha(await readFile(path.join(target,file)));
|
|
28
|
+
const workload=[
|
|
29
|
+
{name:'workspace_open',arguments:{workspace_id:'demo'}},
|
|
30
|
+
{name:'file_read',arguments:{workspace_id:'demo',path:'input.txt'}},
|
|
31
|
+
{name:'coding_session_status',arguments:{}}
|
|
32
|
+
];
|
|
33
|
+
const samples=[],cold=[];
|
|
34
|
+
let catalog;
|
|
35
|
+
const start=new Date().toISOString();
|
|
36
|
+
for(let batch=0;batch<3;batch++){
|
|
37
|
+
const client=new Client({name:'deadbyte-r58-performance',version:'1.0.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
38
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[path.join(target,'src/mcp-server.mjs')],cwd:target,stderr:'pipe',env:{...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:state,DEADBYTE_WORKSPACE_ROOT:workspace,DEADBYTE_SIGNING_KEY:keys.privateKeyPath,DEADBYTE_VERIFY_KEY:keys.publicKeyPath,DEADBYTE_PRIVILEGED_POLICY:'',DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_CODING_POLICY:policyPath,DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0',DEADBYTE_DESKTOP_RUNTIME:'0',DEADBYTE_CAPABILITY_PROFILE:'full'}});
|
|
39
|
+
const errors=[];transport.stderr?.on('data',b=>errors.push(b.toString()));
|
|
40
|
+
try{
|
|
41
|
+
const connectStart=performance.now();await client.connect(transport);cold.push({batch,connect_ms:performance.now()-connectStart});
|
|
42
|
+
const tools=(await client.listTools()).tools;
|
|
43
|
+
if(!catalog){catalog=tools;await writeFile(path.join(run,'catalog.json'),JSON.stringify(tools,null,2),{flag:'wx'});}
|
|
44
|
+
for(let i=0;i<220;i++)for(const request of workload){
|
|
45
|
+
const t=performance.now();const result=await client.callTool(request);const elapsed=performance.now()-t;
|
|
46
|
+
if(result.isError) throw new Error(`benchmark ${request.name} error: ${JSON.stringify(result.structuredContent??result.content)}`);
|
|
47
|
+
if(request.name==='file_read'&&result.structuredContent?.text!==fixture)throw new Error('file read behavioral oracle failed');
|
|
48
|
+
if(i>=20)samples.push({batch,iteration:i-20,tool:request.name,total_stdio_roundtrip_ms:elapsed});
|
|
49
|
+
}
|
|
50
|
+
}finally{await client.close();await writeFile(path.join(run,`stderr-${batch}.log`),errors.join(''),{flag:'wx'});}
|
|
51
|
+
}
|
|
52
|
+
function summary(values){const v=[...values].sort((a,b)=>a-b);const q=p=>v[Math.ceil(p*v.length)-1];return {n:v.length,min:v[0],p50:q(.5),p90:q(.9),p95:q(.95),p99:q(.99),max:v.at(-1)};}
|
|
53
|
+
const rows=Object.fromEntries(workload.map(w=>[w.name,summary(samples.filter(s=>s.tool===w.name).map(s=>s.total_stdio_roundtrip_ms))]));
|
|
54
|
+
const result={schema:'deadbyte.r58.performance-stage1.v1',status:'MEASURED_NOT_RELEASE_ACCEPTANCE',start,end:new Date().toISOString(),target,node:process.version,sourceHashes,workload_sha256:sha(JSON.stringify(workload)),fixture_sha256:sha(fixture),cold_start:cold,rows,aggregate:summary(samples.map(s=>s.total_stdio_roundtrip_ms)),failures:0,timeouts:0,scope:'stdio client through MCP dispatch/core/response; coding disarmed; no live bridge/cloud or active machine binding',missing:['capability_manifest (compact profile)','machine_status','machine_ping','mutating actions','durable jobs','bridge/cloud','latency decomposition','contention','candidate comparison']};
|
|
55
|
+
await writeFile(path.join(run,'raw.json'),JSON.stringify(samples),{flag:'wx'});
|
|
56
|
+
await writeFile(path.join(run,'summary.json'),JSON.stringify(result,null,2),{flag:'wx'});
|
|
57
|
+
console.log(JSON.stringify(result,null,2));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
+
import { performance } from 'node:perf_hooks';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
6
|
+
import { verifyMachinePingPublic } from '../src/machine-identity.mjs';
|
|
7
|
+
|
|
8
|
+
const run=path.resolve(process.argv[2]||'');
|
|
9
|
+
const mode=process.argv[3]||'public';
|
|
10
|
+
if(!['public','local'].includes(mode))throw new Error('transport must be public or local');
|
|
11
|
+
const base=path.resolve('D:/Projects/deadbyte-mcp/testing/0.15.35');
|
|
12
|
+
const rel=path.relative(base,run);
|
|
13
|
+
if(!rel||rel.startsWith('..')||path.isAbsolute(rel))throw new Error('new R58 testing run root required');
|
|
14
|
+
await mkdir(run);
|
|
15
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
16
|
+
const prod='C:/Users/DEADBYTE/.deadbyte-mcp';
|
|
17
|
+
const before=await readFile(path.join(prod,'active-release.json'));
|
|
18
|
+
const pointer=JSON.parse(before);
|
|
19
|
+
if(pointer.release_id!=='v0.15.34-2199bb59f0e0fc20'||pointer.manifest_sha256!=='2199bb59f0e0fc205d470d11f990d3ce3df444be5eba2a7d8dbc60d5f5b790bb')throw new Error('production baseline changed');
|
|
20
|
+
const config=await readFile(pointer.config_path,'utf8');
|
|
21
|
+
const urlPath=config.match(/McpUrlFile\s*=\s*'([^']+)'/)?.[1];
|
|
22
|
+
const urlHash=config.match(/McpUrlFileSha256\s*=\s*'([0-9a-f]{64})'/)?.[1];
|
|
23
|
+
if(!urlPath||!urlHash)throw new Error('pinned public endpoint metadata missing');
|
|
24
|
+
const bytes=await readFile(urlPath);
|
|
25
|
+
if(sha(bytes)!==urlHash)throw new Error('public endpoint metadata hash mismatch');
|
|
26
|
+
const endpoint=new URL(bytes.toString().trim());
|
|
27
|
+
if(endpoint.protocol!=='https:')throw new Error('HTTPS endpoint required');
|
|
28
|
+
let transportEndpoint=endpoint;
|
|
29
|
+
if(mode==='local'){
|
|
30
|
+
const readyText=config.match(/LocalReadyUri\s*=\s*'([^']+)'/)?.[1];
|
|
31
|
+
if(!readyText)throw new Error('pinned loopback readiness address missing');
|
|
32
|
+
const ready=new URL(readyText);
|
|
33
|
+
if(ready.protocol!=='http:'||ready.hostname!=='127.0.0.1')throw new Error('local benchmark must use configured loopback only');
|
|
34
|
+
transportEndpoint=new URL(endpoint.pathname+endpoint.search,ready.origin);
|
|
35
|
+
}
|
|
36
|
+
const client=new Client({name:'deadbyte-r58-public-readonly-baseline',version:'1.0.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
37
|
+
const traceFetch=process.argv[4]==='--trace-fetch';
|
|
38
|
+
const httpEvents=[];
|
|
39
|
+
const measuredFetch=async(input,init)=>{
|
|
40
|
+
let request;try{request=JSON.parse(init?.body||'null');}catch{}
|
|
41
|
+
const event={method:request?.method??init?.method??'GET',tool:request?.params?.name??null,started_ms:performance.now()};
|
|
42
|
+
httpEvents.push(event);
|
|
43
|
+
const response=await fetch(input,init);
|
|
44
|
+
event.headers_ms=performance.now();event.fetch_to_headers_ms=event.headers_ms-event.started_ms;
|
|
45
|
+
event.http_status=response.status;event.content_type=response.headers.get('content-type');
|
|
46
|
+
const json=response.json.bind(response);
|
|
47
|
+
response.json=async()=>{const t=performance.now();try{return await json();}finally{event.json_body_ms=performance.now()-t;event.body_finished_ms=performance.now();}};
|
|
48
|
+
return response;
|
|
49
|
+
};
|
|
50
|
+
const transport=new StreamableHTTPClientTransport(transportEndpoint,traceFetch?{fetch:measuredFetch}:undefined);
|
|
51
|
+
const samples=[];const start=new Date().toISOString();
|
|
52
|
+
function body(result){if(result.isError)throw new Error('public read-only call failed');const s=result.structuredContent;if(!s||typeof s!=='object')throw new Error('structured response missing');return s.result??s;}
|
|
53
|
+
try{
|
|
54
|
+
await client.connect(transport);
|
|
55
|
+
const catalog=(await client.listTools()).tools;
|
|
56
|
+
await writeFile(path.join(run,'catalog.json'),JSON.stringify(catalog,null,2),{flag:'wx'});
|
|
57
|
+
const status=body(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}));
|
|
58
|
+
if(status.release_id!==pointer.release_id||status.release_manifest_sha256!==pointer.manifest_sha256||status.generation_manifest_sha256!==pointer.generation_manifest_sha256)throw new Error('public machine baseline binding mismatch');
|
|
59
|
+
for(let i=0;i<100;i++){
|
|
60
|
+
const nonce=randomBytes(24).toString('hex');const t=performance.now();const eventOffset=httpEvents.length;
|
|
61
|
+
const ping=body(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}));
|
|
62
|
+
const elapsed=performance.now()-t;
|
|
63
|
+
if(ping.nonce!==nonce||ping.release_id!==pointer.release_id||ping.generation_manifest_sha256!==pointer.generation_manifest_sha256||!await verifyMachinePingPublic({ping,publicKeySpkiBase64:status.public_key_spki_base64}))throw new Error('public signed ping independent verification failed');
|
|
64
|
+
samples.push({tool:'machine_ping',iteration:i,public_roundtrip_ms:elapsed,...(traceFetch?{http_requests:httpEvents.slice(eventOffset).map(e=>({...e,after_body_to_client_ms:e.body_finished_ms===undefined?null:t+elapsed-e.body_finished_ms}))}:{})});
|
|
65
|
+
}
|
|
66
|
+
if(!before.equals(await readFile(path.join(prod,'active-release.json'))))throw new Error('production pointer changed during snapshot');
|
|
67
|
+
const v=samples.map(x=>x.public_roundtrip_ms).sort((a,b)=>a-b),q=p=>v[Math.ceil(v.length*p)-1];
|
|
68
|
+
const result={schema:'deadbyte.r58.public-baseline.v1',status:'OBSERVED_BASELINE_NOT_CANDIDATE_ACCEPTANCE',transport_scope:mode==='local'?'LOOPBACK_BRIDGE':'HTTPS_PUBLIC',start,end:new Date().toISOString(),pointer,endpoint_metadata_sha256:urlHash,catalog_count:catalog.length,catalog_sha256:sha(JSON.stringify(catalog)),signed_ping_count:samples.length,latency:{n:v.length,min:v[0],p50:q(.5),p90:q(.9),p95:q(.95),p99:q(.99),max:v.at(-1)},limitations:['100 samples only','transport difference is not exact causal layer decomposition','no candidate comparison','read-only observation not authority lifecycle acceptance']};
|
|
69
|
+
await writeFile(path.join(run,'raw.json'),JSON.stringify(samples),{flag:'wx'});
|
|
70
|
+
await writeFile(path.join(run,'summary.json'),JSON.stringify(result,null,2),{flag:'wx'});
|
|
71
|
+
console.log(JSON.stringify({status:result.status,catalog_count:catalog.length,signed_ping_count:samples.length,latency:result.latency}));
|
|
72
|
+
} finally {await client.close();}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {createHash,randomBytes} from 'node:crypto';
|
|
2
|
+
import {createServer} from 'node:net';
|
|
3
|
+
import {mkdir,readFile,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {spawn} from 'node:child_process';
|
|
5
|
+
import {setTimeout as delay} from 'node:timers/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {Client,StreamableHTTPClientTransport} from '@modelcontextprotocol/client';
|
|
8
|
+
import {generateTrustKeypair} from '../src/trust.mjs';
|
|
9
|
+
|
|
10
|
+
const [generationArg,baselineArg,runArg]=process.argv.slice(2);
|
|
11
|
+
if(!generationArg||!baselineArg||!runArg) throw new Error('usage: public-surface-r58.mjs <generation-root> <baseline-catalog-json> <new-run-root>');
|
|
12
|
+
const root=path.resolve(new URL('..',import.meta.url).pathname.replace(/^\/(?:[A-Za-z]:)/,m=>m.slice(1)));
|
|
13
|
+
const gen=path.resolve(generationArg),baselinePath=path.resolve(baselineArg),run=path.resolve(runArg);
|
|
14
|
+
const allowed=path.resolve('D:/Projects/deadbyte-mcp/testing/0.15.35'),rel=path.relative(allowed,run);
|
|
15
|
+
if(!rel||rel.startsWith('..')||path.isAbsolute(rel)) throw new Error('run root must be inside R58 testing root');
|
|
16
|
+
await mkdir(run);
|
|
17
|
+
const state=path.join(run,'state');await mkdir(state);
|
|
18
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(run,'private.pem'),publicKeyPath:path.join(run,'public.pem')});
|
|
19
|
+
const semanticPath=path.join(gen,'semantic-lsp-config.json');
|
|
20
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
21
|
+
const semanticSha=sha(await readFile(semanticPath));
|
|
22
|
+
const baseline=JSON.parse(await readFile(baselinePath,'utf8'));
|
|
23
|
+
const baseSurface=baseline.map(x=>({name:x.name,inputSchema:x.inputSchema,outputSchema:x.outputSchema})).sort((a,b)=>a.name.localeCompare(b.name));
|
|
24
|
+
const reservation=createServer();await new Promise(resolve=>reservation.listen(0,'127.0.0.1',resolve));const port=reservation.address().port;await new Promise(resolve=>reservation.close(resolve));
|
|
25
|
+
const token=randomBytes(32).toString('hex');
|
|
26
|
+
const env={...process.env};for(const key of Object.keys(env))if(key.startsWith('DEADBYTE_'))delete env[key];
|
|
27
|
+
Object.assign(env,{
|
|
28
|
+
DEADBYTE_CORE_ROOT:root,
|
|
29
|
+
DEADBYTE_STATE_ROOT:state,
|
|
30
|
+
DEADBYTE_WORKSPACE_ROOT:path.join(root,'workspaces'),
|
|
31
|
+
DEADBYTE_BRIDGE_PORT:String(port),
|
|
32
|
+
DEADBYTE_BRIDGE_PATH_TOKEN:token,
|
|
33
|
+
DEADBYTE_SIGNING_KEY:keys.privateKeyPath,
|
|
34
|
+
DEADBYTE_VERIFY_KEY:keys.publicKeyPath,
|
|
35
|
+
DEADBYTE_CAPABILITY_PROFILE:'compact',
|
|
36
|
+
DEADBYTE_AUDIT_RUNTIME:'0',
|
|
37
|
+
DEADBYTE_MACHINE_RUNTIME:'1',
|
|
38
|
+
DEADBYTE_AGENT_RUNTIME:'1',
|
|
39
|
+
DEADBYTE_PROCESS_RUNTIME:'1',
|
|
40
|
+
DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
|
|
41
|
+
DEADBYTE_CODING_RUNTIME:'1',
|
|
42
|
+
DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),
|
|
43
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
44
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
45
|
+
DEADBYTE_DESKTOP_RUNTIME:'1',
|
|
46
|
+
DEADBYTE_DESKTOP_POLICY:path.join(gen,'desktop-policy.json'),
|
|
47
|
+
DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,
|
|
48
|
+
DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
|
|
49
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
50
|
+
DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
|
|
51
|
+
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external',
|
|
52
|
+
DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')
|
|
53
|
+
});
|
|
54
|
+
const bridge=path.join(root,'bridge','src','bridge.mjs');
|
|
55
|
+
const child=spawn(process.execPath,[bridge],{cwd:path.join(root,'bridge'),env,windowsHide:true,stdio:['ignore','pipe','pipe']});
|
|
56
|
+
let stdout='',stderr='';child.stdout.on('data',b=>stdout+=b);child.stderr.on('data',b=>stderr+=b);
|
|
57
|
+
const exited=new Promise(resolve=>child.once('exit',code=>resolve(code)));
|
|
58
|
+
try{
|
|
59
|
+
const deadline=Date.now()+20000;while(!stdout.includes('BRIDGE_READY')&&child.exitCode===null&&Date.now()<deadline)await delay(20);
|
|
60
|
+
if(!stdout.includes('BRIDGE_READY')) throw new Error('candidate bridge failed startup: '+stderr);
|
|
61
|
+
const client=new Client({name:'deadbyte-r58-public-surface',version:'1.0.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
62
|
+
try{
|
|
63
|
+
await client.connect(new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${port}/mcp/${token}`)));
|
|
64
|
+
const listed=(await client.listTools()).tools;
|
|
65
|
+
const candidate=listed.map(x=>({name:x.name,inputSchema:x.inputSchema,outputSchema:x.outputSchema})).sort((a,b)=>a.name.localeCompare(b.name));
|
|
66
|
+
const baseNames=baseSurface.map(x=>x.name),candidateNames=candidate.map(x=>x.name);
|
|
67
|
+
if(JSON.stringify(candidateNames)!==JSON.stringify(baseNames)) throw new Error(`public compact name drift baseline=${baseNames.length} candidate=${candidateNames.length}`);
|
|
68
|
+
const drift=[];
|
|
69
|
+
for(let i=0;i<baseSurface.length;i++){
|
|
70
|
+
if(JSON.stringify(baseSurface[i].inputSchema)!==JSON.stringify(candidate[i].inputSchema)||JSON.stringify(baseSurface[i].outputSchema)!==JSON.stringify(candidate[i].outputSchema)) drift.push(baseSurface[i].name);
|
|
71
|
+
}
|
|
72
|
+
if(drift.length) throw new Error('public schema drift: '+drift.join(','));
|
|
73
|
+
const result={schema:'deadbyte.r58.public-surface-compat.v1',status:'PASS',tool_count:candidate.length,names_sha256:sha(Buffer.from(JSON.stringify(candidateNames))),schema_sha256:sha(Buffer.from(JSON.stringify(candidate))),baseline_catalog_sha256:sha(await readFile(baselinePath)),candidate_bridge_sha256:sha(await readFile(bridge)),drift:[]};
|
|
74
|
+
await writeFile(path.join(run,'candidate-catalog.json'),JSON.stringify(listed,null,2));
|
|
75
|
+
await writeFile(path.join(run,'summary.json'),JSON.stringify(result,null,2));
|
|
76
|
+
console.log(JSON.stringify(result,null,2));
|
|
77
|
+
}finally{await client.close().catch(()=>{});}
|
|
78
|
+
}finally{
|
|
79
|
+
child.kill('SIGTERM');
|
|
80
|
+
await Promise.race([exited,delay(5000)]);
|
|
81
|
+
if(child.exitCode===null) child.kill();
|
|
82
|
+
await writeFile(path.join(run,'bridge.stdout.log'),stdout);
|
|
83
|
+
await writeFile(path.join(run,'bridge.stderr.log'),stderr);
|
|
84
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
Set-StrictMode -Version 3.0
|
|
3
|
+
$root=Split-Path $PSScriptRoot -Parent
|
|
4
|
+
Set-Location -LiteralPath $root
|
|
5
|
+
& node.exe --test --test-concurrency=1 test/r58-concurrent-recovery.test.mjs test/r58-controller-core-observation.test.mjs test/r58-controller-state-coherence.test.mjs test/r58-guardian-channel.test.mjs test/r58-owner-identity.test.mjs test/r58-session-lock.test.mjs test/r58-stale-owner.test.mjs test/remote-session-cli.test.mjs test/coding-runtime.test.mjs test/durable-job-r44.test.mjs test/external-job-provider-r44.test.mjs test/autonomous-local-supervisor.test.mjs test/autonomous-supervisor-integration.test.mjs test/runtime-self-update.test.mjs test/release-slot.test.mjs test/release-activation-gate.test.mjs test/named-tunnel-host.test.mjs test/r56-rollback-launcher-restore.test.mjs test/r57-core-startup-race.test.mjs
|
|
6
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
Set-StrictMode -Version 3.0
|
|
3
|
+
$root=Split-Path $PSScriptRoot -Parent
|
|
4
|
+
Set-Location -LiteralPath $root
|
|
5
|
+
& node.exe --test --test-concurrency=1 test/r58-*.test.mjs test/release-generation.test.mjs test/runtime-self-update.test.mjs test/r57-core-startup-race.test.mjs test/r56-rollback-launcher-restore.test.mjs test/r51-runtime-coherence.test.mjs test/production-docs.test.mjs
|
|
6
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { doctor } from '../src/doctor-r45.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
9
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const insist=(ok,message)=>{if(!ok)throw new Error(message);};
|
|
12
|
+
const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
|
|
13
|
+
const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
|
|
14
|
+
const provenance=JSON.parse(await readFile(path.join(root,'PROVENANCE.json'),'utf8'));
|
|
15
|
+
const matrix=JSON.parse(await readFile(path.join(root,'TEST-MATRIX.json'),'utf8'));
|
|
16
|
+
const active=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
17
|
+
const manifestSha256=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
|
|
18
|
+
|
|
19
|
+
insist(pkg.version==='0.15.34'&&release.version===pkg.version&&release.train==='R57','R57 candidate identity mismatch');
|
|
20
|
+
insist(release.parent.version==='0.15.33','R57 parent mismatch');
|
|
21
|
+
insist(release.parent.manifest_sha256==='776a51cb916e28ddc3cbb2ecd3857151a0e73e0aaf83d4e6308e6674125dd01c','R57 parent manifest mismatch');
|
|
22
|
+
insist(release.parent.generation_manifest_sha256==='20ccc5e4bbf8470b61e0a6d2ad181dad5e8533da980ceba7749e1d6f48d9dbd3','R57 parent generation mismatch');
|
|
23
|
+
insist(release.parent.tarball_sha256==='d84a6df0a972a43c50d5e31242d9217f59858b2d73c4284af296da76d2048e5f','R57 parent tarball mismatch');
|
|
24
|
+
insist(
|
|
25
|
+
active.release_id===release.runtime_predecessor.release_id&&
|
|
26
|
+
active.manifest_sha256===release.runtime_predecessor.manifest_sha256&&
|
|
27
|
+
active.generation_manifest_sha256===release.runtime_predecessor.generation_manifest_sha256,
|
|
28
|
+
'active runtime is not exact sealed 0.15.33 predecessor'
|
|
29
|
+
);
|
|
30
|
+
insist(release.hotfix?.bounded_core_discovery_after_online===true,'R57 bounded core discovery missing');
|
|
31
|
+
insist(release.hotfix?.retry_only_missing_core===true,'R57 zero-child-only retry contract missing');
|
|
32
|
+
insist(release.hotfix?.ambiguous_core_fail_closed===true,'R57 ambiguous-core fail-closed contract missing');
|
|
33
|
+
insist(release.hotfix?.timeout_fail_closed===true,'R57 timeout fail-closed contract missing');
|
|
34
|
+
insist(release.hotfix?.full_regression==='PASS','R57 full regression metadata is not PASS');
|
|
35
|
+
insist(matrix.observed_pre_release_evidence?.full_regression?.status==='PASS','R57 full regression evidence is not PASS');
|
|
36
|
+
insist(provenance.operator_override?.stable_final_claim===false,'R57 must not claim stable/final');
|
|
37
|
+
insist(matrix.operator_override?.four_hour_soak==='NOT_COMPLETED','R57 matrix must preserve soak bypass');
|
|
38
|
+
|
|
39
|
+
const health=await doctor();
|
|
40
|
+
insist(health.status==='ok'&&health.package_version===pkg.version&&health.manifest?.verified===true,'R57 doctor evidence invalid');
|
|
41
|
+
|
|
42
|
+
const evidenceBody={
|
|
43
|
+
schema:'deadbyte.r57-windows-gate.v1',
|
|
44
|
+
status:'passed',
|
|
45
|
+
exit_code:0,
|
|
46
|
+
version:pkg.version,
|
|
47
|
+
train:'R57',
|
|
48
|
+
completed_at_utc:new Date().toISOString(),
|
|
49
|
+
manifest_sha256:manifestSha256,
|
|
50
|
+
parent:release.parent,
|
|
51
|
+
runtime_predecessor:release.runtime_predecessor,
|
|
52
|
+
hotfix:release.hotfix,
|
|
53
|
+
operator_override:provenance.operator_override,
|
|
54
|
+
active_predecessor:{
|
|
55
|
+
release_id:active.release_id,
|
|
56
|
+
version:active.version,
|
|
57
|
+
manifest_sha256:active.manifest_sha256,
|
|
58
|
+
generation_manifest_sha256:active.generation_manifest_sha256
|
|
59
|
+
},
|
|
60
|
+
doctor:{verified_entries:health.manifest.verified_entries},
|
|
61
|
+
gates:{
|
|
62
|
+
r57_focused:true,
|
|
63
|
+
package_boundary:true,
|
|
64
|
+
full_regression:true,
|
|
65
|
+
manifest_final_verify:true,
|
|
66
|
+
four_hour_soak_completed:false
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const bytes=Buffer.from(JSON.stringify(evidenceBody,null,2)+'\n');
|
|
70
|
+
const dir=path.join(stateRoot,'evidence','release');
|
|
71
|
+
await mkdir(dir,{recursive:true});
|
|
72
|
+
const target=path.join(dir,`r57-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
73
|
+
try{
|
|
74
|
+
const handle=await open(target,'wx',0o600);
|
|
75
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
76
|
+
}catch(error){
|
|
77
|
+
if(error?.code!=='EEXIST')throw error;
|
|
78
|
+
const prior=JSON.parse(await readFile(target,'utf8'));
|
|
79
|
+
insist(prior.schema===evidenceBody.schema&&prior.status==='passed'&&prior.version===evidenceBody.version&&prior.manifest_sha256===manifestSha256,'conflicting R57 gate evidence identity');
|
|
80
|
+
}
|
|
81
|
+
console.log(JSON.stringify({status:'passed',path:target,sha256:sha256(bytes),manifest_sha256:manifestSha256}));
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {mkdir,open,readFile} from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {doctor} from '../src/doctor-r45.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
9
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const insist=(ok,message)=>{if(!ok)throw new Error(message);};
|
|
12
|
+
const [pkg,release,provenance,matrix,active]=await Promise.all([
|
|
13
|
+
readFile(path.join(root,'package.json'),'utf8').then(JSON.parse),
|
|
14
|
+
readFile(path.join(root,'RELEASE.json'),'utf8').then(JSON.parse),
|
|
15
|
+
readFile(path.join(root,'PROVENANCE.json'),'utf8').then(JSON.parse),
|
|
16
|
+
readFile(path.join(root,'TEST-MATRIX.json'),'utf8').then(JSON.parse),
|
|
17
|
+
readFile(path.join(stateRoot,'active-release.json'),'utf8').then(JSON.parse)
|
|
18
|
+
]);
|
|
19
|
+
const manifestSha256=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
|
|
20
|
+
|
|
21
|
+
insist(pkg.version==='0.15.35'&&release.version===pkg.version&&release.train==='R58','R58 candidate identity mismatch');
|
|
22
|
+
insist(provenance.version===pkg.version&&matrix.version===pkg.version&&matrix.train==='R58','R58 release metadata identity mismatch');
|
|
23
|
+
insist(release.parent?.version==='0.15.34','R58 parent version mismatch');
|
|
24
|
+
insist(release.parent?.release_id==='v0.15.34-2199bb59f0e0fc20','R58 parent release mismatch');
|
|
25
|
+
insist(release.parent?.manifest_sha256==='2199bb59f0e0fc205d470d11f990d3ce3df444be5eba2a7d8dbc60d5f5b790bb','R58 parent manifest mismatch');
|
|
26
|
+
insist(release.parent?.generation_manifest_sha256==='1a20a62aaeb324a9b5c03d158952f35092d58fedf2ac7725820ba2b094a4a88f','R58 parent generation mismatch');
|
|
27
|
+
insist(release.parent?.tarball_sha256==='a6f7b21faf34793ca17dfb6d0e9ec03320f1468d011e75d8349098df82fc2639','R58 parent tarball mismatch');
|
|
28
|
+
insist(
|
|
29
|
+
active.release_id===release.runtime_predecessor.release_id&&
|
|
30
|
+
active.manifest_sha256===release.runtime_predecessor.manifest_sha256&&
|
|
31
|
+
active.generation_manifest_sha256===release.runtime_predecessor.generation_manifest_sha256,
|
|
32
|
+
'active runtime is not exact sealed 0.15.34 predecessor'
|
|
33
|
+
);
|
|
34
|
+
insist(release.consolidation?.bridge_bundled_inside_release===true,'R58 bundled bridge contract missing');
|
|
35
|
+
insist(release.consolidation?.bridge_recursive_local_dependency_binding===true,'R58 recursive bridge dependency contract missing');
|
|
36
|
+
insist(release.consolidation?.performance_acceptance==='PASS_BOUNDED_PREPUBLISH','R58 performance acceptance metadata not green');
|
|
37
|
+
insist(release.consolidation?.cross_plane_acceptance==='PASS','R58 cross-plane acceptance metadata not green');
|
|
38
|
+
insist(release.consolidation?.public_surface_compatibility==='PASS_43_TOOLS_NO_SCHEMA_DRIFT','R58 public surface contract not green');
|
|
39
|
+
insist(matrix.observed_pre_release_evidence?.full_regression?.status==='PASS','R58 fresh full regression evidence not green');
|
|
40
|
+
insist(matrix.observed_pre_release_evidence?.performance?.status==='PASS_BOUNDED_PREPUBLISH','R58 performance evidence not green');
|
|
41
|
+
insist(matrix.observed_pre_release_evidence?.cross_plane?.status==='PASS','R58 cross-plane evidence not green');
|
|
42
|
+
insist(matrix.observed_pre_release_evidence?.public_surface?.status==='PASS','R58 public surface evidence not green');
|
|
43
|
+
insist(provenance.operator_override?.four_hour_soak==='NOT_COMPLETED','R58 must preserve incomplete soak truth');
|
|
44
|
+
insist(provenance.operator_override?.clean_machine==='NOT_COMPLETED','R58 must preserve incomplete clean-machine truth');
|
|
45
|
+
insist(provenance.operator_override?.stable_final_claim===false,'R58 must not claim stable/final before external evidence');
|
|
46
|
+
|
|
47
|
+
const health=await doctor();
|
|
48
|
+
insist(health.assessment!=='FAIL'&&['ok','warn'].includes(health.status)&&health.package_version===pkg.version&&health.manifest?.verified===true,'R58 doctor evidence invalid');
|
|
49
|
+
|
|
50
|
+
const evidenceBody={
|
|
51
|
+
schema:'deadbyte.r58-windows-gate.v1',
|
|
52
|
+
status:'passed',
|
|
53
|
+
exit_code:0,
|
|
54
|
+
version:pkg.version,
|
|
55
|
+
train:'R58',
|
|
56
|
+
completed_at_utc:new Date().toISOString(),
|
|
57
|
+
manifest_sha256:manifestSha256,
|
|
58
|
+
parent:release.parent,
|
|
59
|
+
runtime_predecessor:release.runtime_predecessor,
|
|
60
|
+
consolidation:release.consolidation,
|
|
61
|
+
operator_override:provenance.operator_override,
|
|
62
|
+
active_predecessor:{
|
|
63
|
+
release_id:active.release_id,
|
|
64
|
+
version:active.version,
|
|
65
|
+
manifest_sha256:active.manifest_sha256,
|
|
66
|
+
generation_manifest_sha256:active.generation_manifest_sha256
|
|
67
|
+
},
|
|
68
|
+
doctor:{status:health.status,assessment:health.assessment,verified_entries:health.manifest.verified_entries,warnings:health.findings.filter(item=>item.level==='WARN').length},
|
|
69
|
+
gates:{
|
|
70
|
+
r58_focused:true,
|
|
71
|
+
package_boundary:true,
|
|
72
|
+
process_policy:true,
|
|
73
|
+
performance_recorded:true,
|
|
74
|
+
cross_plane_recorded:true,
|
|
75
|
+
public_surface_recorded:true,
|
|
76
|
+
full_regression_recorded:true,
|
|
77
|
+
manifest_final_verify:true,
|
|
78
|
+
four_hour_soak_completed:false,
|
|
79
|
+
clean_machine_completed:false
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const bytes=Buffer.from(JSON.stringify(evidenceBody,null,2)+'\n');
|
|
83
|
+
const dir=path.join(stateRoot,'evidence','release');await mkdir(dir,{recursive:true});
|
|
84
|
+
const target=path.join(dir,`r58-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
85
|
+
try{
|
|
86
|
+
const handle=await open(target,'wx',0o600);
|
|
87
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
88
|
+
}catch(error){
|
|
89
|
+
if(error?.code!=='EEXIST')throw error;
|
|
90
|
+
const prior=JSON.parse(await readFile(target,'utf8'));
|
|
91
|
+
insist(prior.schema===evidenceBody.schema&&prior.status==='passed'&&prior.version===evidenceBody.version&&prior.manifest_sha256===manifestSha256,'conflicting R58 gate evidence identity');
|
|
92
|
+
}
|
|
93
|
+
console.log(JSON.stringify({status:'passed',path:target,sha256:sha256(bytes),manifest_sha256:manifestSha256}));
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -18,6 +18,8 @@ import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
|
|
|
18
18
|
import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
|
|
19
19
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
20
20
|
import { resolveStateRoot } from './state-root.mjs';
|
|
21
|
+
import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
|
|
22
|
+
import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
|
|
21
23
|
|
|
22
24
|
const REMOTE_COMMANDS=new Set([
|
|
23
25
|
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
@@ -200,7 +202,7 @@ function controllerRuntimeReady(text){
|
|
|
200
202
|
/^Loop supervisor:\s*RUNNING\s+\[verified\]\s*$/m.test(value);
|
|
201
203
|
}
|
|
202
204
|
function controllerRuntimeDegraded(text){
|
|
203
|
-
return /^DEADBYTE MCP\s*=\s*DEGRADED\s*$/m.test(String(text||''));
|
|
205
|
+
return /^DEADBYTE MCP\s*=\s*(?:DEGRADED|FAILED)\s*$/m.test(String(text||''));
|
|
204
206
|
}
|
|
205
207
|
async function listLogFiles(root){
|
|
206
208
|
const auditDir=path.join(root,'audit');
|
|
@@ -352,17 +354,20 @@ function remoteTag(kind,displayMode){
|
|
|
352
354
|
}
|
|
353
355
|
function writeRemote(stream,text,displayMode){stream.write(remoteDisplayText(text,displayMode));}
|
|
354
356
|
async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
357
|
+
const failed=[];
|
|
355
358
|
for(const command of SESSION_CLEANUP){
|
|
356
359
|
try{
|
|
357
|
-
const result=invokeController(ctx,command,{allowFailure:true,capture:true,echoCaptured:false});
|
|
358
|
-
if(result.status!==0)
|
|
360
|
+
const result=invokeController(ctx,command,{allowFailure:true,capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
361
|
+
if(result.status!==0){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command} exit=${result.status}\n`,displayMode);}
|
|
359
362
|
}
|
|
360
|
-
catch(error){writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
|
|
363
|
+
catch(error){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
|
|
361
364
|
}
|
|
365
|
+
if(failed.length) throw new Error(`remote session cleanup failed; ownership marker retained: ${failed.join(',')}`);
|
|
362
366
|
}
|
|
363
367
|
const GUARDIAN_SCRIPT=fileURLToPath(new URL('./remote-session-guardian.mjs',import.meta.url));
|
|
364
368
|
function remoteSessionMarker(root){return path.join(root,'runtime','remote-session.json');}
|
|
365
369
|
function pidAlive(pid){
|
|
370
|
+
if(!Number.isInteger(pid)||pid<1)return false;
|
|
366
371
|
try{process.kill(pid,0);return true;}
|
|
367
372
|
catch(error){return error?.code!=='ESRCH';}
|
|
368
373
|
}
|
|
@@ -376,9 +381,10 @@ async function readRemoteSessionMarker(root){
|
|
|
376
381
|
return value;
|
|
377
382
|
}
|
|
378
383
|
if(value?.schema==='deadbyte.remote-session.v2'){
|
|
384
|
+
for(const key of ['owner_identity','guardian_identity'])if(value[key]!==undefined&&!validForegroundIdentity(value[key]))throw new Error('remote session ownership identity invalid');
|
|
379
385
|
if(!Number.isInteger(value?.owner_pid)||value.owner_pid<1||
|
|
380
386
|
!/^[0-9a-f]{64}$/.test(String(value?.session_nonce||''))||
|
|
381
|
-
!Number.isInteger(value?.guardian_pid)||value.guardian_pid<
|
|
387
|
+
!Number.isInteger(value?.guardian_pid)||value.guardian_pid<0||
|
|
382
388
|
(value.result_session_id!==undefined&&!/^[0-9a-f]{24}$/.test(String(value.result_session_id)))) throw new Error('remote session marker schema invalid');
|
|
383
389
|
return value;
|
|
384
390
|
}
|
|
@@ -388,8 +394,18 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
388
394
|
let prior=await readRemoteSessionMarker(ctx.root);
|
|
389
395
|
if(!prior) return;
|
|
390
396
|
const ownerPid=remoteOwnerPid(prior);
|
|
391
|
-
if(pidAlive(ownerPid))
|
|
392
|
-
|
|
397
|
+
if(pidAlive(ownerPid)){
|
|
398
|
+
if(!prior.owner_identity)throw new Error(`remote session ownership unverified for legacy pid=${ownerPid}; marker retained`);
|
|
399
|
+
if(prior.owner_identity.pid!==ownerPid)throw new Error('remote session owner identity PID binding mismatch');
|
|
400
|
+
if(foregroundIdentityMatches(prior.owner_identity,queryForegroundIdentity(ownerPid)))throw new Error(`remote session already active pid=${ownerPid}`);
|
|
401
|
+
}
|
|
402
|
+
let guardianAlive=prior.schema==='deadbyte.remote-session.v2'&&pidAlive(prior.guardian_pid);
|
|
403
|
+
if(guardianAlive){
|
|
404
|
+
if(!prior.guardian_identity)throw new Error('remote guardian ownership unverified; marker retained');
|
|
405
|
+
if(prior.guardian_identity.pid!==prior.guardian_pid)throw new Error('remote guardian identity PID binding mismatch');
|
|
406
|
+
guardianAlive=foregroundIdentityMatches(prior.guardian_identity,queryForegroundIdentity(prior.guardian_pid));
|
|
407
|
+
}
|
|
408
|
+
if(guardianAlive){
|
|
393
409
|
const deadline=Date.now()+3000;
|
|
394
410
|
while(Date.now()<deadline){
|
|
395
411
|
await sleep(100);
|
|
@@ -409,18 +425,23 @@ async function claimRemoteSession(ctx){
|
|
|
409
425
|
const resultSessionId=randomBytes(12).toString('hex');
|
|
410
426
|
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:0,session_nonce:nonce,
|
|
411
427
|
result_session_id:resultSessionId,started_at_utc:new Date().toISOString()};
|
|
428
|
+
marker.owner_identity=queryForegroundIdentity(process.pid);
|
|
429
|
+
if(!marker.owner_identity)throw new Error('remote foreground identity unavailable');
|
|
412
430
|
const markerPath=remoteSessionMarker(ctx.root);
|
|
413
431
|
try{await writeFile(markerPath,`${JSON.stringify(marker)}\n`,{encoding:'utf8',flag:'wx'});}
|
|
414
432
|
catch(error){if(error?.code==='EEXIST') throw new Error('remote session already claimed');throw error;}
|
|
415
433
|
let guardian=null;
|
|
416
434
|
try{
|
|
417
|
-
guardian=spawn(process.execPath,[GUARDIAN_SCRIPT,'--state-root',ctx.root,'--owner-pid',String(process.pid),'--session-nonce',nonce],{
|
|
418
|
-
detached:true,stdio:'ignore',windowsHide:true,shell:false,env:process.env
|
|
435
|
+
guardian=spawn(process.execPath,[GUARDIAN_SCRIPT,'--state-root',ctx.root,'--owner-pid',String(process.pid),'--session-nonce',nonce,'--owner-channel','ipc'],{
|
|
436
|
+
detached:true,stdio:['ignore','ignore','ignore','ipc'],windowsHide:true,shell:false,env:process.env
|
|
419
437
|
});
|
|
420
438
|
if(!Number.isInteger(guardian.pid)||guardian.pid<1) throw new Error('remote session guardian did not start');
|
|
421
439
|
marker.guardian_pid=guardian.pid;
|
|
440
|
+
marker.guardian_identity=queryForegroundIdentity(guardian.pid);
|
|
441
|
+
if(!marker.guardian_identity||marker.guardian_identity.parent_pid!==process.pid)throw new Error('remote guardian parent identity mismatch');
|
|
422
442
|
await writeFile(markerPath,`${JSON.stringify(marker)}\n`,'utf8');
|
|
423
443
|
guardian.unref();
|
|
444
|
+
guardian.channel?.unref();
|
|
424
445
|
return marker;
|
|
425
446
|
}catch(error){
|
|
426
447
|
try{guardian?.kill();}catch{}
|
|
@@ -439,8 +460,15 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
439
460
|
const ctx=await remoteContext();
|
|
440
461
|
if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
|
|
441
462
|
if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
|
|
442
|
-
await
|
|
443
|
-
|
|
463
|
+
const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
|
|
464
|
+
let marker;
|
|
465
|
+
try{
|
|
466
|
+
startupLock.assertHeld();
|
|
467
|
+
await recoverRemoteSession(ctx,{displayMode});
|
|
468
|
+
startupLock.assertHeld();
|
|
469
|
+
marker=await claimRemoteSession(ctx);
|
|
470
|
+
startupLock.assertHeld();
|
|
471
|
+
}finally{await startupLock.release();}
|
|
444
472
|
try{
|
|
445
473
|
await synchronizeForegroundRuntime(ctx,{displayMode});
|
|
446
474
|
}catch(error){
|
|
@@ -475,7 +503,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
475
503
|
process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
|
|
476
504
|
}else{
|
|
477
505
|
const recovery=preflight.status===0&&controllerRuntimeDegraded(preflight.stdout);
|
|
478
|
-
process.stdout.write(recovery?'[RECOVERY] Runtime
|
|
506
|
+
process.stdout.write(recovery?'[RECOVERY] Runtime failed readiness; restarting verified owned components...\n':'[BOOT] Starting bridge/runtime...\n');
|
|
479
507
|
const startCommand=recovery?'restart':'start';
|
|
480
508
|
const controllerLogDir=path.join(ctx.root,'runtime','logs');
|
|
481
509
|
await mkdir(controllerLogDir,{recursive:true});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {lstat,readFile} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {resolveStateRoot} from './state-root.mjs';
|
|
4
|
+
import {validForegroundIdentity,foregroundIdentityMatches,queryForegroundIdentity} from './foreground-process-identity.mjs';
|
|
5
|
+
|
|
6
|
+
export async function inspectDoctorForeground(explicitRoot,{queryIdentity=queryForegroundIdentity}={}){
|
|
7
|
+
const state=resolveStateRoot(explicitRoot),runtime=path.join(state,'runtime');
|
|
8
|
+
const markerPath=path.join(runtime,'remote-session.json');
|
|
9
|
+
const findings=[];
|
|
10
|
+
const add=(id,level,what,evidence,next_action=null)=>findings.push({id,level,what,evidence,next_action});
|
|
11
|
+
const inspect='Retain ownership evidence; inspect the foreground session and use supported remote lifecycle commands. Do not kill or re-arm based on PID alone.';
|
|
12
|
+
let marker;
|
|
13
|
+
try{
|
|
14
|
+
for(const dir of [state,runtime]){const info=await lstat(dir);if(!info.isDirectory()||info.isSymbolicLink())throw new Error('unsafe marker parent');}
|
|
15
|
+
const info=await lstat(markerPath);
|
|
16
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>16384)throw new Error('invalid marker file');
|
|
17
|
+
marker=JSON.parse(await readFile(markerPath,'utf8'));
|
|
18
|
+
}catch(error){
|
|
19
|
+
if(error?.code==='ENOENT')add('foreground_marker','PASS','No foreground session marker in this state root.',{present:false});
|
|
20
|
+
else add('foreground_marker','FAIL','Foreground marker cannot be safely read or parsed.',{check:'regular_bounded_json',error_code:error?.code??'VALIDATION_FAILED'},inspect);
|
|
21
|
+
return {status:findings[0].level,findings};
|
|
22
|
+
}
|
|
23
|
+
if(marker?.schema==='deadbyte.remote-session.v1'&&Number.isSafeInteger(marker.pid)&&marker.pid>0){
|
|
24
|
+
add('foreground_marker','WARN','Legacy foreground marker has no verified owner/guardian binding.',{schema:marker.schema,pid:marker.pid},inspect);
|
|
25
|
+
return {status:'WARN',findings};
|
|
26
|
+
}
|
|
27
|
+
if(marker?.schema!=='deadbyte.remote-session.v2'||!Number.isSafeInteger(marker.owner_pid)||marker.owner_pid<1||!Number.isSafeInteger(marker.guardian_pid)||marker.guardian_pid<0||typeof marker.session_nonce!=='string'||!/^[0-9a-f]{64}$/.test(marker.session_nonce)||(marker.result_session_id!==undefined&&!/^[0-9a-f]{24}$/.test(marker.result_session_id))){
|
|
28
|
+
add('foreground_marker','FAIL','Foreground marker schema, nonce or PID fields are invalid.',{check:'foreground_marker_schema'},inspect);
|
|
29
|
+
return {status:'FAIL',findings};
|
|
30
|
+
}
|
|
31
|
+
add('foreground_marker','PASS','Foreground marker fields are valid.',{schema:marker.schema,owner_pid:marker.owner_pid,guardian_pid:marker.guardian_pid});
|
|
32
|
+
for(const [role,pid,identity]of [['owner',marker.owner_pid,marker.owner_identity],['guardian',marker.guardian_pid,marker.guardian_identity]]){
|
|
33
|
+
const id='foreground_'+role;
|
|
34
|
+
if(identity===undefined){add(id,'WARN',`${role} identity is unverified; PID alone is insufficient.`,{pid,binding_present:false},inspect);continue;}
|
|
35
|
+
if(!validForegroundIdentity(identity)||identity.pid!==pid){add(id,'FAIL',`${role} identity is malformed or disagrees with the marker PID.`,{pid,check:'identity_pid_binding'},inspect);continue;}
|
|
36
|
+
if(role==='guardian'&&identity.parent_pid!==marker.owner_pid){add(id,'FAIL','Guardian identity does not bind to the declared owner.',{pid,check:'guardian_parent_binding'},inspect);continue;}
|
|
37
|
+
try{
|
|
38
|
+
const observed=await queryIdentity(pid);
|
|
39
|
+
if(foregroundIdentityMatches(identity,observed))add(id,'PASS',`${role} process matches the recorded birth/executable/command identity.`,{pid,creation_utc:identity.creation_utc,identity_verified:true});
|
|
40
|
+
else add(id,'WARN',`${role} binding is stale; process is absent or its identity changed.`,{pid,observed_present:observed!==null,identity_verified:false},inspect);
|
|
41
|
+
}catch(error){add(id,'FAIL',`${role} process identity observation failed; liveness is unknown.`,{pid,check:'process_identity',error_code:error?.code??'OBSERVATION_FAILED'},inspect);}
|
|
42
|
+
}
|
|
43
|
+
return {status:findings.some(f=>f.level==='FAIL')?'FAIL':findings.some(f=>f.level==='WARN')?'WARN':'PASS',findings};
|
|
44
|
+
}
|