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.
Files changed (56) hide show
  1. package/MANIFEST.SHA256 +55 -27
  2. package/PROVENANCE.json +114 -87
  3. package/README.txt +12 -12
  4. package/RELEASE.json +214 -187
  5. package/SBOM.cdx.json +10 -25
  6. package/TEST-MATRIX.json +297 -258
  7. package/bridge/package.json +1 -1
  8. package/bridge/src/bridge-health-r59.mjs +4 -3
  9. package/bridge/src/bridge.mjs +71 -14
  10. package/bridge/src/canonical-json-r60.mjs +13 -0
  11. package/bridge/src/reconnect-delay-r60.mjs +31 -0
  12. package/bridge/src/remote-call-claims-r60.mjs +157 -0
  13. package/bridge/src/remote-dispatch-r60.mjs +75 -0
  14. package/controller/deadbyte-controller.ps1 +6 -1
  15. package/controller/deadbyte-process-policy.json +4 -4
  16. package/docs/R59-PROVENANCE.json +87 -0
  17. package/docs/R59-RELEASE.json +187 -0
  18. package/docs/R59-SBOM.cdx.json +68 -0
  19. package/docs/R59-TEST-MATRIX.json +258 -0
  20. package/docs/R59-bridge-package.json +13 -0
  21. package/docs/R59-package-lock.json +184 -0
  22. package/docs/R59-package.json +106 -0
  23. package/docs/R59-version.mjs +6 -0
  24. package/docs/R60-TASK-CONTRACT.md +22 -0
  25. package/package.json +5 -3
  26. package/scripts/full-regression-r60.mjs +32 -0
  27. package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
  28. package/src/capability-snapshot.mjs +6 -2
  29. package/src/deadbyte-cli.mjs +80 -34
  30. package/src/remote-authority.mjs +56 -0
  31. package/src/remote-enforcement-probe-r60.mjs +17 -0
  32. package/src/remote-live-log.mjs +19 -2
  33. package/src/remote-log-decode-r60.mjs +32 -0
  34. package/src/remote-mcp-machine-smoke-client.mjs +19 -1
  35. package/src/remote-session-guardian.mjs +20 -3
  36. package/src/tool-audit.mjs +5 -0
  37. package/src/version.mjs +1 -1
  38. package/test/r48-release-contract.test.mjs +1 -1
  39. package/test/r51-runtime-coherence.test.mjs +2 -2
  40. package/test/r58-cleanup-retention.test.mjs +9 -3
  41. package/test/r58-concurrent-recovery.test.mjs +9 -3
  42. package/test/r58-controller-live-local.test.mjs +22 -3
  43. package/test/r58-stale-owner.test.mjs +2 -2
  44. package/test/r59-release-contract.test.mjs +6 -6
  45. package/test/r60-authority-coherence.test.mjs +36 -0
  46. package/test/r60-authority-enforcement.test.mjs +28 -0
  47. package/test/r60-bridge-dispatch.test.mjs +119 -0
  48. package/test/r60-clock-discipline.test.mjs +15 -0
  49. package/test/r60-live-timeline.test.mjs +29 -0
  50. package/test/r60-reconnect-errors.test.mjs +21 -0
  51. package/test/r60-reconnect-jitter.test.mjs +18 -0
  52. package/test/r60-release-contract.test.mjs +81 -0
  53. package/test/r60-remote-call-recovery.test.mjs +170 -0
  54. package/test/r60-windows-log-decode.test.mjs +25 -0
  55. package/test/remote-live-log-r37.test.mjs +1 -1
  56. 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&&(now-capability.observed_at_ms>maxCapabilityAgeMs||now>capability.valid_until_ms);
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);
@@ -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 reconnectDelays=[250,500,1000,2000,4000];
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
- if(result?.structuredContent?.result?.schema!=='deadbyte.capability-manifest.v3') throw new Error('replacement core capability manifest v3 missing');
298
- return result.structuredContent.result;
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<reconnectDelays.length&&!shuttingDown;attempt++){
315
- await wait(reconnectDelays[attempt]);
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}/${reconnectDelays.length} failed: ${error?.message??String(error)}`);
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 downstream.callTool({ name: tool.name, arguments: args }, budget);
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.annotations?.readOnlyHint!==true&&['RECONCILING','DEGRADED'].includes(bridgeHealth.snapshot().state)) throw new Error(`bridge correlation call=${audit.id} generation=${downstreamGeneration}; mutation outcome unknown; reconcile before retry`,{cause:error});
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 downstream.callTool({ name: tool.name, arguments: callArgs }, budget);
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
- Invoke-CoreGate $Cfg 'remote-mcp-machine-smoke-client.mjs' @($url)
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.36-r59\\controller\\runtime\\process-grant.json",
5
- "state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.36-r59\\controller\\runtime\\process-state",
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.36-r59\\bin\\deadbyte-process-host.exe",
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.36-r59",
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
+ }