deadbyte-mcp 0.15.34 → 0.15.36

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 (101) hide show
  1. package/MANIFEST.SHA256 +100 -38
  2. package/PROVENANCE.json +87 -41
  3. package/README.txt +72 -14
  4. package/RELEASE.json +187 -150
  5. package/SBOM.cdx.json +25 -3
  6. package/TEST-MATRIX.json +258 -151
  7. package/bridge/package.json +13 -0
  8. package/bridge/src/bridge-health-r59.mjs +52 -0
  9. package/bridge/src/bridge.mjs +487 -0
  10. package/bridge/src/node-http-adapter.mjs +56 -0
  11. package/bridge/src/public-tool-definitions.mjs +14 -0
  12. package/bridge/src/r40-governor.mjs +206 -0
  13. package/controller/deadbyte-controller.ps1 +152 -18
  14. package/controller/deadbyte-process-policy.json +4 -4
  15. package/docs/R59-TASK-CONTRACT.md +26 -0
  16. package/package.json +7 -3
  17. package/proof/r59-full-regression-current.json +13 -0
  18. package/proof/r59-full-regression-current.stderr.log +0 -0
  19. package/proof/r59-full-regression-current.stdout.log +1268 -0
  20. package/scripts/bridge-benchmark-r58.mjs +67 -0
  21. package/scripts/deploy-r48.mjs +2 -3
  22. package/scripts/gate-windows-r58.ps1 +33 -0
  23. package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
  24. package/scripts/owner-probe-r58.mjs +31 -0
  25. package/scripts/performance-r58.mjs +57 -0
  26. package/scripts/public-baseline-r58.mjs +72 -0
  27. package/scripts/public-surface-r58.mjs +84 -0
  28. package/scripts/r58-cross-plane.ps1 +6 -0
  29. package/scripts/r58-focused.ps1 +6 -0
  30. package/scripts/r59-full-regression-capture.mjs +35 -0
  31. package/scripts/windows-gate-evidence-r58.mjs +93 -0
  32. package/src/capability-resolver.mjs +25 -7
  33. package/src/capability-snapshot.mjs +52 -0
  34. package/src/compact-gateway-mcp-tools.mjs +10 -11
  35. package/src/deadbyte-cli.mjs +40 -12
  36. package/src/doctor-foreground-r58.mjs +44 -0
  37. package/src/doctor-installation-r58.mjs +63 -0
  38. package/src/doctor-r45.mjs +21 -4
  39. package/src/doctor-runtime-r58.mjs +82 -0
  40. package/src/doctor-transactions-r58.mjs +40 -0
  41. package/src/foreground-process-identity.mjs +27 -0
  42. package/src/foreground-session-lock.mjs +20 -0
  43. package/src/foreground-session-lock.ps1 +15 -0
  44. package/src/mcp-server.mjs +179 -118
  45. package/src/r40-compact-disarmed-probe.mjs +1 -1
  46. package/src/r44-compact-disarmed-probe.mjs +1 -1
  47. package/src/r45-compact-disarmed-probe.mjs +1 -1
  48. package/src/release-generation.mjs +1 -0
  49. package/src/release-slot.mjs +2 -1
  50. package/src/remote-mcp-r40-parity-client.mjs +1 -1
  51. package/src/remote-mcp-r44-parity-client.mjs +1 -1
  52. package/src/remote-mcp-r45-parity-client.mjs +1 -1
  53. package/src/remote-mcp-smoke-client.mjs +1 -1
  54. package/src/remote-session-guardian.mjs +8 -1
  55. package/src/runtime-plane-registry.mjs +74 -0
  56. package/src/runtime-update.mjs +85 -2
  57. package/src/version.mjs +1 -1
  58. package/test/bridge-runtime-r59.test.mjs +60 -0
  59. package/test/capability-resolver.test.mjs +16 -0
  60. package/test/capability-snapshot-r59.test.mjs +39 -0
  61. package/test/compact-gateway-r36.test.mjs +22 -2
  62. package/test/compact-max-catalog-r44.test.mjs +1 -1
  63. package/test/compact-max-catalog-r45.test.mjs +1 -1
  64. package/test/contained.test.mjs +2 -1
  65. package/test/controller-policy-preflight-r59.test.mjs +39 -0
  66. package/test/controller-r211.test.mjs +4 -1
  67. package/test/controller.test.mjs +2 -1
  68. package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
  69. package/test/helpers/r58-doctor-fixture.mjs +26 -0
  70. package/test/observation-integration.test.mjs +2 -3
  71. package/test/optional-plane-degraded-r59.test.mjs +44 -0
  72. package/test/r49-windows-gate.test.mjs +2 -2
  73. package/test/r57-release-contract.test.mjs +5 -3
  74. package/test/r58-bridge-dependency-binding.test.mjs +62 -0
  75. package/test/r58-bundled-bridge.test.mjs +24 -0
  76. package/test/r58-cleanup-retention.test.mjs +45 -0
  77. package/test/r58-completed-update-receipt.test.mjs +76 -0
  78. package/test/r58-concurrent-recovery.test.mjs +54 -0
  79. package/test/r58-controller-core-observation.test.mjs +36 -0
  80. package/test/r58-controller-json.test.mjs +48 -0
  81. package/test/r58-controller-live-local.test.mjs +98 -0
  82. package/test/r58-controller-readonly.test.mjs +23 -0
  83. package/test/r58-controller-state-coherence.test.mjs +45 -0
  84. package/test/r58-doctor-cli.test.mjs +20 -0
  85. package/test/r58-doctor-foreground.test.mjs +50 -0
  86. package/test/r58-doctor-installation.test.mjs +55 -0
  87. package/test/r58-doctor-pointer.test.mjs +41 -0
  88. package/test/r58-doctor-runtime.test.mjs +45 -0
  89. package/test/r58-doctor-transactions.test.mjs +21 -0
  90. package/test/r58-guardian-channel.test.mjs +26 -0
  91. package/test/r58-launcher-shell.test.mjs +28 -0
  92. package/test/r58-owner-identity.test.mjs +20 -0
  93. package/test/r58-release-contract.test.mjs +50 -0
  94. package/test/r58-session-lock.test.mjs +26 -0
  95. package/test/r58-stale-owner.test.mjs +71 -0
  96. package/test/r58-windows-gate.test.mjs +32 -0
  97. package/test/r59-release-contract.test.mjs +61 -0
  98. package/test/r59-tool-surface-compat.test.mjs +90 -0
  99. package/test/release-generation.test.mjs +4 -1
  100. package/test/remote-session-cli.test.mjs +55 -2
  101. package/test/runtime-plane-registry-r59.test.mjs +40 -0
