deadbyte-mcp 0.15.36 → 0.15.37
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 +55 -27
- package/PROVENANCE.json +114 -87
- package/README.txt +12 -12
- package/RELEASE.json +214 -187
- package/SBOM.cdx.json +10 -25
- package/TEST-MATRIX.json +297 -258
- package/bridge/package.json +1 -1
- package/bridge/src/bridge-health-r59.mjs +4 -3
- package/bridge/src/bridge.mjs +71 -14
- package/bridge/src/canonical-json-r60.mjs +13 -0
- package/bridge/src/reconnect-delay-r60.mjs +31 -0
- package/bridge/src/remote-call-claims-r60.mjs +157 -0
- package/bridge/src/remote-dispatch-r60.mjs +75 -0
- package/controller/deadbyte-controller.ps1 +6 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R59-PROVENANCE.json +87 -0
- package/docs/R59-RELEASE.json +187 -0
- package/docs/R59-SBOM.cdx.json +68 -0
- package/docs/R59-TEST-MATRIX.json +258 -0
- package/docs/R59-bridge-package.json +13 -0
- package/docs/R59-package-lock.json +184 -0
- package/docs/R59-package.json +106 -0
- package/docs/R59-version.mjs +6 -0
- package/docs/R60-TASK-CONTRACT.md +22 -0
- package/package.json +5 -3
- package/scripts/full-regression-r60.mjs +32 -0
- package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
- package/src/capability-snapshot.mjs +6 -2
- package/src/deadbyte-cli.mjs +80 -34
- package/src/remote-authority.mjs +56 -0
- package/src/remote-enforcement-probe-r60.mjs +17 -0
- package/src/remote-live-log.mjs +19 -2
- package/src/remote-log-decode-r60.mjs +32 -0
- package/src/remote-mcp-machine-smoke-client.mjs +19 -1
- package/src/remote-session-guardian.mjs +20 -3
- package/src/tool-audit.mjs +5 -0
- package/src/version.mjs +1 -1
- package/test/r48-release-contract.test.mjs +1 -1
- package/test/r51-runtime-coherence.test.mjs +2 -2
- package/test/r58-cleanup-retention.test.mjs +9 -3
- package/test/r58-concurrent-recovery.test.mjs +9 -3
- package/test/r58-controller-live-local.test.mjs +22 -3
- package/test/r58-stale-owner.test.mjs +2 -2
- package/test/r59-release-contract.test.mjs +6 -6
- package/test/r60-authority-coherence.test.mjs +36 -0
- package/test/r60-authority-enforcement.test.mjs +28 -0
- package/test/r60-bridge-dispatch.test.mjs +119 -0
- package/test/r60-clock-discipline.test.mjs +15 -0
- package/test/r60-live-timeline.test.mjs +29 -0
- package/test/r60-reconnect-errors.test.mjs +21 -0
- package/test/r60-reconnect-jitter.test.mjs +18 -0
- package/test/r60-release-contract.test.mjs +81 -0
- package/test/r60-remote-call-recovery.test.mjs +170 -0
- package/test/r60-windows-log-decode.test.mjs +25 -0
- package/test/remote-live-log-r37.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +200 -43
|
@@ -6,8 +6,8 @@ function bounded(value,max=512){
|
|
|
6
6
|
.replace(/(?:Bearer\s+\S+|token|secret|password|[A-Za-z]:\\[^ ]+)/gi,'<redacted>').slice(0,max);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function createBridgeHealth({clock=Date.now,maxCapabilityAgeMs=15000}={}){
|
|
10
|
-
if(typeof clock!=='function'||!Number.isInteger(maxCapabilityAgeMs)||maxCapabilityAgeMs<5000||maxCapabilityAgeMs>60000) throw new Error('bridge health options invalid');
|
|
9
|
+
export function createBridgeHealth({clock=Date.now,elapsedClock=clock===Date.now?()=>performance.now():clock,maxCapabilityAgeMs=15000}={}){
|
|
10
|
+
if(typeof clock!=='function'||typeof elapsedClock!=='function'||!Number.isInteger(maxCapabilityAgeMs)||maxCapabilityAgeMs<5000||maxCapabilityAgeMs>60000) throw new Error('bridge health options invalid');
|
|
11
11
|
let phase='STARTING';
|
|
12
12
|
let diagnostic='downstream initialization pending';
|
|
13
13
|
let core=null;
|
|
@@ -24,6 +24,7 @@ export function createBridgeHealth({clock=Date.now,maxCapabilityAgeMs=15000}={})
|
|
|
24
24
|
const observed=Date.parse(manifest.observed_at_utc),validUntil=Date.parse(manifest.valid_until_utc);
|
|
25
25
|
if(!Number.isFinite(observed)||!Number.isFinite(validUntil)||validUntil<observed) throw new Error('bridge capability timestamps invalid');
|
|
26
26
|
capability=Object.freeze({snapshot_id:manifest.snapshot_id,state_epoch:manifest.state_epoch,observed_at_ms:observed,
|
|
27
|
+
received_elapsed_ms:Number(elapsedClock()),age_at_receive_ms:Math.max(0,Number(clock())-observed),
|
|
27
28
|
valid_until_ms:validUntil,release_manifest_sha256:HEX64.test(String(manifest.bindings?.release_manifest_sha256))?manifest.bindings.release_manifest_sha256:null,
|
|
28
29
|
generation_manifest_sha256:HEX64.test(String(manifest.bindings?.generation_manifest_sha256))?manifest.bindings.generation_manifest_sha256:null});
|
|
29
30
|
const unavailable=Object.entries(manifest.planes??{}).filter(([,value])=>value?.state==='DEGRADED'||value?.state==='UNAVAILABLE');
|
|
@@ -38,7 +39,7 @@ export function createBridgeHealth({clock=Date.now,maxCapabilityAgeMs=15000}={})
|
|
|
38
39
|
function snapshot(){
|
|
39
40
|
const now=Number(clock());
|
|
40
41
|
let state=phase,reason=diagnostic;
|
|
41
|
-
const stale=capability&&(
|
|
42
|
+
const stale=capability&&(capability.age_at_receive_ms+Math.max(0,Number(elapsedClock())-capability.received_elapsed_ms)>maxCapabilityAgeMs||now>capability.valid_until_ms);
|
|
42
43
|
if(phase==='READY'&&(!core||!capability||stale)){state='DEGRADED';reason=stale?'capability snapshot stale':'downstream attestation incomplete';}
|
|
43
44
|
if(!PHASES.has(state)) throw new Error('bridge health phase invalid');
|
|
44
45
|
const ready=state==='READY'&&Boolean(core)&&Boolean(capability);
|
package/bridge/src/bridge.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
3
|
import { createServer } from 'node:http';
|
|
3
4
|
import { readFileSync } from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -10,8 +11,23 @@ import {preparePublicToolDefinitions} from './public-tool-definitions.mjs';
|
|
|
10
11
|
import {createDeadbyteNodeHandler} from './node-http-adapter.mjs';
|
|
11
12
|
import { bridgeCatalogSha256, bridgeDeadlineForTool, createBridgeGovernor, enforceDesktopBridgeResult, verifyCompactCatalogAttestation } from './r40-governor.mjs';
|
|
12
13
|
import { createBridgeHealth } from './bridge-health-r59.mjs';
|
|
14
|
+
import { reconnectDelayMs, waitForReconnect, RECONNECT_ATTEMPT_BUDGET, classifyReconnectFailure } from './reconnect-delay-r60.mjs';
|
|
15
|
+
import {openRemoteCallClaims} from './remote-call-claims-r60.mjs';
|
|
16
|
+
import {dispatchBridgeCall,classifyRemoteOperation} from './remote-dispatch-r60.mjs';
|
|
17
|
+
import {canonicalJson} from './canonical-json-r60.mjs';
|
|
13
18
|
|
|
14
19
|
const coreRoot = path.resolve(process.env.DEADBYTE_CORE_ROOT ?? 'C:/Users/DEADBYTE/Downloads/deadbyte-mcp-v0.6.0');
|
|
20
|
+
const configuredStateRoot=process.env.DEADBYTE_STATE_ROOT;
|
|
21
|
+
if(configuredStateRoot&&(!path.isAbsolute(configuredStateRoot)||configuredStateRoot.trim()===''))throw new Error('bridge state root must be absolute');
|
|
22
|
+
const stateRoot=path.resolve(configuredStateRoot||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
23
|
+
const remoteClaims=openRemoteCallClaims({root:path.join(stateRoot,'runtime','remote-call-claims')});
|
|
24
|
+
let recoveryCursor=null,recoveredClaims=0;
|
|
25
|
+
do{
|
|
26
|
+
const batch=await remoteClaims.recover({after:recoveryCursor,limit:100});
|
|
27
|
+
recoveredClaims+=batch.records.length;
|
|
28
|
+
if(recoveredClaims>100000)throw new Error('remote claim recovery exceeds bounded startup inventory');
|
|
29
|
+
recoveryCursor=batch.next_cursor;
|
|
30
|
+
}while(recoveryCursor!==null);
|
|
15
31
|
const coreServer = path.join(coreRoot, 'src', 'mcp-server.mjs');
|
|
16
32
|
const workspaceRoot = path.resolve(process.env.DEADBYTE_WORKSPACE_ROOT ?? path.join(coreRoot, 'workspaces'));
|
|
17
33
|
const port = Number(process.env.DEADBYTE_BRIDGE_PORT ?? '8787');
|
|
@@ -285,17 +301,23 @@ capabilityRefreshTimer.unref?.();
|
|
|
285
301
|
let shuttingDown=false;
|
|
286
302
|
let reconnecting=false;
|
|
287
303
|
let downstreamGeneration=1;
|
|
288
|
-
const
|
|
289
|
-
const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
304
|
+
const reconnectAbort=new AbortController();
|
|
290
305
|
|
|
291
306
|
async function attestReplacement(client){
|
|
292
|
-
if(client.getProtocolEra()!=='modern') throw new Error('replacement core protocol is not modern');
|
|
307
|
+
if(client.getProtocolEra()!=='modern') throw Object.assign(new Error('replacement core protocol is not modern'),{kind:'terminal',code:'DEADBYTE_SCHEMA_INVALID'});
|
|
293
308
|
const replacement=(await client.listTools()).tools.map(tool=>tool.name).sort();
|
|
294
|
-
if(JSON.stringify(replacement)!==JSON.stringify(actualNames)) throw new Error('replacement core catalog drift');
|
|
309
|
+
if(JSON.stringify(replacement)!==JSON.stringify(actualNames)) throw Object.assign(new Error('replacement core catalog drift'),{kind:'terminal',code:'DEADBYTE_SCHEMA_INVALID'});
|
|
295
310
|
const result=await client.callTool({name:'capability_manifest',arguments:{}},bridgeDeadlineForTool('capability_manifest'));
|
|
296
311
|
verifyCompactCatalogAttestation(actualNames,result);
|
|
297
|
-
|
|
298
|
-
|
|
312
|
+
const manifest=result?.structuredContent?.result;
|
|
313
|
+
if(manifest?.schema!=='deadbyte.capability-manifest.v3') throw Object.assign(new Error('replacement core capability manifest v3 missing'),{kind:'terminal',code:'DEADBYTE_SCHEMA_INVALID'});
|
|
314
|
+
const status=(await client.callTool({name:'machine_status',arguments:{}},bridgeDeadlineForTool('machine_status')))?.structuredContent;
|
|
315
|
+
if(status?.status!=='ok'||!HEX64.test(String(status.machine_id??''))||!HEX64.test(String(status.online_epoch??'')))
|
|
316
|
+
throw Object.assign(new Error('replacement core execution probe failed'),{kind:'transient',code:'DEADBYTE_EXECUTION_PROBE_FAILED'});
|
|
317
|
+
if(status.release_manifest_sha256!==manifest.bindings?.release_manifest_sha256||
|
|
318
|
+
status.generation_manifest_sha256!==manifest.bindings?.generation_manifest_sha256)
|
|
319
|
+
throw Object.assign(new Error('replacement core execution binding mismatch'),{kind:'terminal',code:'DEADBYTE_GENERATION_MISMATCH'});
|
|
320
|
+
return manifest;
|
|
299
321
|
}
|
|
300
322
|
function bindDownstreamClose(transport){
|
|
301
323
|
const sdkClose=transport.onclose;
|
|
@@ -311,8 +333,8 @@ async function reconcileDownstream(){
|
|
|
311
333
|
if(reconnecting||shuttingDown)return;
|
|
312
334
|
reconnecting=true;
|
|
313
335
|
try{
|
|
314
|
-
for(let attempt=0;attempt<
|
|
315
|
-
await
|
|
336
|
+
for(let attempt=0;attempt<RECONNECT_ATTEMPT_BUDGET&&!shuttingDown;attempt++){
|
|
337
|
+
if(!(await waitForReconnect(reconnectDelayMs(attempt),reconnectAbort.signal))||shuttingDown)return;
|
|
316
338
|
const nextClient=newDownstreamClient(),nextTransport=newDownstreamTransport();
|
|
317
339
|
pipeCoreStderr(nextTransport);
|
|
318
340
|
try{
|
|
@@ -326,7 +348,11 @@ async function reconcileDownstream(){
|
|
|
326
348
|
return;
|
|
327
349
|
}catch(error){
|
|
328
350
|
await nextClient.close().catch(()=>{});
|
|
329
|
-
bridgeHealth.reconciling(`core reconnect ${attempt+1}/${
|
|
351
|
+
bridgeHealth.reconciling(`core reconnect ${attempt+1}/${RECONNECT_ATTEMPT_BUDGET} failed: ${error?.message??String(error)}`);
|
|
352
|
+
if(classifyReconnectFailure(error)==='TERMINAL'){
|
|
353
|
+
bridgeHealth.degraded(`core reconnect terminal failure: ${error?.message??String(error)}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
330
356
|
}
|
|
331
357
|
}
|
|
332
358
|
bridgeHealth.degraded('core restart budget exhausted; bridge quarantined');
|
|
@@ -345,6 +371,36 @@ function plannerTextFromInput(ctx) {
|
|
|
345
371
|
|
|
346
372
|
const publicToolDefinitions=preparePublicToolDefinitions(listed.tools);
|
|
347
373
|
|
|
374
|
+
const remoteDeviceId=createHash('sha256').update(endpointPath,'utf8').digest('hex');
|
|
375
|
+
const ingressDelivery=new AsyncLocalStorage();
|
|
376
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
377
|
+
async function dispatchDownstream(tool,args,ctx,budget){
|
|
378
|
+
if(ctx.mcpReq.signal?.aborted)throw new Error('remote request cancelled before claim');
|
|
379
|
+
const observed=(await downstream.callTool({name:'machine_status',arguments:{}},bridgeDeadlineForTool('machine_status')))?.structuredContent;
|
|
380
|
+
const capability=bridgeHealth.snapshot().capability;
|
|
381
|
+
if(observed?.status!=='ok'||!HEX64.test(String(observed.machine_id??''))||!HEX64.test(String(observed.online_epoch??''))||
|
|
382
|
+
!HEX64.test(String(observed.generation_manifest_sha256??''))||typeof observed.release_id!=='string'||
|
|
383
|
+
observed.generation_manifest_sha256!==capability?.generation_manifest_sha256||
|
|
384
|
+
observed.release_manifest_sha256!==capability?.release_manifest_sha256)
|
|
385
|
+
throw new Error('remote claim machine/release/generation observation invalid');
|
|
386
|
+
const requestId=createHash('sha256').update(canonicalJson(ctx.mcpReq.id),'utf8').digest('hex');
|
|
387
|
+
const metadata=ctx.mcpReq._meta;
|
|
388
|
+
const idempotencyKey=metadata?.idempotency_key;
|
|
389
|
+
if(idempotencyKey!==undefined&&typeof idempotencyKey!=='string')throw new Error('remote idempotency_key invalid');
|
|
390
|
+
const classification=classifyRemoteOperation(tool,args);
|
|
391
|
+
// A stateless HTTP delivery is not a durable logical session. Only an
|
|
392
|
+
// explicit idempotency key may identify a mutation across deliveries.
|
|
393
|
+
const deliveryId=ingressDelivery.getStore()?.delivery_id;
|
|
394
|
+
if(!deliveryId)throw new Error('remote ingress delivery identity missing');
|
|
395
|
+
const binding={device_id:remoteDeviceId,session_id:deliveryId,request_id:requestId,
|
|
396
|
+
...(idempotencyKey===undefined?{}:{idempotency_key:idempotencyKey}),tool:tool.name,arguments:args,
|
|
397
|
+
expires_at_utc:new Date(Date.now()+budget.maxTotalTimeout).toISOString(),
|
|
398
|
+
machine_id:observed.machine_id,online_epoch:observed.online_epoch,release_id:observed.release_id,
|
|
399
|
+
generation_manifest_sha256:observed.generation_manifest_sha256,classification};
|
|
400
|
+
return dispatchBridgeCall({claims:remoteClaims,binding,classification,signal:ctx.mcpReq.signal,
|
|
401
|
+
invoke:()=>downstream.callTool({name:tool.name,arguments:args},budget)});
|
|
402
|
+
}
|
|
403
|
+
|
|
348
404
|
function buildFrontServer() {
|
|
349
405
|
const front = new McpServer(
|
|
350
406
|
{ name: 'deadbyte-chatgpt-bridge', version: '0.2.0' },
|
|
@@ -366,13 +422,13 @@ function buildFrontServer() {
|
|
|
366
422
|
const ticket = await bridgeGovernor.acquire(tool.name,{signal:ctx.mcpReq.signal});
|
|
367
423
|
try {
|
|
368
424
|
const budget = bridgeDeadlineForTool(tool.name);
|
|
369
|
-
const result = await
|
|
425
|
+
const result = await dispatchDownstream(tool,args,ctx,budget);
|
|
370
426
|
enforceDesktopBridgeResult(tool.name, result);
|
|
371
427
|
auditToolFinish(audit, result);
|
|
372
428
|
return result;
|
|
373
429
|
} catch (error) {
|
|
374
430
|
auditToolFail(audit, error);
|
|
375
|
-
if(tool
|
|
431
|
+
if(classifyRemoteOperation(tool,args)!=='read_only'&&['RECONCILING','DEGRADED'].includes(bridgeHealth.snapshot().state)) throw new Error(`bridge correlation call=${audit.id} generation=${downstreamGeneration}; mutation outcome unknown; reconcile before retry`,{cause:error});
|
|
376
432
|
throw error;
|
|
377
433
|
} finally {
|
|
378
434
|
ticket.release();
|
|
@@ -390,7 +446,7 @@ function buildFrontServer() {
|
|
|
390
446
|
let result;
|
|
391
447
|
try {
|
|
392
448
|
const budget = bridgeDeadlineForTool(tool.name);
|
|
393
|
-
result = await
|
|
449
|
+
result = await dispatchDownstream(tool,callArgs,ctx,budget);
|
|
394
450
|
auditToolFinish(audit, result);
|
|
395
451
|
} catch (error) {
|
|
396
452
|
auditToolFail(audit, error);
|
|
@@ -451,7 +507,7 @@ const httpServer = createServer(async (req, res) => {
|
|
|
451
507
|
res.writeHead(413, { 'content-type': 'text/plain; charset=utf-8' });
|
|
452
508
|
return res.end('request too large');
|
|
453
509
|
}
|
|
454
|
-
await nodeMcpHandler(req, res);
|
|
510
|
+
await ingressDelivery.run({delivery_id:randomUUID()},()=>nodeMcpHandler(req, res));
|
|
455
511
|
} catch (error) {
|
|
456
512
|
if (!res.headersSent) res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });
|
|
457
513
|
if (!res.writableEnded) res.end('bridge error');
|
|
@@ -474,6 +530,7 @@ console.log(`BRIDGE_AUTONOMOUS_RUNTIME ${autonomousConfigured ? 'ENABLED' : 'DIS
|
|
|
474
530
|
|
|
475
531
|
async function shutdown() {
|
|
476
532
|
shuttingDown=true;
|
|
533
|
+
reconnectAbort.abort();
|
|
477
534
|
bridgeHealth.stopping();
|
|
478
535
|
clearInterval(capabilityRefreshTimer);
|
|
479
536
|
httpServer.close();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Keep byte-for-byte canonicalization compatible with src/canonical-json.mjs.
|
|
2
|
+
// Bridge dependencies are pinned inside bridge/, so this local copy is tested
|
|
3
|
+
// against the Core primitive rather than reaching outside the Bridge root.
|
|
4
|
+
function canonicalize(value){
|
|
5
|
+
if(Array.isArray(value))return value.map(canonicalize);
|
|
6
|
+
if(value!==null&&typeof value==='object'){
|
|
7
|
+
const out={};
|
|
8
|
+
for(const key of Object.keys(value).sort())out[key]=canonicalize(value[key]);
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function canonicalJson(value){return JSON.stringify(canonicalize(value));}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
|
|
3
|
+
const BASE_DELAYS_MS=Object.freeze([250,500,1000,2000,4000]);
|
|
4
|
+
|
|
5
|
+
export function reconnectDelayMs(attempt,random=Math.random){
|
|
6
|
+
if(!Number.isInteger(attempt)||attempt<0||attempt>=BASE_DELAYS_MS.length) throw new Error('reconnect attempt outside budget');
|
|
7
|
+
const sample=Number(random());
|
|
8
|
+
if(!Number.isFinite(sample)||sample<0||sample>1) throw new Error('reconnect jitter source invalid');
|
|
9
|
+
return Math.round(BASE_DELAYS_MS[attempt]*(0.75+sample*0.5));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RECONNECT_ATTEMPT_BUDGET=BASE_DELAYS_MS.length;
|
|
13
|
+
|
|
14
|
+
export async function waitForReconnect(milliseconds,signal){
|
|
15
|
+
if(signal?.aborted)return false;
|
|
16
|
+
try{await delay(milliseconds,undefined,{signal});return !signal?.aborted;}
|
|
17
|
+
catch(error){if(error?.name==='AbortError')return false;throw error;}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function classifyReconnectFailure(error){
|
|
21
|
+
const code=String(error?.code??'').toUpperCase();
|
|
22
|
+
if(error?.kind==='terminal')return 'TERMINAL';
|
|
23
|
+
if(error?.kind==='transient')return 'TRANSIENT';
|
|
24
|
+
if(new Set(['DEADBYTE_AUTHORITY_REVOKED','DEADBYTE_POLICY_DENIED','DEADBYTE_SCHEMA_INVALID',
|
|
25
|
+
'DEADBYTE_RELEASE_MISMATCH','DEADBYTE_GENERATION_MISMATCH','DEADBYTE_IDEMPOTENCY_CONFLICT',
|
|
26
|
+
'DEADBYTE_CLAIM_CORRUPT','DEADBYTE_CREDENTIAL_EXPIRED']).has(code))return 'TERMINAL';
|
|
27
|
+
const message=String(error?.message??error??'').toLowerCase();
|
|
28
|
+
if(['EACCES','EPERM','ENOENT'].includes(code)||/identity drift|catalog drift|protocol is not modern|attestation invalid|generation mismatch|policy mismatch/.test(message))return 'TERMINAL';
|
|
29
|
+
if(['ECONNRESET','ECONNREFUSED','EPIPE','ETIMEDOUT','EHOSTUNREACH'].includes(code)||/connection reset|transport closed|timed out|temporarily unavailable/.test(message))return 'TRANSIENT';
|
|
30
|
+
return 'TERMINAL';
|
|
31
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { link, lstat, mkdir, open, readFile, readdir, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json-r60.mjs';
|
|
5
|
+
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const ID=/^[A-Za-z0-9._:-]{1,160}$/;
|
|
8
|
+
const sha=value=>createHash('sha256').update(canonicalJson(value),'utf8').digest('hex');
|
|
9
|
+
|
|
10
|
+
async function publishOnce(target,value){
|
|
11
|
+
const temp=`${target}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
12
|
+
const handle=await open(temp,'wx');
|
|
13
|
+
const record={...value,record_sha256:sha(value)};
|
|
14
|
+
try{await handle.writeFile(`${canonicalJson(record)}\n`,'utf8');await handle.sync();}
|
|
15
|
+
finally{await handle.close();}
|
|
16
|
+
try{await link(temp,target);return true;}
|
|
17
|
+
catch(error){if(error?.code==='EEXIST')return false;throw error;}
|
|
18
|
+
finally{await rm(temp,{force:true});}
|
|
19
|
+
}
|
|
20
|
+
async function readExact(file){
|
|
21
|
+
try{
|
|
22
|
+
const info=await lstat(file);
|
|
23
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>2*1024*1024)throw new Error('remote claim record type or size corrupt');
|
|
24
|
+
const record=JSON.parse(await readFile(file,'utf8'));
|
|
25
|
+
if(!record||typeof record!=='object'||Array.isArray(record))throw new Error('remote claim record corrupt');
|
|
26
|
+
const {record_sha256,...body}=record;
|
|
27
|
+
if(!HEX64.test(String(record_sha256??''))||record_sha256!==sha(body))throw new Error('remote claim record hash corrupt');
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
catch(error){if(error?.code==='ENOENT')return null;throw new Error(`remote claim state unreadable: ${String(error?.message||error)}`);}
|
|
31
|
+
}
|
|
32
|
+
function validate(binding,now){
|
|
33
|
+
if(!binding||typeof binding!=='object'||Array.isArray(binding))throw new Error('remote claim binding invalid');
|
|
34
|
+
for(const key of ['device_id','session_id','request_id','tool','release_id'])if(!ID.test(String(binding[key]??'')))throw new Error(`remote claim ${key} invalid`);
|
|
35
|
+
if(binding.idempotency_key!==undefined&&!ID.test(String(binding.idempotency_key)))throw new Error('remote claim idempotency_key invalid');
|
|
36
|
+
if(!HEX64.test(String(binding.machine_id??'')))throw new Error('remote claim machine_id invalid');
|
|
37
|
+
if(!HEX64.test(String(binding.generation_manifest_sha256??'')))throw new Error('remote claim generation binding invalid');
|
|
38
|
+
if(!HEX64.test(String(binding.online_epoch??'')))throw new Error('remote claim online epoch invalid');
|
|
39
|
+
if(!['read_only','mutating','unknown'].includes(binding.classification))throw new Error('remote claim classification invalid');
|
|
40
|
+
if(!binding.arguments||typeof binding.arguments!=='object'||Array.isArray(binding.arguments))throw new Error('remote claim arguments invalid');
|
|
41
|
+
const expires=Date.parse(binding.expires_at_utc);
|
|
42
|
+
if(!Number.isFinite(expires))throw new Error('remote claim deadline invalid');
|
|
43
|
+
if(expires<=now)throw new Error('remote request expired');
|
|
44
|
+
return Object.freeze({schema:'deadbyte.remote-call-claim.v1',device_id:binding.device_id,session_id:binding.session_id,
|
|
45
|
+
idempotency_key:binding.idempotency_key??null,
|
|
46
|
+
request_id:String(binding.request_id),tool:binding.tool,payload_sha256:sha(binding.arguments),
|
|
47
|
+
received_at_utc:new Date(now).toISOString(),expires_at_utc:new Date(expires).toISOString(),
|
|
48
|
+
release_id:binding.release_id,generation_manifest_sha256:binding.generation_manifest_sha256,
|
|
49
|
+
machine_id:binding.machine_id,online_epoch:binding.online_epoch,classification:binding.classification});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function openRemoteCallClaims({root,clock=Date.now}={}){
|
|
53
|
+
if(typeof root!=='string'||!path.isAbsolute(root)||typeof clock!=='function')throw new Error('remote claim store options invalid');
|
|
54
|
+
const directory=path.resolve(root);
|
|
55
|
+
function files(id){
|
|
56
|
+
if(!HEX64.test(String(id)))throw new Error('remote claim identity invalid');
|
|
57
|
+
const base=path.join(directory,id.slice(0,2),id);
|
|
58
|
+
return {base,claim:`${base}.claim.json`,claimed:`${base}.claimed.json`,dispatch:`${base}.dispatch.json`,interrupted:`${base}.interrupted.json`,terminal:`${base}.terminal.json`};
|
|
59
|
+
}
|
|
60
|
+
async function state(claimId){
|
|
61
|
+
const f=files(claimId);
|
|
62
|
+
const claim=await readExact(f.claim);
|
|
63
|
+
if(!claim||claim.schema!=='deadbyte.remote-call-claim.v1')throw new Error('remote claim missing or invalid');
|
|
64
|
+
const terminal=await readExact(f.terminal);
|
|
65
|
+
if(terminal){
|
|
66
|
+
if(terminal.schema!=='deadbyte.remote-call-terminal.v1'||terminal.claim_id!==claimId||
|
|
67
|
+
!HEX64.test(String(terminal.result_sha256??''))||terminal.result_sha256!==sha(terminal.result))
|
|
68
|
+
throw new Error('remote claim terminal hash corrupt');
|
|
69
|
+
return Object.freeze({claim_id:claimId,owner:false,state:'TERMINAL',side_effect_state:claim.classification==='read_only'?'none':'unknown',result:terminal.result});
|
|
70
|
+
}
|
|
71
|
+
const interrupted=await readExact(f.interrupted);
|
|
72
|
+
if(interrupted){
|
|
73
|
+
if(interrupted.schema!=='deadbyte.remote-call-interrupted.v1'||interrupted.claim_id!==claimId)throw new Error('remote claim interruption corrupt');
|
|
74
|
+
return Object.freeze({claim_id:claimId,owner:false,state:'INTERRUPTED',side_effect_state:claim.classification==='read_only'?'none':'unknown'});
|
|
75
|
+
}
|
|
76
|
+
const dispatched=await readExact(f.dispatch);
|
|
77
|
+
if(dispatched)return Object.freeze({claim_id:claimId,owner:false,state:'EXECUTING',side_effect_state:claim.classification==='read_only'?'none':'may_have_started'});
|
|
78
|
+
const claimed=await readExact(f.claimed);
|
|
79
|
+
return Object.freeze({claim_id:claimId,owner:false,state:claimed?'CLAIMED':'RECEIVED',side_effect_state:'not_started'});
|
|
80
|
+
}
|
|
81
|
+
async function claim(binding){
|
|
82
|
+
const record=validate(binding,Number(clock()));
|
|
83
|
+
const claimId=record.idempotency_key===null
|
|
84
|
+
?sha({device_id:record.device_id,session_id:record.session_id,request_id:record.request_id})
|
|
85
|
+
:sha({device_id:record.device_id,idempotency_key:record.idempotency_key});
|
|
86
|
+
const f=files(claimId);
|
|
87
|
+
await mkdir(path.dirname(f.base),{recursive:true});
|
|
88
|
+
if(await publishOnce(f.claim,record)){
|
|
89
|
+
const owner=await publishOnce(f.claimed,{schema:'deadbyte.remote-call-claimed.v1',claim_id:claimId,execution_state:'CLAIMED',side_effect_state:'not_started'});
|
|
90
|
+
return owner?Object.freeze({claim_id:claimId,owner:true,state:'CLAIMED',side_effect_state:'not_started'}):state(claimId);
|
|
91
|
+
}
|
|
92
|
+
const prior=await readExact(f.claim);
|
|
93
|
+
if(!prior||prior.schema!==record.schema)throw new Error('remote claim identity corrupt');
|
|
94
|
+
if(Date.parse(prior.expires_at_utc)<=Number(clock()))throw new Error('remote request expired');
|
|
95
|
+
if(prior.tool!==record.tool||prior.payload_sha256!==record.payload_sha256)throw new Error(record.idempotency_key===null?'request_id_conflict':'idempotency_key_conflict');
|
|
96
|
+
if(prior.device_id!==record.device_id||prior.idempotency_key!==record.idempotency_key||
|
|
97
|
+
(record.idempotency_key===null&&(prior.session_id!==record.session_id||prior.request_id!==record.request_id)))throw new Error('remote claim identity drift');
|
|
98
|
+
if(prior.release_id!==record.release_id||prior.generation_manifest_sha256!==record.generation_manifest_sha256)throw new Error('remote claim release identity drift');
|
|
99
|
+
if(prior.machine_id!==record.machine_id||prior.online_epoch!==record.online_epoch||prior.classification!==record.classification)throw new Error('remote claim machine/epoch/classification drift');
|
|
100
|
+
const current=await state(claimId);
|
|
101
|
+
if(current.state==='RECEIVED'&&await publishOnce(f.claimed,{schema:'deadbyte.remote-call-claimed.v1',claim_id:claimId,execution_state:'CLAIMED',side_effect_state:'not_started'}))
|
|
102
|
+
return Object.freeze({claim_id:claimId,owner:true,state:'CLAIMED',side_effect_state:'not_started'});
|
|
103
|
+
return state(claimId);
|
|
104
|
+
}
|
|
105
|
+
async function dispatch(claimId){
|
|
106
|
+
const f=files(claimId);
|
|
107
|
+
if(!(await readExact(f.claimed)))throw new Error('remote claim not claimed before dispatch');
|
|
108
|
+
if(!(await publishOnce(f.dispatch,{schema:'deadbyte.remote-call-dispatch.v1',claim_id:claimId,at_utc:new Date(Number(clock())).toISOString(),execution_state:'EXECUTING',side_effect_state:'may_have_started'})))
|
|
109
|
+
throw new Error('remote claim already dispatched; outcome unknown');
|
|
110
|
+
return Object.freeze({claim_id:claimId,state:'EXECUTING',side_effect_state:'may_have_started'});
|
|
111
|
+
}
|
|
112
|
+
async function terminal(claimId,result){
|
|
113
|
+
const f=files(claimId);
|
|
114
|
+
if(!(await readExact(f.dispatch)))throw new Error('remote claim not dispatched');
|
|
115
|
+
const bytes=Buffer.byteLength(canonicalJson(result),'utf8');
|
|
116
|
+
if(bytes>1048576)throw new Error('remote claim terminal result exceeds bound');
|
|
117
|
+
if(!(await publishOnce(f.terminal,{schema:'deadbyte.remote-call-terminal.v1',claim_id:claimId,
|
|
118
|
+
at_utc:new Date(Number(clock())).toISOString(),result_sha256:sha(result),result})))throw new Error('remote claim already terminal');
|
|
119
|
+
return state(claimId);
|
|
120
|
+
}
|
|
121
|
+
async function recover({after=null,limit=100}={}){
|
|
122
|
+
if(after!==null&&!HEX64.test(String(after)))throw new Error('remote recovery cursor invalid');
|
|
123
|
+
if(!Number.isInteger(limit)||limit<1||limit>100)throw new Error('remote recovery batch limit invalid');
|
|
124
|
+
const records=[];let more=false;
|
|
125
|
+
for(let shard=0;shard<256;shard++){
|
|
126
|
+
const prefix=shard.toString(16).padStart(2,'0');
|
|
127
|
+
if(after&&prefix<after.slice(0,2))continue;
|
|
128
|
+
const folder=path.join(directory,prefix);
|
|
129
|
+
let names;
|
|
130
|
+
try{
|
|
131
|
+
const info=await lstat(folder);
|
|
132
|
+
if(!info.isDirectory()||info.isSymbolicLink())throw new Error('remote recovery shard corrupt');
|
|
133
|
+
names=await readdir(folder);
|
|
134
|
+
}catch(error){if(error?.code==='ENOENT')continue;throw error;}
|
|
135
|
+
if(names.length>4096)throw new Error('remote recovery shard exceeds bounded inventory');
|
|
136
|
+
for(const name of names.filter(item=>/^[0-9a-f]{64}\.claim\.json$/.test(item)).sort()){
|
|
137
|
+
const id=name.slice(0,64);
|
|
138
|
+
if(after&&id<=after)continue;
|
|
139
|
+
if(records.length>=limit){more=true;break;}
|
|
140
|
+
const claimRecord=await readExact(files(id).claim);
|
|
141
|
+
if(!claimRecord||claimRecord.schema!=='deadbyte.remote-call-claim.v1')throw new Error('remote recovery claim corrupt');
|
|
142
|
+
if(Date.parse(claimRecord.expires_at_utc)<=Number(clock()))continue;
|
|
143
|
+
let current=await state(id);
|
|
144
|
+
if(current.state==='TERMINAL')continue;
|
|
145
|
+
if(current.state==='EXECUTING'){
|
|
146
|
+
await publishOnce(files(id).interrupted,{schema:'deadbyte.remote-call-interrupted.v1',claim_id:id,
|
|
147
|
+
at_utc:new Date(Number(clock())).toISOString(),execution_state:'INTERRUPTED',side_effect_state:current.side_effect_state==='none'?'none':'unknown'});
|
|
148
|
+
current=await state(id);
|
|
149
|
+
}
|
|
150
|
+
records.push(current);
|
|
151
|
+
}
|
|
152
|
+
if(more)break;
|
|
153
|
+
}
|
|
154
|
+
return Object.freeze({records,next_cursor:more?records.at(-1)?.claim_id??after:null});
|
|
155
|
+
}
|
|
156
|
+
return Object.freeze({claim,dispatch,terminal,state,recover});
|
|
157
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// This function is the one downstream-dispatch boundary used by the Bridge.
|
|
2
|
+
// A published dispatch marker precedes every call that might mutate Core.
|
|
3
|
+
const READ_ONLY_TOOLS=new Set([
|
|
4
|
+
'agent_task_events','agent_task_status','agent_task_verify',
|
|
5
|
+
'autonomous_goal_events','autonomous_goal_status','autonomous_goal_verify','autonomous_session_status',
|
|
6
|
+
'capability_manifest','coding_file_read','coding_files_read','coding_receipt_verify','coding_search',
|
|
7
|
+
'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_outline','coding_symbol_references',
|
|
8
|
+
'coding_task_events','coding_task_status','coding_task_verify','coding_tree','evidence_verify','file_read',
|
|
9
|
+
'host_file_read','machine_fs_list','machine_fs_read','machine_fs_stat','machine_list','machine_ping',
|
|
10
|
+
'machine_process_list','machine_search_page','machine_search_status','machine_status','machine_system_status',
|
|
11
|
+
'native_diff_preview','native_diff_verify','process_session_list','process_session_read','process_session_status'
|
|
12
|
+
]);
|
|
13
|
+
const MUTATING_TOOLS=new Set([
|
|
14
|
+
'agent_task_approve','agent_task_cancel','agent_task_run','agent_task_submit',
|
|
15
|
+
'autonomous_goal_approve','autonomous_goal_cancel','autonomous_goal_run','autonomous_goal_submit','autonomous_release_gate',
|
|
16
|
+
'coding_benchmark_run','coding_command_run','coding_file_write','coding_symbol_replace','coding_task_approve',
|
|
17
|
+
'coding_task_cancel','coding_task_run','coding_task_submit','coding_text_replace','contained_transform',
|
|
18
|
+
'host_exec','host_file_write','machine_fs_delete','machine_fs_edit_exact','machine_fs_mkdir','machine_fs_move',
|
|
19
|
+
'machine_fs_write','machine_revoke','machine_search_cancel','machine_search_start','native_diff_apply',
|
|
20
|
+
'process_session_action','process_session_start','process_session_terminate','proof_execute','workspace_open'
|
|
21
|
+
]);
|
|
22
|
+
const COMPACT_READ_ONLY={
|
|
23
|
+
machine_fs:new Set(['stat','list','read']),
|
|
24
|
+
process_session:new Set(['read','status','list']),
|
|
25
|
+
machine_observe:new Set(['machine_list','machine_status','machine_ping','search_page','search_status',
|
|
26
|
+
'process_list','system_status','hardware_query','registry_query','service_list','driver_list',
|
|
27
|
+
'socket_list','volume_list','usb_list','wsl_list']),
|
|
28
|
+
coding_inspect:new Set(['stat','search','files_read','symbol_outline','symbol_definition','symbol_references',
|
|
29
|
+
'symbol_diagnostics','symbol_implementations','symbol_rename_preview']),
|
|
30
|
+
desktop_control:new Set(['observe','status'])
|
|
31
|
+
};
|
|
32
|
+
const COMPACT_MUTATING={
|
|
33
|
+
machine_fs:new Set(['write','edit_exact','mkdir','move','delete']),
|
|
34
|
+
process_session:new Set(['start','write','action','terminate']),
|
|
35
|
+
machine_observe:new Set(['search_start','search_cancel']),
|
|
36
|
+
desktop_control:new Set(['target','action'])
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function classifyRemoteOperation(tool,args){
|
|
40
|
+
const name=tool?.name;
|
|
41
|
+
if(typeof name!=='string')return 'unknown';
|
|
42
|
+
if(Object.hasOwn(COMPACT_READ_ONLY,name)||Object.hasOwn(COMPACT_MUTATING,name)){
|
|
43
|
+
if(COMPACT_READ_ONLY[name]?.has(args?.op))return 'read_only';
|
|
44
|
+
if(COMPACT_MUTATING[name]?.has(args?.op))return 'mutating';
|
|
45
|
+
return 'unknown';
|
|
46
|
+
}
|
|
47
|
+
if(MUTATING_TOOLS.has(name))return 'mutating';
|
|
48
|
+
if(READ_ONLY_TOOLS.has(name))return 'read_only';
|
|
49
|
+
return 'unknown';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function dispatchBridgeCall({claims,binding,classification,signal,invoke}){
|
|
53
|
+
if(!claims||typeof claims.claim!=='function'||typeof invoke!=='function')throw new Error('remote dispatch dependencies invalid');
|
|
54
|
+
if(!['read_only','mutating','unknown'].includes(classification))throw new Error('remote dispatch classification invalid');
|
|
55
|
+
const claim=await claims.claim(binding);
|
|
56
|
+
if(claim.state==='TERMINAL')return claim.result;
|
|
57
|
+
const recoveredRead=claim.state==='INTERRUPTED'&&classification==='read_only'&&claim.side_effect_state==='none';
|
|
58
|
+
if((claim.state==='EXECUTING'||claim.state==='INTERRUPTED')&&!recoveredRead)throw new Error(`remote claim ${claim.claim_id} outcome unknown; reconcile before retry`);
|
|
59
|
+
if(claim.state!=='CLAIMED'&&!recoveredRead)throw new Error(`remote claim ${claim.claim_id} is ${claim.state}; no dispatch`);
|
|
60
|
+
if(signal?.aborted)throw new Error(`remote claim ${claim.claim_id} cancelled before dispatch`);
|
|
61
|
+
if(!recoveredRead){
|
|
62
|
+
try{await claims.dispatch(claim.claim_id);}
|
|
63
|
+
catch(error){throw new Error(`remote claim ${claim.claim_id} already in flight; no replay`,{cause:error});}
|
|
64
|
+
}
|
|
65
|
+
let result;
|
|
66
|
+
try{result=await invoke();}
|
|
67
|
+
catch(error){throw new Error(`remote claim ${claim.claim_id} outcome unknown after dispatch; no replay`,{cause:error});}
|
|
68
|
+
try{await claims.terminal(claim.claim_id,result);}
|
|
69
|
+
catch(error){
|
|
70
|
+
const existing=await claims.state(claim.claim_id);
|
|
71
|
+
if(recoveredRead&&existing.state==='TERMINAL')return existing.result;
|
|
72
|
+
throw new Error(`remote claim ${claim.claim_id} terminal publication failed; outcome unknown`,{cause:error});
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -1432,7 +1432,12 @@ function Invoke-MachineSmoke($Cfg) {
|
|
|
1432
1432
|
$url = Get-CurrentUrl $Cfg
|
|
1433
1433
|
if (-not $url) { Fail 'public MCP URL unavailable' }
|
|
1434
1434
|
Wait-PublicDnsReady $Cfg $url
|
|
1435
|
-
|
|
1435
|
+
$probeArgs = @($url)
|
|
1436
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$env:DEADBYTE_REMOTE_PROBE_MODE)) {
|
|
1437
|
+
if ([string]$env:DEADBYTE_REMOTE_PROBE_MODE -notin @('default','desktop','host','full','revoked')) { Fail 'invalid remote enforcement probe mode' }
|
|
1438
|
+
$probeArgs += [string]$env:DEADBYTE_REMOTE_PROBE_MODE
|
|
1439
|
+
}
|
|
1440
|
+
Invoke-CoreGate $Cfg 'remote-mcp-machine-smoke-client.mjs' $probeArgs
|
|
1436
1441
|
Write-Host 'MACHINE IDENTITY LIVE GATE PASS: public status/list + SPKI identity + signed Ed25519 ping verified.'
|
|
1437
1442
|
}
|
|
1438
1443
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.process-policy.v1",
|
|
3
3
|
"enabled": true,
|
|
4
|
-
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
5
|
-
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
4
|
+
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.37-r60\\controller\\runtime\\process-grant.json",
|
|
5
|
+
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.37-r60\\controller\\runtime\\process-state",
|
|
6
6
|
"native_host": {
|
|
7
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
7
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.37-r60\\bin\\deadbyte-process-host.exe",
|
|
8
8
|
"sha256": "daf9cfb371bee88cafbf5597eaa507fec504dbe0b0ddd40d0c3c2e02817f49e4"
|
|
9
9
|
},
|
|
10
10
|
"roots": {
|
|
11
11
|
"core": {
|
|
12
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
12
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.37-r60",
|
|
13
13
|
"deny_paths": [
|
|
14
14
|
"node_modules",
|
|
15
15
|
"controller/runtime",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "deadbyte.provenance.v1",
|
|
3
|
+
"version": "0.15.36",
|
|
4
|
+
"source_model": "exact published and production-accepted 0.15.35 artifact -\u003e isolated R59 candidate -\u003e optional-plane degraded availability -\u003e runtime capability snapshot v3 -\u003e bounded Bridge Core reconciliation with no mutation replay -\u003e controller root preflight -\u003e fresh full regression/package/public frontdoor gates -\u003e operator-authorized publication with four-hour soak and clean-machine explicitly not completed -\u003e production activation/rollback/restore",
|
|
5
|
+
"signing": "ed25519",
|
|
6
|
+
"parent_tarball_sha256": "8259e30946d28dd138c1e2ce2f483fe1370d1658544f2adbfebafa4db8875bba",
|
|
7
|
+
"parent_activation_status": "PRODUCTION_ACCEPTED_WITH_SIGNED_ROLLBACK_AND_RESTORE",
|
|
8
|
+
"parent_rollback_receipt_sha256": "2f8aa5784fe6629c25af776b247b22b8e84d1a51c3420339ddd7a7445cf6ff9b",
|
|
9
|
+
"runtime_predecessor": {
|
|
10
|
+
"release_id": "v0.15.35-1b5715b276facbc0",
|
|
11
|
+
"manifest_sha256": "1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4",
|
|
12
|
+
"generation_manifest_sha256": "047e73b62d7b7819b84201a221a09b7e647cacf21f82101ebed4fbd7e23fc430"
|
|
13
|
+
},
|
|
14
|
+
"source_predecessor_tarball_sha256": "8259e30946d28dd138c1e2ce2f483fe1370d1658544f2adbfebafa4db8875bba",
|
|
15
|
+
"source_predecessor_manifest_file_sha256": "1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4",
|
|
16
|
+
"reproducibility": "0.15.35 remains immutable production authority while R59 is developed in D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.36-r59. Publication is operator-authorized with four-hour soak and clean-machine truth preserved as NOT_COMPLETED; no stable/final claim is permitted.",
|
|
17
|
+
"public_surface_evidence": {
|
|
18
|
+
"status": "PASS",
|
|
19
|
+
"baseline_release": "v0.15.35-1b5715b276facbc0",
|
|
20
|
+
"tool_count": 43,
|
|
21
|
+
"unchanged": 43,
|
|
22
|
+
"added": 0,
|
|
23
|
+
"removed": 0,
|
|
24
|
+
"schema_changed": 0,
|
|
25
|
+
"surface_sha256": "8e98978b720633acb0c7d4d65c36a945243c47f6dbccfa64ab877329f2956fe3",
|
|
26
|
+
"evidence_receipt_sha256": "2d3adcc33d010c4977974971222e6b13d613ff268097fff0eec0113e4f82f281"
|
|
27
|
+
},
|
|
28
|
+
"fresh_full_regression": {
|
|
29
|
+
"status": "PENDING_R59_AFTER_RELEASE_IDENTITY_CUT",
|
|
30
|
+
"proof_path": "proof/r59-full-regression-current.json"
|
|
31
|
+
},
|
|
32
|
+
"inherited_r58_consolidation": {
|
|
33
|
+
"frontend_status_vocabulary": true,
|
|
34
|
+
"read_only_structured_status": true,
|
|
35
|
+
"doctor_read_only_installation_runtime_transaction_checks": true,
|
|
36
|
+
"stale_foreground_identity_hardening": true,
|
|
37
|
+
"bridge_bundled_inside_release": true,
|
|
38
|
+
"bridge_recursive_local_dependency_binding": true,
|
|
39
|
+
"bridge_dynamic_relative_import_fail_closed": true,
|
|
40
|
+
"bridge_path_escape_fail_closed": true,
|
|
41
|
+
"isolated_controller_bridge_core_lifecycle": "PASS",
|
|
42
|
+
"performance_acceptance": "PASS_BOUNDED_PREPUBLISH",
|
|
43
|
+
"fresh_full_regression": "PASS",
|
|
44
|
+
"package_acceptance": "PENDING",
|
|
45
|
+
"registry_round_trip": "PENDING",
|
|
46
|
+
"production_activation_rollback_restore": "PENDING",
|
|
47
|
+
"stable_final_claim": false,
|
|
48
|
+
"cross_plane_acceptance": "PASS",
|
|
49
|
+
"public_surface_compatibility": "PASS_43_TOOLS_NO_SCHEMA_DRIFT"
|
|
50
|
+
},
|
|
51
|
+
"r59_runtime_hardening": {
|
|
52
|
+
"optional_plane_degradation": true,
|
|
53
|
+
"stable_tool_registration_on_optional_failure": true,
|
|
54
|
+
"capability_manifest_v3": true,
|
|
55
|
+
"live_overlay_authority_ceiling": true,
|
|
56
|
+
"bridge_bounded_reconciliation": true,
|
|
57
|
+
"bridge_health_requires_fresh_child_catalog_capability_attestation": true,
|
|
58
|
+
"mutation_unknown_outcome_no_replay": true,
|
|
59
|
+
"read_only_retry_only_before_dispatch": true,
|
|
60
|
+
"cancellation_aware_governor": true,
|
|
61
|
+
"controller_policy_root_preflight": true,
|
|
62
|
+
"public_surface_compatibility": "PASS_43_TOOLS_NO_SCHEMA_DRIFT",
|
|
63
|
+
"frontdoor_signed_ping": "PASS",
|
|
64
|
+
"four_hour_soak": "NOT_COMPLETED",
|
|
65
|
+
"clean_machine": "NOT_COMPLETED",
|
|
66
|
+
"stable_final_claim": false
|
|
67
|
+
},
|
|
68
|
+
"inherited_parent_performance_evidence": {
|
|
69
|
+
"summary_path": "D:\\Projects\\deadbyte-mcp\\testing\\0.15.35\\performance-summary.json",
|
|
70
|
+
"summary_sha256": "4586d8462ce1ae643d255315d4b4febfdd3f731dd8728e4b37b5636b622b756c",
|
|
71
|
+
"bridge_candidate_first_p95_reduction_pct": 63.632445348471286,
|
|
72
|
+
"bridge_baseline_first_p95_reduction_pct": 43.06594367803213,
|
|
73
|
+
"stdio_aggregate_p95_delta_pct": 1.984392678457768,
|
|
74
|
+
"limitations_preserved": true
|
|
75
|
+
},
|
|
76
|
+
"builder": "deadbyte-r59-resilient-runtime",
|
|
77
|
+
"parent_release_id": "v0.15.35-1b5715b276facbc0",
|
|
78
|
+
"parent_manifest_sha256": "1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4",
|
|
79
|
+
"parent_generation_manifest_sha256": "047e73b62d7b7819b84201a221a09b7e647cacf21f82101ebed4fbd7e23fc430",
|
|
80
|
+
"source_predecessor_version": "0.15.35",
|
|
81
|
+
"operator_override": {
|
|
82
|
+
"four_hour_soak": "WAIVED_BY_OPERATOR",
|
|
83
|
+
"clean_machine": "WAIVED_BY_OPERATOR",
|
|
84
|
+
"reason": "Operator explicitly requested immediate deployment without further testing.",
|
|
85
|
+
"stable_final_claim": false
|
|
86
|
+
}
|
|
87
|
+
}
|