deadbyte-mcp 0.14.0 → 0.15.1

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 (157) hide show
  1. package/AGENTS.md +11 -18
  2. package/CONTEXT.md +3 -3
  3. package/MANIFEST.SHA256 +156 -70
  4. package/PROVENANCE.json +10 -0
  5. package/R43-PARENT.json +20 -0
  6. package/R44-DONORS.json +99 -0
  7. package/R44-PARENT.json +18 -0
  8. package/R45-PARENT.json +18 -0
  9. package/README.txt +19 -14
  10. package/RELEASE.json +37 -0
  11. package/SBOM.cdx.json +46 -0
  12. package/TEST-MATRIX.json +31 -0
  13. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  14. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  15. package/controller/README.TXT +5 -6
  16. package/controller/deadbyte-controller.ps1 +2 -2
  17. package/docs/ARCHITECTURE.md +8 -1
  18. package/docs/R44-ARCHITECTURE.md +37 -0
  19. package/native/deadbyte-hardware-observer.cpp +308 -0
  20. package/package.json +18 -8
  21. package/scripts/autonomous-tests.ps1 +2 -2
  22. package/scripts/build-hardware-r45.ps1 +71 -0
  23. package/scripts/deferred-slot-operation-r43.mjs +126 -0
  24. package/scripts/deferred-slot-operation-r44.mjs +126 -0
  25. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  26. package/scripts/derive-r45-parent.mjs +24 -0
  27. package/scripts/derive-r45-release.mjs +72 -0
  28. package/scripts/derive-r45-tests.mjs +20 -0
  29. package/scripts/final-closure-r43.mjs +115 -0
  30. package/scripts/final-closure-r44.mjs +121 -0
  31. package/scripts/final-closure-r45.mjs +121 -0
  32. package/scripts/final-closure-verify-r43.mjs +213 -0
  33. package/scripts/final-closure-verify-r44.mjs +220 -0
  34. package/scripts/final-closure-verify-r45.mjs +220 -0
  35. package/scripts/finalize-r43.mjs +217 -0
  36. package/scripts/finalize-r44.mjs +217 -0
  37. package/scripts/finalize-r45.mjs +219 -0
  38. package/scripts/full-regression-r43.mjs +20 -0
  39. package/scripts/full-regression-r44.mjs +20 -0
  40. package/scripts/full-regression-r45.mjs +20 -0
  41. package/scripts/full-regression-summary-r45.mjs +14 -0
  42. package/scripts/gate-windows-r43.ps1 +36 -0
  43. package/scripts/gate-windows-r44.ps1 +36 -0
  44. package/scripts/gate-windows-r45.ps1 +44 -0
  45. package/scripts/init-host-policy.ps1 +39 -1
  46. package/scripts/inspect-active-release-r45.mjs +20 -0
  47. package/scripts/release-manifest.mjs +3 -1
  48. package/scripts/release-parity-r43.mjs +218 -0
  49. package/scripts/release-parity-r44.mjs +242 -0
  50. package/scripts/release-parity-r45.mjs +253 -0
  51. package/scripts/release-parity-tests-r43.ps1 +8 -0
  52. package/scripts/release-parity-tests-r44.ps1 +8 -0
  53. package/scripts/release-parity-tests-r45.ps1 +8 -0
  54. package/scripts/verify-r43-parent.mjs +126 -0
  55. package/scripts/verify-r44-parent.mjs +142 -0
  56. package/scripts/verify-r45-parent.mjs +142 -0
  57. package/scripts/windows-gate-evidence-r43.mjs +55 -0
  58. package/scripts/windows-gate-evidence-r44.mjs +55 -0
  59. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  60. package/src/agent-backend-r44.mjs +254 -0
  61. package/src/autonomous-output-contracts.mjs +1 -1
  62. package/src/autonomous-policy.mjs +41 -4
  63. package/src/autonomous-runtime.mjs +37 -19
  64. package/src/autonomous-supervisor-host.mjs +12 -2
  65. package/src/capability-ledger.mjs +3 -1
  66. package/src/codex-app-server-r44.mjs +154 -0
  67. package/src/coding-plane.mjs +66 -19
  68. package/src/compact-gateway-mcp-tools.mjs +4 -2
  69. package/src/deadbyte-cli.mjs +17 -9
  70. package/src/doctor-r45.mjs +65 -0
  71. package/src/durable-job-r44.mjs +145 -0
  72. package/src/external-job-provider-r44.mjs +126 -0
  73. package/src/host-plane.mjs +156 -87
  74. package/src/job-supervisor-host-r44.mjs +112 -0
  75. package/src/machine-fs-smoke-client.mjs +1 -1
  76. package/src/machine-fs.mjs +21 -10
  77. package/src/mcp-host-smoke-client.mjs +1 -1
  78. package/src/mcp-server.mjs +84 -26
  79. package/src/mcp-smoke-client.mjs +1 -1
  80. package/src/native-diff-v2.mjs +12 -7
  81. package/src/native-diff.mjs +32 -9
  82. package/src/observation-mcp-tools.mjs +5 -1
  83. package/src/observation-output-contracts.mjs +8 -0
  84. package/src/observation-runtime.mjs +33 -2
  85. package/src/observation-smoke-client.mjs +7 -3
  86. package/src/operator-surface.mjs +2 -2
  87. package/src/output-contracts.mjs +27 -11
  88. package/src/process-policy.mjs +19 -0
  89. package/src/process-runtime.mjs +5 -2
  90. package/src/process-smoke-client.mjs +2 -2
  91. package/src/provider-circuit.mjs +8 -2
  92. package/src/r44-compact-disarmed-probe.mjs +74 -0
  93. package/src/r44-full-surface-probe.mjs +34 -0
  94. package/src/r45-compact-disarmed-probe.mjs +74 -0
  95. package/src/r45-full-surface-probe.mjs +34 -0
  96. package/src/release-generation.mjs +1 -0
  97. package/src/remote-device-r44.mjs +205 -0
  98. package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
  99. package/src/remote-mcp-r44-parity-client.mjs +203 -0
  100. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  101. package/src/remote-mcp-smoke-client.mjs +7 -4
  102. package/src/runtime-update.mjs +100 -39
  103. package/src/skill-mcp-tools-r44.mjs +37 -0
  104. package/src/skill-plane-r44.mjs +203 -0
  105. package/src/tunnel-stdio-compat-smoke.mjs +1 -1
  106. package/src/version.mjs +1 -1
  107. package/test/agent-backend-r44.test.mjs +115 -0
  108. package/test/agent-runtime.test.mjs +4 -3
  109. package/test/autonomous-loop.test.mjs +121 -11
  110. package/test/autonomous-runtime.test.mjs +3 -2
  111. package/test/capability-ledger-r36.test.mjs +3 -3
  112. package/test/cli-entrypoint.test.mjs +4 -4
  113. package/test/codex-app-server-r44.test.mjs +115 -0
  114. package/test/codex-autonomous-r44.test.mjs +87 -0
  115. package/test/coding-plane.test.mjs +3 -2
  116. package/test/coding-runtime.test.mjs +36 -1
  117. package/test/compact-catalog-r36.test.mjs +3 -3
  118. package/test/compact-max-catalog-r40.test.mjs +4 -2
  119. package/test/compact-max-catalog-r44.test.mjs +97 -0
  120. package/test/compact-max-catalog-r45.test.mjs +99 -0
  121. package/test/contained.test.mjs +23 -11
  122. package/test/controller.test.mjs +5 -1
  123. package/test/core.test.mjs +1 -1
  124. package/test/durable-job-r44.test.mjs +75 -0
  125. package/test/external-job-provider-r44.test.mjs +46 -0
  126. package/test/full-max-catalog-r40.test.mjs +5 -3
  127. package/test/full-max-catalog-r44.test.mjs +91 -0
  128. package/test/full-max-catalog-r45.test.mjs +91 -0
  129. package/test/hardware-observation-r45.test.mjs +44 -0
  130. package/test/host-inline-r45.test.mjs +67 -0
  131. package/test/host-plane.test.mjs +15 -1
  132. package/test/machine-mcp-integration.test.mjs +1 -0
  133. package/test/native-diff.test.mjs +28 -0
  134. package/test/observation-mcp-tools.test.mjs +4 -3
  135. package/test/output-schema.test.mjs +3 -2
  136. package/test/package-boundary.test.mjs +1 -1
  137. package/test/process-policy.test.mjs +22 -1
  138. package/test/production-docs.test.mjs +3 -3
  139. package/test/prompts-resources.test.mjs +1 -1
  140. package/test/r33-closeout-regression.test.mjs +20 -36
  141. package/test/r33-finalization.test.mjs +7 -7
  142. package/test/r43-finalization.test.mjs +31 -0
  143. package/test/r43-finalizer.test.mjs +34 -0
  144. package/test/r43-parent.test.mjs +29 -0
  145. package/test/r44-finalization.test.mjs +58 -0
  146. package/test/r44-finalizer.test.mjs +48 -0
  147. package/test/r44-parent.test.mjs +25 -0
  148. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  149. package/test/r45-finalization.test.mjs +84 -0
  150. package/test/release-generation.test.mjs +1 -0
  151. package/test/release-version.test.mjs +65 -20
  152. package/test/remote-device-r44.test.mjs +104 -0
  153. package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
  154. package/test/runtime-self-update.test.mjs +42 -0
  155. package/test/skill-mcp-r44.test.mjs +38 -0
  156. package/test/skill-plane-r44.test.mjs +93 -0
  157. package/test/standing-grants.test.mjs +5 -1
