deadbyte-mcp 0.15.35 → 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 (92) hide show
  1. package/MANIFEST.SHA256 +91 -46
  2. package/PROVENANCE.json +76 -30
  3. package/README.txt +62 -8
  4. package/RELEASE.json +78 -23
  5. package/SBOM.cdx.json +10 -25
  6. package/TEST-MATRIX.json +109 -44
  7. package/bridge/package.json +4 -2
  8. package/bridge/src/bridge-health-r59.mjs +53 -0
  9. package/bridge/src/bridge.mjs +158 -26
  10. package/bridge/src/canonical-json-r60.mjs +13 -0
  11. package/bridge/src/r40-governor.mjs +56 -10
  12. package/bridge/src/reconnect-delay-r60.mjs +31 -0
  13. package/bridge/src/remote-call-claims-r60.mjs +157 -0
  14. package/bridge/src/remote-dispatch-r60.mjs +75 -0
  15. package/controller/deadbyte-controller.ps1 +76 -13
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R59-PROVENANCE.json +87 -0
  18. package/docs/R59-RELEASE.json +187 -0
  19. package/docs/R59-SBOM.cdx.json +68 -0
  20. package/docs/R59-TASK-CONTRACT.md +26 -0
  21. package/docs/R59-TEST-MATRIX.json +258 -0
  22. package/docs/R59-bridge-package.json +13 -0
  23. package/docs/R59-package-lock.json +184 -0
  24. package/docs/R59-package.json +106 -0
  25. package/docs/R59-version.mjs +6 -0
  26. package/docs/R60-TASK-CONTRACT.md +22 -0
  27. package/package.json +7 -3
  28. package/proof/r59-full-regression-current.json +13 -0
  29. package/proof/r59-full-regression-current.stderr.log +0 -0
  30. package/proof/r59-full-regression-current.stdout.log +1268 -0
  31. package/scripts/full-regression-r60.mjs +32 -0
  32. package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
  33. package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
  34. package/scripts/r59-full-regression-capture.mjs +35 -0
  35. package/src/capability-resolver.mjs +25 -7
  36. package/src/capability-snapshot.mjs +56 -0
  37. package/src/compact-gateway-mcp-tools.mjs +10 -11
  38. package/src/deadbyte-cli.mjs +80 -34
  39. package/src/mcp-server.mjs +179 -118
  40. package/src/r40-compact-disarmed-probe.mjs +1 -1
  41. package/src/r44-compact-disarmed-probe.mjs +1 -1
  42. package/src/r45-compact-disarmed-probe.mjs +1 -1
  43. package/src/remote-authority.mjs +56 -0
  44. package/src/remote-enforcement-probe-r60.mjs +17 -0
  45. package/src/remote-live-log.mjs +19 -2
  46. package/src/remote-log-decode-r60.mjs +32 -0
  47. package/src/remote-mcp-machine-smoke-client.mjs +19 -1
  48. package/src/remote-mcp-r40-parity-client.mjs +1 -1
  49. package/src/remote-mcp-r44-parity-client.mjs +1 -1
  50. package/src/remote-mcp-r45-parity-client.mjs +1 -1
  51. package/src/remote-mcp-smoke-client.mjs +1 -1
  52. package/src/remote-session-guardian.mjs +20 -3
  53. package/src/runtime-plane-registry.mjs +74 -0
  54. package/src/tool-audit.mjs +5 -0
  55. package/src/version.mjs +1 -1
  56. package/test/bridge-runtime-r59.test.mjs +60 -0
  57. package/test/capability-resolver.test.mjs +16 -0
  58. package/test/capability-snapshot-r59.test.mjs +39 -0
  59. package/test/compact-gateway-r36.test.mjs +22 -2
  60. package/test/compact-max-catalog-r44.test.mjs +1 -1
  61. package/test/compact-max-catalog-r45.test.mjs +1 -1
  62. package/test/contained.test.mjs +2 -1
  63. package/test/controller-policy-preflight-r59.test.mjs +39 -0
  64. package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
  65. package/test/observation-integration.test.mjs +2 -3
  66. package/test/optional-plane-degraded-r59.test.mjs +44 -0
  67. package/test/r48-release-contract.test.mjs +1 -1
  68. package/test/r49-windows-gate.test.mjs +1 -1
  69. package/test/r51-runtime-coherence.test.mjs +2 -2
  70. package/test/r58-bundled-bridge.test.mjs +3 -1
  71. package/test/r58-cleanup-retention.test.mjs +9 -3
  72. package/test/r58-concurrent-recovery.test.mjs +9 -3
  73. package/test/r58-controller-live-local.test.mjs +36 -4
  74. package/test/r58-controller-state-coherence.test.mjs +1 -1
  75. package/test/r58-release-contract.test.mjs +3 -2
  76. package/test/r58-stale-owner.test.mjs +2 -2
  77. package/test/r58-windows-gate.test.mjs +2 -1
  78. package/test/r59-release-contract.test.mjs +61 -0
  79. package/test/r59-tool-surface-compat.test.mjs +90 -0
  80. package/test/r60-authority-coherence.test.mjs +36 -0
  81. package/test/r60-authority-enforcement.test.mjs +28 -0
  82. package/test/r60-bridge-dispatch.test.mjs +119 -0
  83. package/test/r60-clock-discipline.test.mjs +15 -0
  84. package/test/r60-live-timeline.test.mjs +29 -0
  85. package/test/r60-reconnect-errors.test.mjs +21 -0
  86. package/test/r60-reconnect-jitter.test.mjs +18 -0
  87. package/test/r60-release-contract.test.mjs +81 -0
  88. package/test/r60-remote-call-recovery.test.mjs +170 -0
  89. package/test/r60-windows-log-decode.test.mjs +25 -0
  90. package/test/remote-live-log-r37.test.mjs +1 -1
  91. package/test/remote-session-cli.test.mjs +200 -43
  92. package/test/runtime-plane-registry-r59.test.mjs +40 -0
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+
3
+ export function safeRemoteLogPrefixLength(bytes,encoding='utf8'){
4
+ if(!Buffer.isBuffer(bytes))throw new Error('remote log prefix bytes invalid');
5
+ if(encoding==='utf16le'){
6
+ let size=bytes.length-(bytes.length%2);
7
+ if(size>=2){const last=bytes.readUInt16LE(size-2);if(last>=0xd800&&last<=0xdbff)size-=2;}
8
+ return size;
9
+ }
10
+ if(encoding!=='utf8')throw new Error('remote log prefix encoding invalid');
11
+ let index=bytes.length-1;
12
+ while(index>=0&&(bytes[index]&0xc0)===0x80)index--;
13
+ if(index<0)return 0;
14
+ const lead=bytes[index];
15
+ const needed=lead<0x80?1:lead>=0xc2&&lead<=0xdf?2:lead>=0xe0&&lead<=0xef?3:lead>=0xf0&&lead<=0xf4?4:1;
16
+ return bytes.length-index<needed?index:bytes.length;
17
+ }
18
+
19
+ export function decodeRemoteLogBytes(file,bytes,{encoding=null}={}){
20
+ if(typeof file!=='string'||!Buffer.isBuffer(bytes))throw new Error('remote log decoder input invalid');
21
+ const knownLog=/\.log$/i.test(path.basename(file));
22
+ if(!knownLog)return bytes.toString('utf8');
23
+ const utf16=encoding==='utf16le'||(bytes.length>=2&&bytes[0]===0xff&&bytes[1]===0xfe);
24
+ if(!utf16)return bytes.toString('utf8').replace(/^\uFEFF/,'');
25
+ return bytes.toString('utf16le').replace(/^\uFEFF/,'');
26
+ }
27
+
28
+ export function boundedRemoteDiagnostic(value,maxChars=2048){
29
+ if(!Number.isInteger(maxChars)||maxChars<32||maxChars>8192)throw new Error('remote diagnostic bound invalid');
30
+ const line=String(value??'').replace(/\x1b\[[0-9;]*[A-Za-z]/g,'').replace(/[\x00-\x1f\x7f]+/g,' ').replace(/\s{2,}/g,' ').trim();
31
+ return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
32
+ }
@@ -1,11 +1,17 @@
1
1
  import { randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
2
4
  import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
3
5
  import { verifyMachinePingPublic } from './machine-identity.mjs';
6
+ import { parseActivePointer } from './release-slot.mjs';
7
+ import { probeRemoteEnforcement } from './remote-enforcement-probe-r60.mjs';
4
8
 
5
9
  const endpointText = process.argv[2];
6
10
  if (!endpointText) throw new Error('usage: node src/remote-mcp-machine-smoke-client.mjs https://host/mcp/path');
7
11
  const endpoint = new URL(endpointText);
8
12
  if (endpoint.protocol !== 'https:') throw new Error('machine live gate requires HTTPS');
13
+ const enforcementMode=process.argv[3]??null;
14
+ if(enforcementMode!==null&&!['default','desktop','host','full','revoked'].includes(enforcementMode))throw new Error('remote enforcement probe mode invalid');
9
15
 
10
16
  const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
11
17
  const client = new Client(
@@ -49,6 +55,17 @@ try {
49
55
  }
50
56
  const signatureVerified = await verifyMachinePingPublic({ ping, publicKeySpkiBase64: status.public_key_spki_base64 });
51
57
  if (!signatureVerified) throw new Error('machine ping public Ed25519 verification failed');
58
+ if(enforcementMode!==null){
59
+ const stateRoot=process.env.DEADBYTE_STATE_ROOT;
60
+ if(!stateRoot||!path.isAbsolute(stateRoot))throw new Error('remote enforcement probe state root missing');
61
+ const pointer=parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
62
+ for(const [actual,expected] of [
63
+ [status.release_id,pointer.release_id],
64
+ [status.release_manifest_sha256,pointer.manifest_sha256],
65
+ [status.generation_manifest_sha256,pointer.generation_manifest_sha256]
66
+ ])if(actual!==expected)throw new Error('signed machine status does not bind active release/generation');
67
+ await probeRemoteEnforcement({client,mode:enforcementMode});
68
+ }
52
69
 
53
70
  console.log(JSON.stringify({
54
71
  status: 'passed',
@@ -59,7 +76,8 @@ try {
59
76
  release_id: status.release_id,
60
77
  release_manifest_sha256: status.release_manifest_sha256,
61
78
  generation_manifest_sha256: status.generation_manifest_sha256,
62
- signature_verified: true
79
+ signature_verified: true,
80
+ ...(enforcementMode?{enforcement_mode:enforcementMode,enforcement_verified:true}:{})
63
81
  }));
64
82
  } finally {
65
83
  await client.close().catch(() => {});
@@ -98,7 +98,7 @@ export async function runR40CompactParity(endpointText){
98
98
  if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
99
99
  }
100
100
  const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
101
- if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R40 compact capability manifest malformed');
101
+ if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R40 compact capability manifest malformed');
102
102
  if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
103
103
  manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R40 compact manifest catalog binding mismatch');
104
104
  const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
@@ -98,7 +98,7 @@ export async function runR44CompactParity(endpointText){
98
98
  if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
99
99
  }
100
100
  const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
101
- if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R44 compact capability manifest malformed');
101
+ if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R44 compact capability manifest malformed');
102
102
  if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
103
103
  manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R44 compact manifest catalog binding mismatch');
104
104
  const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
@@ -98,7 +98,7 @@ export async function runR45CompactParity(endpointText){
98
98
  if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
99
99
  }
100
100
  const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
101
- if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R45 compact capability manifest malformed');
101
+ if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R45 compact capability manifest malformed');
102
102
  if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
103
103
  manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R45 compact manifest catalog binding mismatch');
104
104
  const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
@@ -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');
@@ -6,8 +6,9 @@ import path from 'node:path';
6
6
  import { setTimeout as sleep } from 'node:timers/promises';
7
7
  import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
+ import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
9
10
 
10
- const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
11
+ const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
11
12
  // This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
12
13
  // poll. A reused PID cannot keep a new foreground guardian alive.
13
14
  let ownerDisconnected=false;
@@ -42,12 +43,23 @@ function markerMatches(marker,{ownerPid,nonce}){
42
43
  marker?.owner_pid===ownerPid&&
43
44
  marker?.session_nonce===nonce;
44
45
  }
45
- function invokeCleanup(powershell,launcher,command,stateRoot){
46
+ function invokeCleanup(powershell,launcher,command,stateRoot,probeMode=null){
46
47
  const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
47
- stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
48
+ stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot,
49
+ ...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})}),timeout:probeMode?150000:30000
48
50
  });
49
51
  return !result.error&&result.status===0;
50
52
  }
53
+ function verifyRevoked(powershell,launcher,stateRoot){
54
+ const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'status','-Json'],{
55
+ stdio:['ignore','pipe','pipe'],encoding:'utf8',windowsHide:true,shell:false,
56
+ env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
57
+ });
58
+ if(result.error||result.status!==0) return null;
59
+ try{
60
+ return assertAuthorityVector(parseAuthorityObservation(result.stdout),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));
61
+ }catch{return null;}
62
+ }
51
63
 
52
64
  async function main(){
53
65
  if(process.platform!=='win32') return;
@@ -82,6 +94,11 @@ async function main(){
82
94
  if(!invokeCleanup(powershell,launcher,command,root)) failed.push(command);
83
95
  }
84
96
  if(failed.length) throw new Error('guardian cleanup failed; ownership marker retained: '+failed.join(','));
97
+ const observed=verifyRevoked(powershell,launcher,root);
98
+ if(!observed) throw new Error('guardian authority cleanup unverified; ownership marker retained');
99
+ if(observed.state==='ONLINE'&&!invokeCleanup(powershell,launcher,'machinetest',root,'revoked'))
100
+ throw new Error('guardian runtime enforcement cleanup unverified; ownership marker retained');
101
+ if(!invokeCleanup(powershell,launcher,'park',root)) throw new Error('guardian park failed; ownership marker retained');
85
102
  const current=await readMarker(markerPath);
86
103
  if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
87
104
  return;
@@ -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
+
@@ -116,7 +116,12 @@ function summarizeOutcome(result,error,durationMs){
116
116
  const structured=result?.structuredContent;
117
117
  const status=typeof structured?.status==='string'?structured.status:(result?.isError===true?'error':'ok');
118
118
  const isError=result?.isError===true;
119
+ const capability=structured?.result;
120
+ const snapshot=capability?.schema==='deadbyte.capability-manifest.v3'&&HEX64.test(String(capability.snapshot_id??''))&&
121
+ Number.isInteger(capability.state_epoch)&&capability.state_epoch>0
122
+ ?{snapshot_id:capability.snapshot_id,state_epoch:capability.state_epoch}:{};
119
123
  return {status:String(status).slice(0,128),is_error:isError,duration_ms:duration,receipt_sha256s:outcomeReceiptHashes(result),
124
+ ...snapshot,
120
125
  ...(isError?{reason:resultOutcomeReason(result)}:{})};
121
126
  }
122
127
 
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.35';
1
+ export const DEADBYTE_VERSION = '0.15.37';
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
+ });