@@ -127,7 +127,7 @@ try {
127
127
  if (compactProfile) {
128
128
  const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
129
129
  const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
130
- const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && (exactR40Compact || exactR44Compact);
130
+ const manifestV2 = ['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema) && (exactR40Compact || exactR44Compact);
131
131
  if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
132
132
  !Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
133
133
  throw new Error('compact capability manifest malformed');
@@ -8,6 +8,10 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
9
 
10
10
  const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
11
+ // This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
12
+ // poll. A reused PID cannot keep a new foreground guardian alive.
13
+ let ownerDisconnected=false;
14
+ process.once('disconnect',()=>{ownerDisconnected=true;});
11
15
 
12
16
  function parseArgs(argv){
13
17
  const out={};
@@ -51,7 +55,10 @@ async function main(){
51
55
  const root=resolveStateRoot(args['state-root']);
52
56
  const ownerPid=Number(args['owner-pid']);
53
57
  const nonce=String(args['session-nonce']||'');
58
+ const ipcOwner=args['owner-channel']==='ipc';
59
+ if(ipcOwner&&typeof process.send!=='function')throw new Error('guardian owner IPC channel missing');
54
60
  if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
61
+ if(ipcOwner&&process.ppid!==ownerPid)throw new Error('guardian IPC parent does not match declared owner');
55
62
  if(!/^[0-9a-f]{64}$/.test(nonce)) throw new Error('guardian session nonce invalid');
56
63
 
57
64
  const launcher=await requireRegular(path.join(root,'bin','DEADBYTE-MCP.ps1'),'stable DEADBYTE launcher');
@@ -63,7 +70,7 @@ async function main(){
63
70
  for(;;){
64
71
  const marker=await readMarker(markerPath);
65
72
  if(!marker||!markerMatches(marker,{ownerPid,nonce})) return;
66
- if(pidAlive(ownerPid)){
73
+ if(ipcOwner?!ownerDisconnected:pidAlive(ownerPid)){
67
74
  await sleep(250);
68
75
  continue;
69
76
  }
@@ -0,0 +1,74 @@
1
+ const PLANE_ID=/^[a-z][a-z0-9_-]{0,63}$/;
2
+
3
+ function boundedReason(error){
4
+ const text=error instanceof Error?error.message:String(error);
5
+ return text.replace(/[\r\n\t]+/g,' ').slice(0,1024);
6
+ }
7
+
8
+ export class RuntimePlaneUnavailableError extends Error {
9
+ constructor(plane,diagnostic={}){
10
+ super(`${plane} runtime unavailable: ${String(diagnostic.reason??diagnostic.code??'not ready')}`);
11
+ this.name='RuntimePlaneUnavailableError';
12
+ this.code='DEADBYTE_RUNTIME_UNAVAILABLE';
13
+ this.plane=plane;
14
+ this.diagnostic=diagnostic;
15
+ }
16
+ }
17
+
18
+ export function createUnavailableRuntime(plane,diagnostic={}){
19
+ if(typeof plane!=='string'||!PLANE_ID.test(plane)) throw new Error('runtime plane id invalid');
20
+ const fail=()=>Promise.reject(new RuntimePlaneUnavailableError(plane,diagnostic));
21
+ return new Proxy(Object.freeze({plane,unavailable:true,diagnostic}),{
22
+ get(target,property){
23
+ if(property in target) return target[property];
24
+ if(property==='then') return undefined;
25
+ return fail;
26
+ }
27
+ });
28
+ }
29
+
30
+ export function createRuntimePlaneRegistry(){
31
+ const planes=new Map();
32
+ let epoch=0;
33
+ function update(id,next){
34
+ planes.set(id,Object.freeze({...next}));
35
+ epoch++;
36
+ return next.runtime??null;
37
+ }
38
+ function define(id,{configured,required=false,dependencies=[]}={}){
39
+ if(typeof id!=='string'||!PLANE_ID.test(id)||planes.has(id)) throw new Error(`runtime plane definition invalid: ${String(id)}`);
40
+ if(!Array.isArray(dependencies)||dependencies.some(dep=>typeof dep!=='string'||!PLANE_ID.test(dep))) throw new Error(`runtime plane dependencies invalid: ${id}`);
41
+ planes.set(id,Object.freeze({id,configured:configured===true,required:required===true,dependencies:[...new Set(dependencies)],state:configured===true?'STARTING':'DISABLED',ready:false,runtime:null,diagnostic:configured===true?null:{code:'RUNTIME_DISABLED',reason:'runtime is not configured',recoverable:false}}));
42
+ }
43
+ async function initialize(id,initializer,{policySha256=null}={}){
44
+ const current=planes.get(id);
45
+ if(!current) throw new Error(`runtime plane not defined: ${id}`);
46
+ if(!current.configured) return null;
47
+ for(const dependency of current.dependencies){
48
+ const dep=planes.get(dependency);
49
+ if(!dep||!dep.ready){
50
+ return update(id,{...current,state:'UNAVAILABLE',ready:false,runtime:null,policy_sha256:policySha256,
51
+ diagnostic:{code:'DEPENDENCY_UNAVAILABLE',reason:`dependency '${dependency}' is not ready`,recoverable:true}});
52
+ }
53
+ }
54
+ try{
55
+ const runtime=await initializer();
56
+ if(!runtime) throw new Error('initializer returned no runtime');
57
+ return update(id,{...current,state:'READY',ready:true,runtime,policy_sha256:policySha256,diagnostic:null});
58
+ }catch(error){
59
+ if(current.required) throw error;
60
+ return update(id,{...current,state:'DEGRADED',ready:false,runtime:null,policy_sha256:policySha256,
61
+ diagnostic:{code:'INITIALIZATION_FAILED',reason:boundedReason(error),recoverable:true}});
62
+ }
63
+ }
64
+ function state(id){
65
+ const value=planes.get(id);
66
+ if(!value) throw new Error(`runtime plane not defined: ${id}`);
67
+ const {runtime,...publicState}=value;
68
+ return Object.freeze(publicState);
69
+ }
70
+ function runtime(id){return planes.get(id)?.runtime??null;}
71
+ function snapshot(){return Object.freeze({epoch,planes:Object.freeze(Object.fromEntries([...planes].map(([id])=>[id,state(id)])))});}
72
+ return Object.freeze({define,initialize,state,runtime,snapshot});
73
+ }
74
+
@@ -1,7 +1,7 @@
1
1
  import { createHash, createPrivateKey, createPublicKey, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
3
  import { existsSync } from 'node:fs';
4
- import { lstat, mkdir, open, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
@@ -306,6 +306,69 @@ async function normalizeComponentIdentities(config){
306
306
  return next;
307
307
  }
308
308
 
309
+ function bridgeDependencyName(relativePath,usedNames){
310
+ const normalized=String(relativePath).replaceAll('\\','/').replaceAll('/','_');
311
+ let name=normalized;
312
+ if(!/^[A-Za-z0-9._-]{1,32}$/.test(name)||usedNames.has(name)){
313
+ name=`dep-${sha256Bytes(Buffer.from(String(relativePath),'utf8')).slice(0,20)}`;
314
+ }
315
+ if(usedNames.has(name)) throw new Error(`bridge dependency name collision: ${relativePath}`);
316
+ usedNames.add(name);
317
+ return name;
318
+ }
319
+ function bridgeStaticRelativeImports(source,file){
320
+ const found=[];
321
+ for(const [index,line] of String(source).split(/\r?\n/).entries()){
322
+ if(/\bimport\s*\(\s*['"]\.\.?\//.test(line)) throw new Error(`bridge dynamic relative import is forbidden: ${file}:${index+1}`);
323
+ const importStart=/^\s*import\b/.test(line);
324
+ const exportStart=/^\s*export\b/.test(line)&&/\bfrom\b/.test(line);
325
+ if(!importStart&&!exportStart) continue;
326
+ const match=importStart
327
+ ? /^\s*import\s+(?:(?:[^'"]+?)\s+from\s*)?['"]([^'"]+)['"]\s*;?\s*$/.exec(line)
328
+ : /^\s*export\s+[^'"]*?\s+from\s*['"]([^'"]+)['"]\s*;?\s*$/.exec(line);
329
+ if(!match) throw new Error(`bridge import syntax unsupported for dependency binding: ${file}:${index+1}`);
330
+ if(match[1].startsWith('.')) found.push(match[1]);
331
+ }
332
+ return found;
333
+ }
334
+ async function discoverBridgeLocalDependencies(root,entryScript){
335
+ const absoluteRoot=path.resolve(root),realRoot=await realpath(absoluteRoot);
336
+ const srcRoot=path.join(absoluteRoot,'src');
337
+ const queue=[path.resolve(entryScript)],visited=new Set(),files=[];
338
+ while(queue.length){
339
+ const current=queue.shift();
340
+ const currentReal=await realpath(current);
341
+ const relativeReal=path.relative(realRoot,currentReal);
342
+ if(relativeReal.startsWith('..')||path.isAbsolute(relativeReal)) throw new Error(`bridge dependency escapes bridge root: ${current}`);
343
+ if(visited.has(currentReal)) continue;
344
+ visited.add(currentReal);
345
+ if(visited.size>16) throw new Error('bridge local dependency closure exceeds 16 files');
346
+ const stat=await lstat(current);
347
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`bridge dependency must be a regular file: ${current}`);
348
+ const source=await readFile(current,'utf8');
349
+ const specifiers=bridgeStaticRelativeImports(source,current);
350
+ if(currentReal!==await realpath(entryScript)) files.push(currentReal);
351
+ for(const specifier of specifiers){
352
+ if(!specifier.startsWith('./')) throw new Error(`bridge relative import escapes bridge root: ${specifier}`);
353
+ if(specifier.includes('?')||specifier.includes('#')) throw new Error(`bridge relative import query/fragment unsupported: ${specifier}`);
354
+ const resolved=path.resolve(path.dirname(current),specifier);
355
+ const relative=path.relative(absoluteRoot,resolved);
356
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency escapes bridge root: ${specifier}`);
357
+ if(!/\.(?:mjs|js)$/i.test(resolved)) throw new Error(`bridge local dependency must be .mjs/.js: ${specifier}`);
358
+ queue.push(resolved);
359
+ }
360
+ }
361
+ const unique=[...new Set(files)].sort((a,b)=>a.localeCompare(b));
362
+ const usedNames=new Set();
363
+ const dependencies=[];
364
+ for(const file of unique){
365
+ const relative=path.relative(srcRoot,file).replaceAll('\\','/');
366
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency must remain under src: ${file}`);
367
+ dependencies.push({Name:bridgeDependencyName(relative,usedNames),Path:file,Sha256:await sha256File(file)});
368
+ }
369
+ return dependencies;
370
+ }
371
+
309
372
  export async function prepareBridgeSpec(config,bootstrap){
310
373
  if(!bootstrap) return structuredClone(config);
311
374
  if(typeof bootstrap.root!=='string'||!path.isAbsolute(bootstrap.root)||!HEX64.test(String(bootstrap.scriptSha256??''))) {
@@ -327,6 +390,7 @@ export async function prepareBridgeSpec(config,bootstrap){
327
390
  next.Bridge.Arguments='src\\bridge.mjs';
328
391
  next.Bridge.ScriptPath=script;
329
392
  next.Bridge.ScriptSha256=actual;
393
+ next.Bridge.Dependencies=await discoverBridgeLocalDependencies(root,script);
330
394
  return next;
331
395
  }
332
396
 
@@ -1334,6 +1398,23 @@ function handoffPriorPointer(body){
1334
1398
  function handoffNewPointer(body){
1335
1399
  return body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1336
1400
  }
1401
+ async function verifyCompletedUpdateReceipt(body,state,statePath,key){
1402
+ const candidate=handoffNewPointer(body),prior=handoffPriorPointer(body);
1403
+ if(!candidate||!samePublicPointer(candidate,statePointer(state,'new_pointer'))||!samePublicPointer(prior,statePointer(state,'prior_pointer'))) throw new Error('completed update handoff pointer binding mismatch');
1404
+ if(state.expected_version!==candidate.version||state.expected_manifest_sha256!==candidate.manifest_sha256) throw new Error('completed update journal subject mismatch');
1405
+ const expectedPath=path.join(path.dirname(statePath),'receipt.json');
1406
+ if(typeof state.receipt_path!=='string'||!path.isAbsolute(state.receipt_path)||path.resolve(state.receipt_path)!==expectedPath||!HEX64.test(String(state.receipt_sha256??''))) throw new Error('completed update receipt reference invalid');
1407
+ const info=await lstat(expectedPath);
1408
+ if(!info.isFile()||info.isSymbolicLink()) throw new Error('completed update receipt must be a regular file');
1409
+ const receipt=await readJson(expectedPath);
1410
+ if(!verifySlotUpdateEnvelope(receipt,key.publicKey)||receipt.receipt_sha256!==state.receipt_sha256) throw new Error('completed update receipt signature or digest mismatch');
1411
+ const subject=receipt.body;
1412
+ if(subject.schema!==UPDATE_SCHEMA||subject.rollback_performed!==false||subject.update_id!==body.update_id||subject.release_version!==candidate.version||subject.candidate_manifest_sha256!==candidate.manifest_sha256||
1413
+ !samePublicPointer(statePointer(subject,'active_pointer'),candidate)||!samePublicPointer(statePointer(subject,'prior_pointer'),prior)||
1414
+ subject.release_slot?.release_id!==candidate.release_id||subject.release_slot?.release_root!==candidate.release_root||subject.release_slot?.manifest_sha256!==candidate.manifest_sha256||
1415
+ subject.generation?.generation_root!==candidate.generation_root||subject.generation?.generation_manifest_sha256!==candidate.generation_manifest_sha256||subject.launcher?.sha256!==candidate.launcher_sha256) throw new Error('completed update receipt subject binding mismatch');
1416
+ return {receipt_path:expectedPath,receipt_sha256:receipt.receipt_sha256};
1417
+ }
1337
1418
  async function resumeSlotUpdate({handoffPath}){
1338
1419
  const absoluteHandoff=path.resolve(handoffPath),probe=await readJson(absoluteHandoff);
1339
1420
  const stateRoot=path.resolve(String(probe?.body?.state_root??path.dirname(path.dirname(path.dirname(path.dirname(absoluteHandoff))))));
@@ -1342,8 +1423,9 @@ async function resumeSlotUpdate({handoffPath}){
1342
1423
  if(releaseState.schema!=='deadbyte.r27-slot-update-state.v1'||releaseState.update_id!==body.update_id) throw new Error('handoff state binding mismatch');
1343
1424
  let phase=String(releaseState.phase??'');
1344
1425
  if(phase==='completed'){
1426
+ const verifiedReceipt=await verifyCompletedUpdateReceipt(body,releaseState,statePath,key);
1345
1427
  return {ok:true,resumed:false,completed:true,update_id:body.update_id,state_path:statePath,
1346
- receipt_path:releaseState.receipt_path??null,receipt_sha256:releaseState.receipt_sha256??null};
1428
+ ...verifiedReceipt,historical_receipt_verified:true,current_runtime_verified:false};
1347
1429
  }
1348
1430
  if(phase.startsWith('failed')) throw new Error('slot update already failed at phase='+phase);
1349
1431
  const priorPointer=handoffPriorPointer(body),candidatePointer=handoffNewPointer(body);
@@ -1735,6 +1817,7 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
1735
1817
  console.log('UPDATE_ID '+result.update_id);
1736
1818
  console.log('STATE_PATH '+result.state_path);
1737
1819
  if(result.receipt_sha256) console.log('RECEIPT_SHA256 '+result.receipt_sha256);
1820
+ if(result.historical_receipt_verified===true) console.log('VERIFICATION_SCOPE historical-signed-receipt; current-runtime-not-observed');
1738
1821
  return result;
1739
1822
  }
1740
1823
  if(argv.includes('--recover-interrupted')){
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.34';
1
+ export const DEADBYTE_VERSION = '0.15.36';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -0,0 +1,60 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createBridgeGovernor } from '../bridge/src/r40-governor.mjs';
4
+ import { createBridgeHealth } from '../bridge/src/bridge-health-r59.mjs';
5
+
6
+ const limits={
7
+ normal:{max_active:1,max_queue:2,queue_timeout_ms:1000},
8
+ autonomous:{max_active:1,max_queue:1,queue_timeout_ms:1000},
9
+ desktop_observe:{max_active:1,max_queue:1,queue_timeout_ms:1000},
10
+ desktop_action:{max_active:1,max_queue:1,queue_timeout_ms:1000}
11
+ };
12
+
13
+ test('R59 bridge governor removes cancelled queued work and drains without replay',async()=>{
14
+ const governor=createBridgeGovernor({limits});
15
+ const first=await governor.acquire('machine_status');
16
+ const abort=new AbortController();
17
+ const queued=governor.acquire('machine_ping',{signal:abort.signal});
18
+ abort.abort(new Error('caller disconnected'));
19
+ await assert.rejects(queued,/caller disconnected|aborted/);
20
+ assert.equal(governor.snapshot().normal.queued,0);
21
+ assert.equal(governor.snapshot().normal.cancelled,1);
22
+ const queuedDuringRecovery=governor.acquire('machine_ping');
23
+ assert.equal(governor.reconcile('core generation changed'),1);
24
+ await assert.rejects(queuedDuringRecovery,/core generation changed/);
25
+ first.release();
26
+ const after=await governor.acquire('machine_status');
27
+ after.release();
28
+ const drain=governor.close('bridge reconciling');
29
+ await assert.rejects(governor.acquire('machine_status'),/closed/);
30
+ await drain;
31
+ assert.equal(governor.snapshot().normal.active,0);
32
+ });
33
+
34
+ test('R59 bridge health requires fresh child negotiation catalog and v3 capability attestation',()=>{
35
+ let now=100_000;
36
+ const health=createBridgeHealth({clock:()=>now,maxCapabilityAgeMs:15_000});
37
+ assert.equal(health.snapshot().http_status,503);
38
+ health.coreConnected({childIdentity:'core-abc',protocolEra:'modern',catalogSha256:'a'.repeat(64)});
39
+ assert.equal(health.snapshot().http_status,503);
40
+ health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'b'.repeat(64),
41
+ state_epoch:3,observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),
42
+ bindings:{release_manifest_sha256:'c'.repeat(64),generation_manifest_sha256:'d'.repeat(64)}});
43
+ assert.equal(health.snapshot().state,'READY');
44
+ assert.equal(health.snapshot().http_status,200);
45
+ health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'e'.repeat(64),state_epoch:4,
46
+ observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),
47
+ bindings:{release_manifest_sha256:'c'.repeat(64),generation_manifest_sha256:'d'.repeat(64)},
48
+ planes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',diagnostic:{code:'INITIALIZATION_FAILED'}}}});
49
+ assert.equal(health.snapshot().state,'DEGRADED');
50
+ assert.equal(health.snapshot().http_status,503);
51
+ health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'b'.repeat(64),state_epoch:5,
52
+ observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),bindings:{}});
53
+ now+=15_001;
54
+ assert.equal(health.snapshot().state,'DEGRADED');
55
+ assert.equal(health.snapshot().http_status,503);
56
+ health.reconciling('core transport closed after dispatch');
57
+ const degraded=health.snapshot();
58
+ assert.equal(degraded.state,'RECONCILING');
59
+ assert.doesNotMatch(JSON.stringify(degraded),/token|secret|C:\\/i);
60
+ });
@@ -18,6 +18,22 @@ test('capability resolver enforces 5 canonical states', () => {
18
18
  assert.equal(CAPABILITY_STATES.UNAVAILABLE, 'UNAVAILABLE');
19
19
  });
20
20
 
21
+ test('R59 live plane state can only reduce signed capability availability', async()=>{
22
+ const ledger={entries:[{
23
+ id:'coding.search',compact_tool:'coding_inspect',full_tool:'coding_search',compact_op:'search',
24
+ authority:'coding',mutating:false,production:true,runtime_flag:'DEADBYTE_CODING_RUNTIME'
25
+ }]};
26
+ const result=await resolveCapabilityStates({
27
+ ledger,toolCatalog:['coding_inspect'],profile:'compact',env:{DEADBYTE_CODING_RUNTIME:'1'},
28
+ codingRuntime:{sessionStatus:async()=>({armed:true})},
29
+ runtimePlanes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',ready:false,diagnostic:{code:'INITIALIZATION_FAILED',reason:'missing root',recoverable:true}}}
30
+ });
31
+ assert.equal(result.capabilities[0].state,CAPABILITY_STATES.DEGRADED);
32
+ assert.equal(result.capabilities[0].ready,false);
33
+ assert.equal(result.capabilities[0].diagnostic.code,'INITIALIZATION_FAILED');
34
+ assert.deepEqual(result.effective_capabilities,[]);
35
+ });
36
+
21
37
  test('deriveCapabilityRequirements maps dependencies accurately', () => {
22
38
  const reqCodingRead = deriveCapabilityRequirements({
23
39
  id: 'coding.search',
@@ -0,0 +1,39 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createCapabilitySnapshotTracker } from '../src/capability-snapshot.mjs';
4
+
5
+ const H=value=>String(value).repeat(64).slice(0,64);
6
+ const ledger={schema:'deadbyte.capability-ledger.v1',ledger_sha256:H('a'),entries:[{
7
+ id:'machine.status',full_tool:'machine_status',compact_tool:'machine_observe',compact_op:'machine_status',
8
+ runtime_flag:'DEADBYTE_MACHINE_RUNTIME',authority:'none',mutating:false,receipt_class:'machine',production:true,host_only:false
9
+ }]};
10
+ const resolution={capabilities:[{...ledger.entries[0],tool:'machine_observe',operations:['machine_status'],requires:['DEADBYTE_MACHINE_RUNTIME'],state:'AVAILABLE',ready:true,diagnostic:null}],effective_capabilities:['machine.status'],unavailable_capabilities:[],degraded_capabilities:[]};
11
+
12
+ test('R59 capability snapshot v3 is a backward-compatible content-bound live overlay', () => {
13
+ let now=Date.parse('2026-10-02T12:00:00.000Z');
14
+ const tracker=createCapabilitySnapshotTracker({clock:()=>now,ttlMs:5000});
15
+ const input={profile:'compact',ledger,toolCatalog:['machine_observe'],catalogSha256:H('b'),resolution,
16
+ bindings:{release_id:'v0.15.36-test',release_manifest_sha256:H('c'),generation_manifest_sha256:H('d')},
17
+ planes:{machine:{state:'READY',ready:true,policy_sha256:null,diagnostic:null}}};
18
+ const first=tracker.snapshot(input);
19
+ assert.equal(first.schema,'deadbyte.capability-manifest.v3');
20
+ assert.equal(first.ledger_sha256,ledger.ledger_sha256);
21
+ assert.deepEqual(first.tool_catalog,['machine_observe']);
22
+ assert.equal(first.state_epoch,1);
23
+ assert.match(first.snapshot_id,/^[0-9a-f]{64}$/);
24
+ assert.equal(first.capabilities[0].selected_route.tool,'machine_observe');
25
+ now+=1000;
26
+ const same=tracker.snapshot(input);
27
+ assert.equal(same.snapshot_id,first.snapshot_id);
28
+ assert.equal(same.state_epoch,1);
29
+ assert.notEqual(same.observed_at_utc,first.observed_at_utc);
30
+ const changed=tracker.snapshot({...input,planes:{machine:{state:'DEGRADED',ready:false,policy_sha256:null,diagnostic:{code:'DOWN',reason:'probe failed'}}}});
31
+ assert.equal(changed.state_epoch,2);
32
+ assert.notEqual(changed.snapshot_id,first.snapshot_id);
33
+ });
34
+
35
+ test('R59 capability snapshot rejects routes outside the signed ledger', () => {
36
+ const tracker=createCapabilitySnapshotTracker();
37
+ assert.throws(()=>tracker.snapshot({profile:'compact',ledger,toolCatalog:['invented'],catalogSha256:H('b'),
38
+ resolution:{...resolution,capabilities:[{...resolution.capabilities[0],id:'invented.capability'}]},bindings:{},planes:{}}),/outside signed ledger/);
39
+ });
@@ -72,7 +72,7 @@ test('R36 coding_inspect gateway calls runtime directly and never MCP-recurses',
72
72
  assert.equal(result.structuredContent.result.path,'src/a.mjs');
73
73
  });
74
74
 
75
- test('R40 capability manifest v2 attests the exact registered compact catalog',async()=>{
75
+ test('R59 capability manifest v3 preserves exact compact catalog attestation',async()=>{
76
76
  const server=fakeServer();
77
77
  registerCompactGatewayTools(server,{
78
78
  profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'a'.repeat(64)},
@@ -81,13 +81,33 @@ test('R40 capability manifest v2 attests the exact registered compact catalog',a
81
81
  });
82
82
  const result=await server.tools.get('capability_manifest').handler({});
83
83
  const manifest=result.structuredContent.result;
84
- assert.equal(manifest.schema,'deadbyte.capability-manifest.v2');
84
+ assert.equal(manifest.schema,'deadbyte.capability-manifest.v3');
85
85
  assert.equal(manifest.profile,'compact');
86
86
  assert.deepEqual(manifest.tool_catalog,[...server.tools.keys()].sort());
87
87
  assert.equal(manifest.tool_count,server.tools.size);
88
88
  assert.match(manifest.catalog_sha256,/^[0-9a-f]{64}$/);
89
89
  });
90
90
 
91
+ test('R59 capability manifest v3 preserves v2 catalog fields and adds live bindings',async()=>{
92
+ const server=fakeServer();
93
+ registerCompactGatewayTools(server,{
94
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'a'.repeat(64)},
95
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
96
+ runtimePlanes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',ready:false,diagnostic:{code:'INITIALIZATION_FAILED',reason:'missing root',recoverable:true}}},
97
+ releaseBindingProvider:async()=>({release_id:'v0.15.36-test',release_manifest_sha256:'b'.repeat(64),generation_manifest_sha256:'c'.repeat(64)}),
98
+ catalogProvider:()=>[...server.tools.keys()]
99
+ });
100
+ const result=await server.tools.get('capability_manifest').handler({});
101
+ const manifest=result.structuredContent.result;
102
+ assert.equal(manifest.schema,'deadbyte.capability-manifest.v3');
103
+ assert.equal(manifest.tool_count,server.tools.size);
104
+ assert.deepEqual(manifest.tool_catalog,[...server.tools.keys()].sort());
105
+ assert.match(manifest.snapshot_id,/^[0-9a-f]{64}$/);
106
+ assert.equal(manifest.state_epoch,1);
107
+ assert.equal(manifest.bindings.release_id,'v0.15.36-test');
108
+ assert.equal(manifest.planes.DEADBYTE_CODING_RUNTIME.state,'DEGRADED');
109
+ });
110
+
91
111
 
92
112
  test('R40 desktop_control is stable when runtime is absent and routes typed read-only operations when present',async()=>{
93
113
  const server=fakeServer();
@@ -81,7 +81,7 @@ test('R44 real max-runtime compact stdio catalog is exactly 43 with verified ski
81
81
  const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
82
82
  assert.equal(manifest.isError,false);
83
83
  const m=manifest.structuredContent.result;
84
- assert.equal(m.schema,'deadbyte.capability-manifest.v2');
84
+ assert.equal(m.schema,'deadbyte.capability-manifest.v3');
85
85
  assert.equal(m.profile,'compact');
86
86
  assert.equal(m.tool_count,43);
87
87
  assert.deepEqual(m.tool_catalog,names);
@@ -81,7 +81,7 @@ test('R45 real max-runtime compact stdio catalog is exactly 43 with verified ski
81
81
  const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
82
82
  assert.equal(manifest.isError,false);
83
83
  const m=manifest.structuredContent.result;
84
- assert.equal(m.schema,'deadbyte.capability-manifest.v2');
84
+ assert.equal(m.schema,'deadbyte.capability-manifest.v3');
85
85
  assert.equal(m.profile,'compact');
86
86
  assert.equal(m.tool_count,43);
87
87
  assert.deepEqual(m.tool_catalog,names);
@@ -714,7 +714,8 @@ test('MCP contained_transform schema is a compile-time operation enum with no ex
714
714
  assert.ok(containedStart >= 0 && containedEnd > containedStart);
715
715
  assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
716
716
  assert.match(contained, /inputSchema: z\.strictObject\(\{/);
717
- assert.match(server, /if \(hostPolicy\)/);
717
+ assert.match(server, /if \(planeRegistry\.state\('host'\)\.configured\)/);
718
+ assert.match(server, /requirePlane\('host',hostPolicy\)/);
718
719
  for (const name of ['host_file_read', 'host_file_write', 'host_exec']) assert.match(server, new RegExp(`'${name}'`));
719
720
  assert.doesNotMatch(contained, /inputSchema: z\.object\(\{/);
720
721
  assert.doesNotMatch(contained, /executable:\s*z\.|argv:\s*z\.|environment:\s*z\.|challenge:\s*z\.|request_digest:\s*z\.|worker_id:\s*z\./);
@@ -0,0 +1,39 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawnSync } from 'node:child_process';
7
+
8
+ const controllerPath=new URL('../controller/deadbyte-controller.ps1',import.meta.url);
9
+ function quote(value){return `'${String(value).replaceAll("'","''")}'`;}
10
+ function functionBlock(source,name,nextName){
11
+ const start=source.indexOf(`function ${name}`),end=source.indexOf(`function ${nextName}`,start+1);
12
+ assert.ok(start>=0&&end>start,`missing ${name}`);
13
+ return source.slice(start,end);
14
+ }
15
+
16
+ test('R59 controller refuses to arm process or coding policy with a missing root',async()=>{
17
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r59-controller-preflight-'));
18
+ try{
19
+ const runtime=path.join(root,'runtime');
20
+ const missing=path.join(root,'missing');
21
+ const coding=path.join(root,'coding.json'),processPolicy=path.join(root,'process.json');
22
+ await writeFile(coding,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
23
+ lease_file:path.join(runtime,'coding-grant.json'),roots:{project:{path:missing}}}));
24
+ await writeFile(processPolicy,JSON.stringify({schema:'deadbyte.process-policy.v1',enabled:true,
25
+ grant_file:path.join(runtime,'process-grant.json'),roots:{project:{path:missing}},profiles:{node:{}}}));
26
+ const source=await readFile(controllerPath,'utf8');
27
+ const blocks=[functionBlock(source,'Assert-PolicyRootsAvailable','Get-HostPolicyMetadata'),
28
+ functionBlock(source,'Get-ProcessPolicyMetadata','Get-ProcessGrantStatus'),
29
+ functionBlock(source,'Get-CodingPolicyMetadata','Get-CodingLeaseStatus')].join('\n');
30
+ const script=`$ErrorActionPreference='Stop';$RuntimeDir=${quote(runtime)};function Fail([string]$m){throw $m};function Get-ExecutableHash($p){return '${'a'.repeat(64)}'};${blocks};`+
31
+ `$errors=@();foreach($case in @(@{kind='process';cfg=@{ProcessPolicyFile=${quote(processPolicy)}}},@{kind='coding';cfg=@{CodingPolicyFile=${quote(coding)}}})){try{if($case.kind -eq 'process'){Get-ProcessPolicyMetadata $case.cfg|Out-Null}else{Get-CodingPolicyMetadata $case.cfg|Out-Null};throw 'unexpected pass'}catch{$errors+=$_.Exception.Message}};$errors|ConvertTo-Json -Compress`;
32
+ const result=spawnSync('powershell',['-NoLogo','-NoProfile','-NonInteractive','-Command',script],{encoding:'utf8'});
33
+ assert.equal(result.status,0,result.stderr);
34
+ const errors=JSON.parse(result.stdout.trim());
35
+ assert.equal(errors.length,2);
36
+ assert.match(errors[0],/process policy root 'project' missing/);
37
+ assert.match(errors[1],/coding policy root 'project' missing/);
38
+ }finally{await rm(root,{recursive:true,force:true});}
39
+ });
@@ -17,5 +17,8 @@ test('R21.1 binds controller execution to CoreRoot controller directory', async
17
17
 
18
18
  test('R21.1 validates controller root before creating command runtime directories', async () => {
19
19
  const source = await readFile(controller, 'utf8');
20
- assert.match(source, /function Run-Command[\s\S]*?\$cfg = Get-Config\s+Assert-ControllerRootBound \$cfg\s+Ensure-Directory \$RuntimeDir/);
20
+ const dispatch = source.slice(source.indexOf('function Run-CommandCore('), source.indexOf('function Run-Command(', source.indexOf('function Run-CommandCore(')));
21
+ assert.match(dispatch, /\$cfg = Get-Config\s+Assert-ControllerRootBound \$cfg\s+\$readOnly = \$Name -in/);
22
+ assert.match(dispatch, /if \(-not \$readOnly\) \{ Ensure-Directory \$RuntimeDir; Ensure-Directory \$LogDir \}/);
23
+ assert.ok(dispatch.indexOf('Assert-ControllerRootBound $cfg') < dispatch.indexOf('Ensure-Directory $RuntimeDir'));
21
24
  });
@@ -12,7 +12,8 @@ test('controller derives runtime state from observed identities and readiness',
12
12
  assert.match(source, /'OFFLINE'/);
13
13
  assert.match(source, /'PARKED'/);
14
14
  assert.match(source, /'ONLINE'/);
15
- assert.match(source, /'DEGRADED'/);
15
+ assert.match(source, /'STARTING'/);
16
+ assert.match(source, /'FAILED'/);
16
17
  assert.doesNotMatch(source, /state\.txt/i);
17
18
  });
18
19