@@ -55,7 +55,7 @@ try {
55
55
  await client.connect(transport);
56
56
  const listed = await client.listTools();
57
57
  const names = listed.tools.map(tool => tool.name).sort();
58
- const expected = ['contained_transform','evidence_verify','file_read','proof_execute','workspace_open',
58
+ const expected = ['contained_transform','evidence_verify','file_read','proof_execute','skill_catalog','workspace_open',
59
59
  'process_session_action','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate'].sort();
60
60
  if (JSON.stringify(names) !== JSON.stringify(expected)) throw new Error(`process smoke catalog mismatch: ${JSON.stringify(names)}`);
61
61
  const started = structured(await client.callTool({ name: 'process_session_start', arguments: {
@@ -114,7 +114,7 @@ try {
114
114
  }
115
115
  if (sequence < 3 || previous !== finalHead) throw new Error('process journal head mismatch');
116
116
  if ((await readFile(journalPath, 'utf8')).includes('DEADBYTE_ACTION_PING')) throw new Error('process journal leaked action payload');
117
- console.log(JSON.stringify({ status: 'passed', protocol_revision: '2026-07-28', tool_count: 11,
117
+ console.log(JSON.stringify({ status: 'passed', protocol_revision: '2026-07-28', tool_count: 12,
118
118
  profile_id: 'ping_hold', session_id: sessionId, observed_output_bytes: read.stream_head,
119
119
  action_id: action.action_id, logical_bytes: action.logical_bytes, logical_sha256: action.logical_sha256,
120
120
  final_state: terminated.session.state, event_count: sequence, journal_head_sha256: previous,
@@ -48,10 +48,16 @@ function openAt(state,ms,policy){
48
48
  }
49
49
 
50
50
  export function providerCircuitId(planner){
51
- if(!['openai_compatible','openai_responses'].includes(planner?.backend)||typeof planner.endpoint!=='string'||typeof planner.model!=='string') {
51
+ if(!planner||typeof planner.model!=='string') throw new Error('provider circuit planner identity invalid');
52
+ let identity;
53
+ if(['openai_compatible','openai_responses'].includes(planner.backend)&&typeof planner.endpoint==='string') {
54
+ identity=`${planner.backend}\0${planner.endpoint}\0${planner.model}`;
55
+ } else if(planner.backend==='codex_app_server'&&HEX64.test(planner.executable_sha256??'')&&typeof planner.root_id==='string') {
56
+ identity=`${planner.backend}\0${planner.executable_sha256}\0${planner.model}\0${planner.root_id}`;
57
+ } else {
52
58
  throw new Error('provider circuit planner identity invalid');
53
59
  }
54
- const digest=createHash('sha256').update(Buffer.from(`${planner.backend}\0${planner.endpoint}\0${planner.model}`,'utf8')).digest('hex');
60
+ const digest=createHash('sha256').update(Buffer.from(identity,'utf8')).digest('hex');
55
61
  return `${planner.backend}:${digest}`;
56
62
  }
57
63
 
@@ -0,0 +1,74 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
7
+ import { R44_COMPACT_GATEWAYS,R44_COMPACT_TOOL_LIMIT } from './remote-mcp-r44-parity-client.mjs';
8
+ import { loadDesktopPolicy } from './desktop-policy-r40.mjs';
9
+ import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
10
+ import { loadPublicVerifyKey } from './trust.mjs';
11
+
12
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
13
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
14
+ const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
15
+ function structured(result,label){
16
+ if(result.isError) throw new Error(label+' returned error: '+JSON.stringify(result.structuredContent??result.content));
17
+ if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(label+' missing structured output');
18
+ return result.structuredContent;
19
+ }
20
+ function gateway(result,label){
21
+ const value=structured(result,label);
22
+ if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(label+' gateway status='+String(value.status));
23
+ return value.result;
24
+ }
25
+ async function verifyDesktop(policy,verifyKey,id,sha,kind){
26
+ const receipt=JSON.parse(await readFile(path.join(policy.evidence_dir,id+'.json'),'utf8'));
27
+ const proof=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:verifyKey.publicKey,keyId:verifyKey.keyId});
28
+ if(!proof.ok||proof.receipt_sha256!==sha||proof.kind!==kind) throw new Error('R44 desktop '+kind+' receipt verification failed');
29
+ }
30
+ export async function probeR44CompactDisarmed(endpointText){
31
+ const endpoint=new URL(endpointText);if(endpoint.protocol!=='https:')throw new Error('R44 compact disarmed probe requires HTTPS');
32
+ const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
33
+ const desktopPolicy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
34
+ const verifyKey=await loadPublicVerifyKey(verifyKeyPath);
35
+ const client=new Client({name:'deadbyte-r44-compact-disarmed-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
36
+ const transport=new StreamableHTTPClientTransport(endpoint);
37
+ try{
38
+ await client.connect(transport);
39
+ const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
40
+ if(names.length!==R44_COMPACT_TOOL_LIMIT)throw new Error('R44 compact disarmed catalog must be exactly '+R44_COMPACT_TOOL_LIMIT+', got '+names.length);
41
+ for(const name of R44_COMPACT_GATEWAYS)if(!names.includes(name))throw new Error('R44 compact gateway missing '+name);
42
+ for(const tool of listed.tools)if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties)throw new Error('tool '+tool.name+' missing strict output schema');
43
+ const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
44
+ if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||manifest.tool_count!==names.length||
45
+ JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names))throw new Error('R44 compact manifest malformed');
46
+ const required=manifest.capabilities.filter(x=>x?.production===true&&x?.host_only!==true);
47
+ for(const item of required)if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool))throw new Error('compact route missing '+String(item.id));
48
+ const skillCatalog=structured(await client.callTool({name:'skill_catalog',arguments:{op:'list'}}),'skill_catalog');
49
+ if(skillCatalog.status!=='ok'||skillCatalog.op!=='list'||!skillCatalog.result||!Array.isArray(skillCatalog.result.skills)||
50
+ !/^[0-9a-f]{64}$/.test(String(skillCatalog.result.registry_sha256??'')))throw new Error('R44 skill catalog verification failed');
51
+ const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding status');
52
+ const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous status');
53
+ const desktop=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
54
+ if(coding.armed!==false||autonomous.coding_armed!==false||autonomous.autonomous_armed!==false||desktop.armed!==false)throw new Error('R44 closure requires Coding/Autonomous/Desktop DISARMED');
55
+ const observed=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
56
+ await verifyDesktop(desktopPolicy,verifyKey,observed.receipt_id,observed.receipt_sha256,'desktop_observation');
57
+ const grounded=gateway(await client.callTool({name:'desktop_control',arguments:{op:'target',frame_id:observed.frame.frame_id,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}}),'desktop target');
58
+ await verifyDesktop(desktopPolicy,verifyKey,grounded.grounding.receipt_id,grounded.grounding.receipt_sha256,'desktop_grounding');
59
+ const machine=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
60
+ const nonce='r44-final-'+randomBytes(16).toString('hex');
61
+ const ping=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}),'machine ping');
62
+ if(ping.nonce!==nonce||ping.release_id!==machine.release_id||ping.release_manifest_sha256!==machine.release_manifest_sha256)throw new Error('R44 machine ping binding mismatch');
63
+ return {status:'passed',profile:'compact',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
64
+ strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,skill_catalog_verified:true,skill_count:skillCatalog.result.skills.length,capability_count:manifest.capabilities.length,
65
+ production_non_host_capabilities:required.length,capability_closure:true,coding_armed:false,autonomous_armed:false,desktop_armed:false,
66
+ desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
67
+ desktop_frame_id:observed.frame.frame_id,desktop_target_id:grounded.target.target_id,
68
+ release_id:machine.release_id,release_manifest_sha256:machine.release_manifest_sha256,machine_status:machine,machine_ping:ping,nonce,
69
+ protocol_revision:client.getNegotiatedProtocolVersion()};
70
+ }finally{await client.close().catch(()=>{});}
71
+ }
72
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
73
+ if(invoked){const endpoint=process.argv[2];if(!endpoint)throw new Error('usage: node src/r44-compact-disarmed-probe.mjs https://host/mcp/path');
74
+ probeR44CompactDisarmed(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
@@ -0,0 +1,34 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { Client } from '@modelcontextprotocol/client';
6
+ import { StdioClientTransport,getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { R44_EXPECTED_TOOLS } from './remote-mcp-r44-parity-client.mjs';
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ export async function probeR44FullSurface({packageRoot,generationRoot,stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
11
+ const pkgRoot=packageRoot?path.resolve(packageRoot):path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
12
+ const gen=path.resolve(generationRoot),semanticPath=path.join(gen,'semantic-lsp-config.json');
13
+ const semanticSha=sha256(await readFile(semanticPath));
14
+ const env={...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:path.resolve(stateRoot),DEADBYTE_CORE_ROOT:pkgRoot,
15
+ DEADBYTE_WORKSPACE_ROOT:path.join(pkgRoot,'workspaces'),DEADBYTE_CAPABILITY_PROFILE:'full',DEADBYTE_AUDIT_RUNTIME:'0',
16
+ DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'1',DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
17
+ DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_MACHINE_FS_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1',
18
+ DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:path.join(gen,'desktop-policy.json'),
19
+ DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
20
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
21
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external',DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')};
22
+ const client=new Client({name:'deadbyte-r44-full-surface-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
23
+ const transport=new StdioClientTransport({command:process.execPath,args:[path.join(pkgRoot,'src','mcp-server.mjs')],cwd:pkgRoot,env,stderr:'pipe'});
24
+ try{
25
+ await client.connect(transport);const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
26
+ if(JSON.stringify(names)!==JSON.stringify(R44_EXPECTED_TOOLS))throw new Error('R44 full catalog mismatch count='+names.length);
27
+ for(const tool of listed.tools)if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties)throw new Error('R44 full tool '+tool.name+' missing strict output schema');
28
+ return {status:'passed',profile:'full',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
29
+ strict_output_schemas:true,desktop_tools:['desktop_status','desktop_observe','desktop_target','desktop_action'],protocol_revision:client.getNegotiatedProtocolVersion()};
30
+ }finally{await client.close().catch(()=>{});}
31
+ }
32
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){const [packageRoot,generationRoot]=process.argv.slice(2);
33
+ if(!packageRoot||!generationRoot)throw new Error('usage: node src/r44-full-surface-probe.mjs <package-root> <generation-root>');
34
+ probeR44FullSurface({packageRoot,generationRoot}).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
@@ -0,0 +1,74 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
7
+ import { R45_COMPACT_GATEWAYS,R45_COMPACT_TOOL_LIMIT } from './remote-mcp-r45-parity-client.mjs';
8
+ import { loadDesktopPolicy } from './desktop-policy-r40.mjs';
9
+ import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
10
+ import { loadPublicVerifyKey } from './trust.mjs';
11
+
12
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
13
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
14
+ const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
15
+ function structured(result,label){
16
+ if(result.isError) throw new Error(label+' returned error: '+JSON.stringify(result.structuredContent??result.content));
17
+ if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(label+' missing structured output');
18
+ return result.structuredContent;
19
+ }
20
+ function gateway(result,label){
21
+ const value=structured(result,label);
22
+ if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(label+' gateway status='+String(value.status));
23
+ return value.result;
24
+ }
25
+ async function verifyDesktop(policy,verifyKey,id,sha,kind){
26
+ const receipt=JSON.parse(await readFile(path.join(policy.evidence_dir,id+'.json'),'utf8'));
27
+ const proof=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:verifyKey.publicKey,keyId:verifyKey.keyId});
28
+ if(!proof.ok||proof.receipt_sha256!==sha||proof.kind!==kind) throw new Error('R45 desktop '+kind+' receipt verification failed');
29
+ }
30
+ export async function probeR45CompactDisarmed(endpointText){
31
+ const endpoint=new URL(endpointText);if(endpoint.protocol!=='https:')throw new Error('R45 compact disarmed probe requires HTTPS');
32
+ const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
33
+ const desktopPolicy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
34
+ const verifyKey=await loadPublicVerifyKey(verifyKeyPath);
35
+ const client=new Client({name:'deadbyte-r45-compact-disarmed-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
36
+ const transport=new StreamableHTTPClientTransport(endpoint);
37
+ try{
38
+ await client.connect(transport);
39
+ const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
40
+ if(names.length!==R45_COMPACT_TOOL_LIMIT)throw new Error('R45 compact disarmed catalog must be exactly '+R45_COMPACT_TOOL_LIMIT+', got '+names.length);
41
+ for(const name of R45_COMPACT_GATEWAYS)if(!names.includes(name))throw new Error('R45 compact gateway missing '+name);
42
+ for(const tool of listed.tools)if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties)throw new Error('tool '+tool.name+' missing strict output schema');
43
+ const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
44
+ if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||manifest.tool_count!==names.length||
45
+ JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names))throw new Error('R45 compact manifest malformed');
46
+ const required=manifest.capabilities.filter(x=>x?.production===true&&x?.host_only!==true);
47
+ for(const item of required)if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool))throw new Error('compact route missing '+String(item.id));
48
+ const skillCatalog=structured(await client.callTool({name:'skill_catalog',arguments:{op:'list'}}),'skill_catalog');
49
+ if(skillCatalog.status!=='ok'||skillCatalog.op!=='list'||!skillCatalog.result||!Array.isArray(skillCatalog.result.skills)||
50
+ !/^[0-9a-f]{64}$/.test(String(skillCatalog.result.registry_sha256??'')))throw new Error('R45 skill catalog verification failed');
51
+ const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding status');
52
+ const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous status');
53
+ const desktop=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
54
+ if(coding.armed!==false||autonomous.coding_armed!==false||autonomous.autonomous_armed!==false||desktop.armed!==false)throw new Error('R45 closure requires Coding/Autonomous/Desktop DISARMED');
55
+ const observed=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
56
+ await verifyDesktop(desktopPolicy,verifyKey,observed.receipt_id,observed.receipt_sha256,'desktop_observation');
57
+ const grounded=gateway(await client.callTool({name:'desktop_control',arguments:{op:'target',frame_id:observed.frame.frame_id,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}}),'desktop target');
58
+ await verifyDesktop(desktopPolicy,verifyKey,grounded.grounding.receipt_id,grounded.grounding.receipt_sha256,'desktop_grounding');
59
+ const machine=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
60
+ const nonce='r45-final-'+randomBytes(16).toString('hex');
61
+ const ping=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}),'machine ping');
62
+ if(ping.nonce!==nonce||ping.release_id!==machine.release_id||ping.release_manifest_sha256!==machine.release_manifest_sha256)throw new Error('R45 machine ping binding mismatch');
63
+ return {status:'passed',profile:'compact',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
64
+ strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,skill_catalog_verified:true,skill_count:skillCatalog.result.skills.length,capability_count:manifest.capabilities.length,
65
+ production_non_host_capabilities:required.length,capability_closure:true,coding_armed:false,autonomous_armed:false,desktop_armed:false,
66
+ desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
67
+ desktop_frame_id:observed.frame.frame_id,desktop_target_id:grounded.target.target_id,
68
+ release_id:machine.release_id,release_manifest_sha256:machine.release_manifest_sha256,machine_status:machine,machine_ping:ping,nonce,
69
+ protocol_revision:client.getNegotiatedProtocolVersion()};
70
+ }finally{await client.close().catch(()=>{});}
71
+ }
72
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
73
+ if(invoked){const endpoint=process.argv[2];if(!endpoint)throw new Error('usage: node src/r45-compact-disarmed-probe.mjs https://host/mcp/path');
74
+ probeR45CompactDisarmed(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
@@ -0,0 +1,34 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { Client } from '@modelcontextprotocol/client';
6
+ import { StdioClientTransport,getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { R45_EXPECTED_TOOLS } from './remote-mcp-r45-parity-client.mjs';
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ export async function probeR45FullSurface({packageRoot,generationRoot,stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
11
+ const pkgRoot=packageRoot?path.resolve(packageRoot):path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
12
+ const gen=path.resolve(generationRoot),semanticPath=path.join(gen,'semantic-lsp-config.json');
13
+ const semanticSha=sha256(await readFile(semanticPath));
14
+ const env={...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:path.resolve(stateRoot),DEADBYTE_CORE_ROOT:pkgRoot,
15
+ DEADBYTE_WORKSPACE_ROOT:path.join(pkgRoot,'workspaces'),DEADBYTE_CAPABILITY_PROFILE:'full',DEADBYTE_AUDIT_RUNTIME:'0',
16
+ DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'1',DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
17
+ DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_MACHINE_FS_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1',
18
+ DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:path.join(gen,'desktop-policy.json'),
19
+ DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
20
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
21
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external',DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')};
22
+ const client=new Client({name:'deadbyte-r45-full-surface-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
23
+ const transport=new StdioClientTransport({command:process.execPath,args:[path.join(pkgRoot,'src','mcp-server.mjs')],cwd:pkgRoot,env,stderr:'pipe'});
24
+ try{
25
+ await client.connect(transport);const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
26
+ if(JSON.stringify(names)!==JSON.stringify(R45_EXPECTED_TOOLS))throw new Error('R45 full catalog mismatch count='+names.length);
27
+ for(const tool of listed.tools)if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties)throw new Error('R45 full tool '+tool.name+' missing strict output schema');
28
+ return {status:'passed',profile:'full',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
29
+ strict_output_schemas:true,desktop_tools:['desktop_status','desktop_observe','desktop_target','desktop_action'],protocol_revision:client.getNegotiatedProtocolVersion()};
30
+ }finally{await client.close().catch(()=>{});}
31
+ }
32
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){const [packageRoot,generationRoot]=process.argv.slice(2);
33
+ if(!packageRoot||!generationRoot)throw new Error('usage: node src/r45-full-surface-probe.mjs <package-root> <generation-root>');
34
+ probeR45FullSurface({packageRoot,generationRoot}).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
@@ -84,6 +84,7 @@ async function rebindAutonomousPolicy(raw,{stateRoot,codingPolicy,codingPolicyBy
84
84
  if(id==='windows-release'){
85
85
  if(profile.kind!=='command'||rootId!=='core'||profile.command_id!=='powershell_script') throw new Error('windows release profile shape invalid');
86
86
  profile.script_path=await currentWindowsGateScript(rootPath);
87
+ profile.timeout_ms=600000;
87
88
  profile.description='Full Windows target release gate; runs only when finish is requested.';
88
89
  }
89
90
  const rel=safeRel(String(profile.script_path).replaceAll('\\','/'));
@@ -0,0 +1,205 @@
1
+ import { createHash, createPublicKey, randomBytes } from 'node:crypto';
2
+ import { lstat, mkdir, open, readFile, realpath, rename } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const STATE_SCHEMA='deadbyte.remote-device-state.v1';
6
+ const CREDENTIAL_SCHEMA='deadbyte.remote-credential.v1';
7
+ const HEX64=/^[0-9a-f]{64}$/;
8
+ const CAP=/^[A-Za-z0-9._-]{1,128}$/;
9
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
11
+ ?Object.fromEntries(Object.keys(value).sort((a,b)=>a.localeCompare(b,'en')).map(k=>[k,canonical(value[k])])):value;
12
+ const canonicalJson=value=>JSON.stringify(canonical(value));
13
+ function insist(ok,message){if(!ok)throw new Error(message);}
14
+ function now(){return new Date().toISOString();}
15
+ async function atomicJson(file,value){
16
+ await mkdir(path.dirname(file),{recursive:true});
17
+ const bytes=Buffer.from(`${JSON.stringify(value,null,2)}\n`,'utf8');
18
+ const temp=`${file}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
19
+ const h=await open(temp,'wx');
20
+ try{await h.writeFile(bytes);await h.sync();}finally{await h.close();}
21
+ await rename(temp,file);
22
+ return sha(bytes);
23
+ }
24
+ function validateState(value){
25
+ insist(value&&typeof value==='object'&&!Array.isArray(value),'remote state must be object');
26
+ insist(value.schema===STATE_SCHEMA,'remote state schema mismatch');
27
+ insist(value.devices&&typeof value.devices==='object'&&!Array.isArray(value.devices),'remote devices invalid');
28
+ insist(value.requests&&typeof value.requests==='object'&&!Array.isArray(value.requests),'remote requests invalid');
29
+ for(const [id,d] of Object.entries(value.devices)){
30
+ insist(HEX64.test(id)&&d.device_id===id,'remote device identity invalid');
31
+ insist(Number.isInteger(d.epoch)&&d.epoch>=1,'remote device epoch invalid');
32
+ insist(typeof d.public_key_spki_base64==='string','remote device SPKI invalid');
33
+ insist(typeof d.revoked==='boolean'&&typeof d.reported_online==='boolean'&&typeof d.reachable==='boolean','remote device state flags invalid');
34
+ }
35
+ for(const [id,r] of Object.entries(value.requests)){
36
+ insist(HEX64.test(id)&&r.request_id===id,'remote request identity invalid');
37
+ insist(HEX64.test(r.request_sha256??''),'remote request hash invalid');
38
+ insist(['accepted','executing','completed','failed'].includes(r.state),'remote request state invalid');
39
+ }
40
+ return value;
41
+ }
42
+ async function loadState(file){
43
+ try{
44
+ const st=await lstat(file);insist(st.isFile()&&!st.isSymbolicLink(),'remote state must be real file');
45
+ let parsed;try{parsed=JSON.parse(await readFile(file,'utf8'));}catch(error){throw new Error(`remote state JSON invalid: ${error.message}`);}
46
+ return validateState(parsed);
47
+ }catch(error){
48
+ if(error?.code!=='ENOENT')throw error;
49
+ return {schema:STATE_SCHEMA,devices:{},requests:{},updated_at_utc:now()};
50
+ }
51
+ }
52
+ function parseDeviceKey(spki){
53
+ insist(typeof spki==='string'&&spki.length>=1&&spki.length<=4096,'device SPKI invalid');
54
+ let der;try{der=Buffer.from(spki,'base64');}catch{throw new Error('device SPKI base64 invalid');}
55
+ insist(der.length>0&&der.toString('base64')===spki,'device SPKI base64 non-canonical');
56
+ const key=createPublicKey({key:der,format:'der',type:'spki'});
57
+ insist(key.asymmetricKeyType==='ed25519','remote device key must be Ed25519');
58
+ return {deviceId:sha(der),spki};
59
+ }
60
+ function publicDevice(d){
61
+ return {device_id:d.device_id,display_name:d.display_name,epoch:d.epoch,revoked:d.revoked,
62
+ reported_online:d.reported_online,reachable:d.reachable,connection_id:d.connection_id,updated_at_utc:d.updated_at_utc};
63
+ }
64
+ function payloadHash({deviceId,epoch,capabilityId,payload}){
65
+ return sha(Buffer.from(canonicalJson({device_id:deviceId,epoch,capability_id:capabilityId,payload}),'utf8'));
66
+ }
67
+
68
+ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapability,onCredentialSuperseded=null}={}){
69
+ insist(typeof stateDir==='string'&&path.isAbsolute(stateDir),'remote stateDir must be absolute');
70
+ insist(relay&&typeof relay.probe==='function'&&typeof relay.request==='function','remote relay contract invalid');
71
+ insist(typeof authorizeCapability==='function','remote capability authorizer required');
72
+ insist(onCredentialSuperseded===null||typeof onCredentialSuperseded==='function','remote credential supersede callback invalid');
73
+ await mkdir(stateDir,{recursive:true});const root=await realpath(stateDir);
74
+ const stateFile=path.join(root,'remote-devices.json'),credentialsDir=path.join(root,'credentials');
75
+ await mkdir(credentialsDir,{recursive:true});
76
+ let state=await loadState(stateFile);
77
+ let lock=Promise.resolve();
78
+ const withLock=fn=>{
79
+ const run=lock.then(fn,fn);lock=run.catch(()=>{});return run;
80
+ };
81
+ const persist=()=>{state.updated_at_utc=now();return atomicJson(stateFile,state);};
82
+ const getDevice=(deviceId,epoch=null)=>{
83
+ insist(HEX64.test(deviceId??''),'device id invalid');
84
+ const d=state.devices[deviceId];insist(d,'unknown remote device');
85
+ if(epoch!==null)insist(d.epoch===epoch,'remote device epoch mismatch');
86
+ insist(!d.revoked,'remote device revoked');
87
+ return d;
88
+ };
89
+
90
+ async function pair({publicKeySpkiBase64,displayName}={}){
91
+ const {deviceId,spki}=parseDeviceKey(publicKeySpkiBase64);
92
+ insist(typeof displayName==='string'&&displayName.length>=1&&displayName.length<=128,'device display name invalid');
93
+ return withLock(async()=>{
94
+ const existing=state.devices[deviceId];
95
+ if(existing){
96
+ insist(!existing.revoked,'remote device revoked');
97
+ insist(existing.public_key_spki_base64===spki,'remote device key drift');
98
+ return publicDevice(existing);
99
+ }
100
+ const d={device_id:deviceId,public_key_spki_base64:spki,display_name:displayName,epoch:1,revoked:false,
101
+ reported_online:false,reachable:false,connection_id:null,last_probe_nonce:null,updated_at_utc:now()};
102
+ state.devices[deviceId]=d;await persist();return publicDevice(d);
103
+ });
104
+ }
105
+ async function status({deviceId}={}){
106
+ const d=getDevice(deviceId);return publicDevice(d);
107
+ }
108
+ async function reportOnline({deviceId,epoch,connectionId}={}){
109
+ insist(typeof connectionId==='string'&&connectionId.length>=1&&connectionId.length<=256,'connection id invalid');
110
+ return withLock(async()=>{
111
+ const d=getDevice(deviceId,epoch);d.reported_online=true;d.reachable=false;d.connection_id=connectionId;
112
+ d.last_probe_nonce=null;d.updated_at_utc=now();await persist();return publicDevice(d);
113
+ });
114
+ }
115
+ async function probe({deviceId,epoch}={}){
116
+ const nonce=randomBytes(32).toString('hex');
117
+ let connectionId;
118
+ await withLock(async()=>{
119
+ const d=getDevice(deviceId,epoch);insist(d.reported_online&&d.connection_id,'remote device is not reported online');
120
+ connectionId=d.connection_id;d.reachable=false;d.last_probe_nonce=nonce;d.updated_at_utc=now();await persist();
121
+ });
122
+ const response=await relay.probe({device_id:deviceId,epoch,connection_id:connectionId,nonce});
123
+ return withLock(async()=>{
124
+ const d=getDevice(deviceId,epoch);
125
+ insist(response&&response.device_id===deviceId&&response.epoch===epoch&&response.nonce===nonce&&response.ready===true,'remote readiness probe acknowledgment invalid');
126
+ insist(d.connection_id===connectionId&&d.last_probe_nonce===nonce,'remote connection changed during readiness probe');
127
+ d.reachable=true;d.updated_at_utc=now();await persist();return {...publicDevice(d),probe_nonce:nonce};
128
+ });
129
+ }
130
+ async function dispatch({deviceId,epoch,capabilityId,payload={},requestId}={}){
131
+ insist(HEX64.test(requestId??''),'remote request id invalid');
132
+ insist(typeof capabilityId==='string'&&CAP.test(capabilityId),'remote capability id invalid');
133
+ insist(payload&&typeof payload==='object','remote payload must be JSON object');
134
+ let payloadJson;try{payloadJson=canonicalJson(payload);}catch{throw new Error('remote payload is not JSON serializable');}
135
+ const requestSha=payloadHash({deviceId,epoch,capabilityId,payload});
136
+ const existing=state.requests[requestId];
137
+ if(existing){
138
+ insist(existing.request_sha256===requestSha,'remote request id conflict');
139
+ return {...structuredClone(existing),replay:true};
140
+ }
141
+ const allowed=await authorizeCapability({deviceId,epoch,capabilityId});
142
+ insist(allowed===true,'remote capability denied by local policy');
143
+ await withLock(async()=>{
144
+ const d=getDevice(deviceId,epoch);insist(d.reported_online&&d.reachable,'remote device not reachable');
145
+ const again=state.requests[requestId];
146
+ if(again){insist(again.request_sha256===requestSha,'remote request id conflict');return;}
147
+ state.requests[requestId]={request_id:requestId,request_sha256:requestSha,device_id:deviceId,epoch,capability_id:capabilityId,
148
+ payload_sha256:sha(Buffer.from(payloadJson,'utf8')),state:'accepted',created_at_utc:now(),updated_at_utc:now()};
149
+ await persist();
150
+ });
151
+ if(state.requests[requestId].state!=='accepted')return {...structuredClone(state.requests[requestId]),replay:true};
152
+ let response=null;
153
+ try{response=await relay.request({device_id:deviceId,epoch,capability_id:capabilityId,payload,request_id:requestId,request_sha256:requestSha});}
154
+ catch(error){
155
+ return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_error';rec.error=String(error?.message??error);rec.updated_at_utc=now();await persist();return structuredClone(rec);});
156
+ }
157
+ if(!response||response.ack!=='accepted'||response.request_id!==requestId){
158
+ return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_ack';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
159
+ }
160
+ await withLock(async()=>{const rec=state.requests[requestId];rec.state='executing';rec.acknowledged_at_utc=now();rec.updated_at_utc=now();await persist();});
161
+ const terminal=response.terminal;
162
+ if(!terminal||!['completed','failed'].includes(terminal.status)){
163
+ return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_terminal';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
164
+ }
165
+ return withLock(async()=>{
166
+ const rec=state.requests[requestId];rec.state=terminal.status;
167
+ if(terminal.status==='completed'){insist(HEX64.test(terminal.result_sha256??''),'remote terminal result hash invalid');rec.result_sha256=terminal.result_sha256;}
168
+ else {rec.error_code=typeof terminal.error_code==='string'?terminal.error_code:'remote_failed';}
169
+ rec.updated_at_utc=now();rec.completed_at_utc=now();await persist();return structuredClone(rec);
170
+ });
171
+ }
172
+ async function revoke({deviceId,epoch}={}){
173
+ return withLock(async()=>{
174
+ const d=getDevice(deviceId,epoch);d.revoked=true;d.reported_online=false;d.reachable=false;d.connection_id=null;d.updated_at_utc=now();await persist();
175
+ return {...publicDevice(d),revoked:true};
176
+ });
177
+ }
178
+ async function rotateCredential({deviceId,epoch,encryptedBundleBase64,expectedPreviousSha256}={}){
179
+ const d=getDevice(deviceId,epoch);void d;
180
+ insist(typeof encryptedBundleBase64==='string'&&encryptedBundleBase64.length>=1&&encryptedBundleBase64.length<=1024*1024,'encrypted credential bundle invalid');
181
+ const encrypted=Buffer.from(encryptedBundleBase64,'base64');
182
+ insist(encrypted.length>0&&encrypted.toString('base64')===encryptedBundleBase64,'encrypted credential base64 non-canonical');
183
+ const credentialSha=sha(encrypted),file=path.join(credentialsDir,`${deviceId}.json`);
184
+ let previous=null;
185
+ try{
186
+ const st=await lstat(file);insist(st.isFile()&&!st.isSymbolicLink(),'credential state must be real file');
187
+ previous=JSON.parse(await readFile(file,'utf8'));
188
+ insist(previous.schema===CREDENTIAL_SCHEMA&&previous.device_id===deviceId,'credential state identity invalid');
189
+ }catch(error){if(error?.code!=='ENOENT')throw error;}
190
+ if(expectedPreviousSha256===null)insist(previous===null,'credential precondition failed: expected absent');
191
+ else {
192
+ insist(HEX64.test(expectedPreviousSha256??''),'credential previous hash invalid');
193
+ insist(previous?.credential_sha256===expectedPreviousSha256,'credential precondition failed');
194
+ }
195
+ const value={schema:CREDENTIAL_SCHEMA,device_id:deviceId,epoch,credential_sha256:credentialSha,
196
+ encrypted_bundle_base64:encryptedBundleBase64,rotated_at_utc:now()};
197
+ await atomicJson(file,value);
198
+ if(previous&&previous.credential_sha256!==credentialSha&&onCredentialSuperseded){
199
+ await onCredentialSuperseded({device_id:deviceId,epoch,previous_credential_sha256:previous.credential_sha256,new_credential_sha256:credentialSha});
200
+ }
201
+ return {status:'committed',device_id:deviceId,epoch,credential_sha256:credentialSha,previous_credential_sha256:previous?.credential_sha256??null};
202
+ }
203
+ async function list(){return {status:'ok',devices:Object.values(state.devices).map(publicDevice).sort((a,b)=>a.device_id.localeCompare(b.device_id)),requests:structuredClone(state.requests)};}
204
+ return Object.freeze({pair,status,reportOnline,probe,dispatch,revoke,rotateCredential,list,state_file:stateFile});
205
+ }
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
6
- import { R36_COMPACT_GATEWAYS, R36_COMPACT_TOOL_LIMIT } from './remote-mcp-r36-parity-client.mjs';
6
+ import { R44_COMPACT_GATEWAYS, R44_COMPACT_TOOL_LIMIT } from './remote-mcp-r44-parity-client.mjs';
7
7
  import { promptContexts, promptObservations, promptMutationEpoch } from './autonomous-live-state.mjs';
8
8
  import { classifyAutonomousTimeoutRecovery } from './autonomous-timeout-recovery.mjs';
9
9
 
@@ -113,10 +113,10 @@ try {
113
113
  await client.connect(transport);
114
114
  const listed = await client.listTools();
115
115
  const names = listed.tools.map(tool => tool.name).sort();
116
- if (names.length > R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact public catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
117
- for (const name of R36_COMPACT_GATEWAYS) if (!names.includes(name)) throw new Error(`R36 compact public gateway missing ${name}`);
116
+ if (names.length > R44_COMPACT_TOOL_LIMIT) throw new Error(`R44 compact public catalog exceeds ${R44_COMPACT_TOOL_LIMIT}: ${names.length}`);
117
+ for (const name of R44_COMPACT_GATEWAYS) if (!names.includes(name)) throw new Error(`R44 compact public gateway missing ${name}`);
118
118
  for (const name of ['autonomous_goal_submit','autonomous_goal_run','autonomous_goal_status','autonomous_goal_events','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status','native_diff_apply','native_diff_preview','native_diff_verify']) {
119
- if (!names.includes(name)) throw new Error(`R36 compact autonomous control missing ${name}`);
119
+ if (!names.includes(name)) throw new Error(`R44 compact autonomous control missing ${name}`);
120
120
  }
121
121
  for (const tool of listed.tools) {
122
122
  if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {