deadbyte-mcp 0.13.0 → 0.15.0

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 (153) hide show
  1. package/.gitattributes +7 -0
  2. package/AGENTS.md +18 -0
  3. package/CONTEXT.md +3 -3
  4. package/MANIFEST.SHA256 +152 -75
  5. package/R42-PARENT.json +19 -0
  6. package/R43-PARENT.json +20 -0
  7. package/R44-DONORS.json +99 -0
  8. package/R44-PARENT.json +18 -0
  9. package/README.txt +7 -2
  10. package/bin/appcontainer-stage.obj +0 -0
  11. package/bin/bootstrap-advapi32.obj +0 -0
  12. package/bin/bootstrap-exitcode.obj +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.obj +0 -0
  15. package/bin/child-control-stage.obj +0 -0
  16. package/bin/contained-reverse-worker.obj +0 -0
  17. package/bin/contained-transform-worker.obj +0 -0
  18. package/bin/containment-probe.obj +0 -0
  19. package/bin/deadbyte-contain.obj +0 -0
  20. package/bin/deadbyte-exec.obj +0 -0
  21. package/bin/deadbyte-process-host.obj +0 -0
  22. package/bin/deadbyte-tunnel-host.obj +0 -0
  23. package/controller/README.TXT +4 -5
  24. package/controller/deadbyte-controller.ps1 +1 -1
  25. package/controller/deadbyte-desktop-policy.json +5 -5
  26. package/controller/deadbyte-process-policy.json +4 -4
  27. package/docs/ARCHITECTURE.md +8 -1
  28. package/docs/R44-ARCHITECTURE.md +37 -0
  29. package/harness/r42-agent-evals.json +48 -0
  30. package/harness/task-contract.schema.json +33 -0
  31. package/package.json +15 -8
  32. package/proof/CONTAINMENT-BUILD.txt +6 -6
  33. package/scripts/agent-eval-r42.mjs +46 -0
  34. package/scripts/deferred-slot-operation-r42.mjs +126 -0
  35. package/scripts/deferred-slot-operation-r43.mjs +126 -0
  36. package/scripts/deferred-slot-operation-r44.mjs +126 -0
  37. package/scripts/final-closure-r42.mjs +116 -0
  38. package/scripts/final-closure-r43.mjs +115 -0
  39. package/scripts/final-closure-r44.mjs +121 -0
  40. package/scripts/final-closure-verify-r42.mjs +223 -0
  41. package/scripts/final-closure-verify-r43.mjs +213 -0
  42. package/scripts/final-closure-verify-r44.mjs +220 -0
  43. package/scripts/finalize-r43.mjs +217 -0
  44. package/scripts/finalize-r44.mjs +217 -0
  45. package/scripts/full-regression-r43.mjs +20 -0
  46. package/scripts/full-regression-r44.mjs +20 -0
  47. package/scripts/gate-windows-r42.ps1 +54 -0
  48. package/scripts/gate-windows-r43.ps1 +36 -0
  49. package/scripts/gate-windows-r44.ps1 +36 -0
  50. package/scripts/release-parity-r42.mjs +235 -0
  51. package/scripts/release-parity-r43.mjs +218 -0
  52. package/scripts/release-parity-r44.mjs +242 -0
  53. package/scripts/release-parity-tests-r42.ps1 +6 -0
  54. package/scripts/release-parity-tests-r43.ps1 +8 -0
  55. package/scripts/release-parity-tests-r44.ps1 +8 -0
  56. package/scripts/verify-r42-parent.mjs +101 -0
  57. package/scripts/verify-r43-parent.mjs +126 -0
  58. package/scripts/verify-r44-parent.mjs +142 -0
  59. package/scripts/windows-gate-evidence-r42.mjs +64 -0
  60. package/scripts/windows-gate-evidence-r43.mjs +55 -0
  61. package/scripts/windows-gate-evidence-r44.mjs +55 -0
  62. package/src/agent-backend-r44.mjs +254 -0
  63. package/src/agent-eval-r42.mjs +73 -0
  64. package/src/agent-eval-runner-r42.mjs +138 -0
  65. package/src/agent-governance-r42.mjs +142 -0
  66. package/src/autonomous-context-engine.mjs +2 -0
  67. package/src/autonomous-loop-state.mjs +10 -0
  68. package/src/autonomous-mcp-tools.mjs +21 -6
  69. package/src/autonomous-output-contracts.mjs +18 -2
  70. package/src/autonomous-planner-contracts.mjs +3 -0
  71. package/src/autonomous-planner.mjs +46 -6
  72. package/src/autonomous-policy.mjs +50 -5
  73. package/src/autonomous-runtime.mjs +138 -25
  74. package/src/autonomous-supervisor-host.mjs +12 -2
  75. package/src/autonomous-supervisor.mjs +1 -1
  76. package/src/capability-ledger.mjs +2 -1
  77. package/src/codex-app-server-r44.mjs +154 -0
  78. package/src/coding-plane.mjs +66 -19
  79. package/src/durable-job-r44.mjs +145 -0
  80. package/src/evidence-publisher-r42.mjs +76 -0
  81. package/src/external-job-provider-r44.mjs +126 -0
  82. package/src/host-plane.mjs +27 -6
  83. package/src/job-supervisor-host-r44.mjs +112 -0
  84. package/src/machine-fs-smoke-client.mjs +1 -1
  85. package/src/machine-fs.mjs +21 -10
  86. package/src/mcp-host-smoke-client.mjs +1 -1
  87. package/src/mcp-server.mjs +20 -3
  88. package/src/mcp-smoke-client.mjs +1 -1
  89. package/src/native-diff-v2.mjs +3 -3
  90. package/src/native-diff.mjs +3 -2
  91. package/src/observation-smoke-client.mjs +1 -1
  92. package/src/operator-surface.mjs +2 -2
  93. package/src/process-policy.mjs +19 -0
  94. package/src/process-runtime.mjs +5 -2
  95. package/src/process-smoke-client.mjs +2 -2
  96. package/src/provider-circuit.mjs +10 -4
  97. package/src/r44-compact-disarmed-probe.mjs +74 -0
  98. package/src/r44-full-surface-probe.mjs +34 -0
  99. package/src/remote-device-r44.mjs +205 -0
  100. package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
  101. package/src/remote-mcp-r44-parity-client.mjs +203 -0
  102. package/src/remote-mcp-smoke-client.mjs +7 -4
  103. package/src/runtime-update.mjs +64 -31
  104. package/src/skill-mcp-tools-r44.mjs +37 -0
  105. package/src/skill-plane-r44.mjs +203 -0
  106. package/src/tunnel-stdio-compat-smoke.mjs +1 -1
  107. package/src/version.mjs +1 -1
  108. package/test/agent-backend-r44.test.mjs +115 -0
  109. package/test/agent-eval-r42.test.mjs +39 -0
  110. package/test/agent-eval-runner-r42.test.mjs +61 -0
  111. package/test/agent-governance-r42.test.mjs +77 -0
  112. package/test/agent-runtime.test.mjs +3 -3
  113. package/test/autonomous-loop.test.mjs +97 -11
  114. package/test/autonomous-r34-e2e.test.mjs +2 -1
  115. package/test/autonomous-runtime.test.mjs +2 -2
  116. package/test/capability-ledger-r36.test.mjs +3 -3
  117. package/test/cli-entrypoint.test.mjs +1 -1
  118. package/test/codex-app-server-r44.test.mjs +115 -0
  119. package/test/codex-autonomous-r44.test.mjs +87 -0
  120. package/test/coding-plane.test.mjs +2 -2
  121. package/test/coding-runtime.test.mjs +36 -1
  122. package/test/compact-catalog-r36.test.mjs +3 -3
  123. package/test/compact-max-catalog-r40.test.mjs +4 -2
  124. package/test/compact-max-catalog-r44.test.mjs +97 -0
  125. package/test/core.test.mjs +1 -1
  126. package/test/durable-job-r44.test.mjs +75 -0
  127. package/test/evidence-publisher-r42.test.mjs +53 -0
  128. package/test/external-job-provider-r44.test.mjs +46 -0
  129. package/test/full-max-catalog-r40.test.mjs +5 -3
  130. package/test/full-max-catalog-r44.test.mjs +91 -0
  131. package/test/host-plane.test.mjs +15 -1
  132. package/test/human-clarification-r42.test.mjs +53 -0
  133. package/test/native-diff.test.mjs +28 -0
  134. package/test/output-schema.test.mjs +2 -2
  135. package/test/process-policy.test.mjs +22 -1
  136. package/test/prompts-resources.test.mjs +1 -1
  137. package/test/provider-responses-r42.test.mjs +90 -0
  138. package/test/r33-closeout-regression.test.mjs +14 -16
  139. package/test/r33-finalization.test.mjs +7 -7
  140. package/test/r42-finalization.test.mjs +38 -0
  141. package/test/r43-finalization.test.mjs +31 -0
  142. package/test/r43-finalizer.test.mjs +34 -0
  143. package/test/r43-parent.test.mjs +29 -0
  144. package/test/r44-finalization.test.mjs +58 -0
  145. package/test/r44-finalizer.test.mjs +48 -0
  146. package/test/r44-parent.test.mjs +25 -0
  147. package/test/release-version.test.mjs +53 -19
  148. package/test/remote-device-r44.test.mjs +104 -0
  149. package/test/runtime-self-update.test.mjs +42 -0
  150. package/test/skill-mcp-r44.test.mjs +38 -0
  151. package/test/skill-plane-r44.test.mjs +93 -0
  152. package/test/standing-grants.test.mjs +5 -1
  153. package/test/task-contract-r42.test.mjs +41 -0
@@ -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,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) {
@@ -0,0 +1,203 @@
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 { R34_EXPECTED_TOOLS } from './remote-mcp-r34-parity-client.mjs';
8
+ import { loadCodingPolicy } from './coding-plane.mjs';
9
+ import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
10
+ import { verifyObservationReceiptFile } from './observation-runtime.mjs';
11
+ import { loadDesktopPolicy } from './desktop-policy-r40.mjs';
12
+ import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
13
+ import { loadPublicVerifyKey } from './trust.mjs';
14
+
15
+ export const R44_EXPECTED_TOOLS=Object.freeze([...new Set([
16
+ ...R34_EXPECTED_TOOLS,
17
+ 'coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_rename_preview',
18
+ 'desktop_status','desktop_observe','desktop_target','desktop_action','skill_catalog'
19
+ ])].sort());
20
+ if(R44_EXPECTED_TOOLS.length !== 81) throw new Error(`R44 full catalog identity mismatch: ${R44_EXPECTED_TOOLS.length}`);
21
+ export const R44_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session','desktop_control'].sort());
22
+ export const R44_COMPACT_TOOL_LIMIT=43;
23
+
24
+ const here=path.dirname(fileURLToPath(import.meta.url));
25
+ const repoRoot=path.resolve(here,'..');
26
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
27
+ const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
28
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
29
+ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
30
+
31
+ function structured(result,label){
32
+ if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
33
+ if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
34
+ return result.structuredContent;
35
+ }
36
+ function gateway(result,label){
37
+ const value=structured(result,label);
38
+ if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)} error=${String(value.error??'')}`);
39
+ return value.result;
40
+ }
41
+ async function fileSha(relative){return sha256(await readFile(path.join(repoRoot,...relative.split('/'))));}
42
+ async function verifyMachineReceipt(policy,receiptId,operation){
43
+ const checked=await verifyMachineFsReceiptFile({policy,verifyKeyPath,receiptId});
44
+ if(!checked.ok||checked.signature_valid!==true||checked.operation!==operation) throw new Error(`machine-fs receipt verification failed for ${operation}`);
45
+ return checked;
46
+ }
47
+ async function verifyObservationReceipt(policy,receiptId){
48
+ const checked=await verifyObservationReceiptFile({policy,verifyKeyPath,receiptId});
49
+ if(!checked.ok||checked.signature_valid!==true) throw new Error('observation receipt verification failed');
50
+ return checked;
51
+ }
52
+ async function verifyDesktopReceipt(policy,verifyKey,receiptId,receiptSha256,kind){
53
+ const file=path.join(policy.evidence_dir,`${receiptId}.json`);
54
+ const receipt=JSON.parse(await readFile(file,'utf8'));
55
+ const checked=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:verifyKey.publicKey,keyId:verifyKey.keyId});
56
+ if(!checked.ok||checked.receipt_sha256!==receiptSha256||checked.kind!==kind) throw new Error(`desktop receipt verification failed for ${kind}`);
57
+ return checked;
58
+ }
59
+ async function waitSearch(client,searchId){
60
+ for(let attempt=0;attempt<200;attempt+=1){
61
+ const status=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_status',search_id:searchId}}),'search_status');
62
+ if(['completed','cancelled','failed'].includes(status.state)) return status;
63
+ await sleep(25);
64
+ }
65
+ throw new Error('R44 compact search timeout');
66
+ }
67
+ async function waitProcessOutput(client,sessionId){
68
+ for(let attempt=0;attempt<100;attempt+=1){
69
+ const page=gateway(await client.callTool({name:'process_session',arguments:{op:'read',session_id:sessionId,offset:0,max_bytes:65536}}),'process read');
70
+ if(Buffer.from(page.data_base64??'','base64').toString('utf8').includes('DEADBYTE_ACTION_PING')) return page;
71
+ await sleep(50);
72
+ }
73
+ throw new Error('R44 compact process output timeout');
74
+ }
75
+
76
+ export async function runR44CompactParity(endpointText){
77
+ const endpoint=new URL(endpointText);
78
+ if(endpoint.protocol!=='https:') throw new Error('R44 compact parity requires HTTPS');
79
+ const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
80
+ const policy=await loadCodingPolicy(path.join(pointer.generation_root,'coding-policy.json'));
81
+ const desktopPolicy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
82
+ const desktopVerifyKey=await loadPublicVerifyKey(verifyKeyPath);
83
+ const client=new Client({name:'deadbyte-r44-compact-parity',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
84
+ const transport=new StreamableHTTPClientTransport(endpoint);
85
+ const tag=randomBytes(8).toString('hex');
86
+ const scratchRoot='parity_scratch';
87
+ const target=`.deadbyte-r44-parity-${tag}.txt`;
88
+ let processSession=null,targetPresent=false;
89
+ const machineReceipts=[],observationReceipts=[];
90
+ try{
91
+ await client.connect(transport);
92
+ const listed=await client.listTools();
93
+ const names=listed.tools.map(tool=>tool.name).sort();
94
+ if(names.length>R44_COMPACT_TOOL_LIMIT) throw new Error(`R44 compact catalog exceeds ${R44_COMPACT_TOOL_LIMIT}: ${names.length}`);
95
+ if(names.length!==R44_COMPACT_TOOL_LIMIT) throw new Error(`R44 compact catalog must be exactly ${R44_COMPACT_TOOL_LIMIT}, got ${names.length}`);
96
+ for(const name of R44_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R44 compact gateway missing ${name}`);
97
+ for(const tool of listed.tools){
98
+ if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
99
+ }
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');
102
+ if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
103
+ manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R44 compact manifest catalog binding mismatch');
104
+ const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
105
+ for(const item of required){
106
+ if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact capability route missing: ${String(item.id)}`);
107
+ }
108
+ const skillCatalog=structured(await client.callTool({name:'skill_catalog',arguments:{op:'list'}}),'skill_catalog');
109
+ if(skillCatalog.status!=='ok'||skillCatalog.op!=='list'||!skillCatalog.result||!Array.isArray(skillCatalog.result.skills)||
110
+ !/^[0-9a-f]{64}$/.test(String(skillCatalog.result.registry_sha256??''))) throw new Error('R44 skill catalog verification failed');
111
+ const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
112
+ const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
113
+ if(coding.armed!==true||autonomous.coding_armed!==true||autonomous.autonomous_armed!==true) throw new Error('R44 compact parity requires coding and autonomous standing grants');
114
+ const desktopStatus=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
115
+ if(desktopStatus.configured!==true||desktopStatus.armed!==false) throw new Error('R44 compact parity requires Desktop configured and DISARMED');
116
+ const desktopObserved=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
117
+ await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopObserved.receipt_id,desktopObserved.receipt_sha256,'desktop_observation');
118
+ const desktopTarget=gateway(await client.callTool({name:'desktop_control',arguments:{op:'target',frame_id:desktopObserved.frame.frame_id,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}}),'desktop target');
119
+ await verifyDesktopReceipt(desktopPolicy,desktopVerifyKey,desktopTarget.grounding.receipt_id,desktopTarget.grounding.receipt_sha256,'desktop_grounding');
120
+ if(desktopTarget.target?.source!=='uia') throw new Error('R44 compact UIA root grounding missing');
121
+ const tree=structured(await client.callTool({name:'coding_tree',arguments:{root_id:'core',path:'workspaces/demo',max_depth:2,max_entries:128}}),'coding_tree');
122
+ if(tree.status!=='ok'||!Array.isArray(tree.entries)) throw new Error('coding_tree malformed');
123
+ const stat=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'stat',root_id:'core',path:'package.json',hash:true}}),'coding stat');
124
+ if(stat.type!=='file'||!/^[0-9a-f]{64}$/.test(String(stat.sha256??''))||!/^[0-9a-f]{64}$/.test(String(stat.observation_sha256??''))) throw new Error('coding stat identity missing');
125
+ const search=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'search',root_id:'core',path:'src',pattern:'R36_CAPABILITY_LEDGER',target:'content',mode:'literal',case_sensitive:true,max_results:20,max_file_bytes:262144,max_scan_bytes:4194304,page_size:20,cursor:null}}),'coding search');
126
+ if(!Array.isArray(search.matches)||search.matches.length<1) throw new Error('coding search returned no canonical ledger match');
127
+
128
+ const buildScript='scripts/autonomous-build.ps1';
129
+ const testScript='scripts/release-parity-tests-r40.ps1';
130
+ const build=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:buildScript,script_sha256:await fileSha(buildScript),args:[],cwd:'.',timeout_ms:120000}},{timeout:180000,maxTotalTimeout:180000}),'coding build');
131
+ if(build.status!=='passed'||build.exit_code!==0) throw new Error('R44 coding build failed');
132
+ const buildVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:build.receipt_id}}),'coding build receipt');
133
+ if(buildVerify.ok!==true||buildVerify.signature_valid!==true) throw new Error('R44 coding build receipt invalid');
134
+ const tests=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:testScript,script_sha256:await fileSha(testScript),args:[],cwd:'.',timeout_ms:300000}},{timeout:360000,maxTotalTimeout:360000}),'coding tests');
135
+ if(tests.status!=='passed'||tests.exit_code!==0) throw new Error('R44 coding tests failed');
136
+ const testVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:tests.receipt_id}}),'coding test receipt');
137
+ if(testVerify.ok!==true||testVerify.signature_valid!==true) throw new Error('R44 coding test receipt invalid');
138
+
139
+ const initial=`needle-${tag}\n`;
140
+ const write=gateway(await client.callTool({name:'machine_fs',arguments:{op:'write',root_id:scratchRoot,path:target,content:initial,encoding:'utf8',expected_sha256:'absent'}}),'machine_fs write');
141
+ targetPresent=true;machineReceipts.push(write.receipt_id);await verifyMachineReceipt(policy,write.receipt_id,'machine_fs_write');
142
+ const bounded=gateway(await client.callTool({name:'machine_fs',arguments:{op:'read',root_id:scratchRoot,path:target,offset:0,max_bytes:128,hash_file:true}}),'machine_fs read');
143
+ if(Buffer.from(bounded.data_base64,'base64').toString('utf8')!==initial||bounded.file_sha256!==write.sha256) throw new Error('R44 bounded machine read mismatch');
144
+ const edit=gateway(await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'needle',new_string:'beta',expected_replacements:1}}),'machine_fs edit');
145
+ machineReceipts.push(edit.receipt_id);await verifyMachineReceipt(policy,edit.receipt_id,'machine_fs_edit_exact');
146
+ const stale=await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'beta',new_string:'bad',expected_replacements:1}});
147
+ const staleValue=stale.structuredContent;
148
+ if(!staleValue||typeof staleValue!=='object') throw new Error('R44 stale machine-fs denial missing structured output');
149
+ if(stale.isError!==true||staleValue.status!=='denied'||!/precondition mismatch/i.test(String(staleValue.error??''))) {
150
+ throw new Error(`R44 stale machine-fs precondition was not denied: ${JSON.stringify(staleValue)}`);
151
+ }
152
+
153
+ const searchStart=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_start',root_id:scratchRoot,path:'.',pattern:tag,case_sensitive:true,max_results:20,max_files:200,max_bytes:262144,max_file_bytes:16384}}),'observe search start');
154
+ const searchStatus=await waitSearch(client,searchStart.search_id);
155
+ if(searchStatus.state!=='completed'||!searchStatus.receipt_id) throw new Error('R44 compact observation search failed');
156
+ observationReceipts.push(searchStatus.receipt_id);await verifyObservationReceipt(policy,searchStatus.receipt_id);
157
+ const page=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_page',search_id:searchStart.search_id,limit:20,cursor:null}}),'observe search page');
158
+ if(!page.results.some(item=>item.path===target)) throw new Error('R44 compact search did not return parity file');
159
+ const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');
160
+ observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);
161
+ const machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
162
+ if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R44 compact machine status release mismatch');
163
+
164
+ const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
165
+ processSession=started.session_id??started.session?.session_id;
166
+ if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R44 compact process session id missing');
167
+ const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
168
+ const processRead=await waitProcessOutput(client,processSession);
169
+ const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
170
+ const terminalState=terminated.state??terminated.session?.state;
171
+ if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R44 compact process lifecycle failed');
172
+
173
+ const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
174
+ const deleted=gateway(await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}),'machine_fs delete');
175
+ machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
176
+
177
+ return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
178
+ catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
179
+ capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
180
+ gateway_count:R44_COMPACT_GATEWAYS.length,skill_catalog_verified:true,skill_count:skillCatalog.result.skills.length,tree_entries:tree.entries.length,coding_stat_verified:true,coding_search_result_count:search.matches.length,
181
+ desktop_armed:false,desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
182
+ desktop_frame_id:desktopObserved.frame.frame_id,desktop_target_id:desktopTarget.target.target_id,
183
+ machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
184
+ observation_search_result_count:page.results.length,process_session_id:processSession,process_output_bytes:processRead.stream_head,
185
+ process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
186
+ build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
187
+ execution_provider:'deadbyte_mcp',rdc_operation_count:0};
188
+ } finally {
189
+ if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
190
+ if(targetPresent){
191
+ try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
192
+ if(fsStat.identity_sha256) await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}).catch(()=>{});
193
+ }catch{}
194
+ }
195
+ await client.close().catch(()=>{});
196
+ }
197
+ }
198
+
199
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
200
+ const endpoint=process.argv[2];
201
+ if(!endpoint) throw new Error('usage: node src/remote-mcp-r44-parity-client.mjs https://host/mcp/path');
202
+ runR44CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
203
+ }
@@ -8,7 +8,7 @@ if (!['smoke', 'full'].includes(mode)) throw new Error('mode must be smoke or fu
8
8
  const endpoint = new URL(endpointText);
9
9
  if (endpoint.protocol !== 'https:') throw new Error('remote MCP gate requires HTTPS');
10
10
 
11
- const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
11
+ const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
12
12
  const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
13
13
  const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
14
14
  const processTools = [
@@ -53,6 +53,8 @@ const r36CompactTools = [
53
53
  if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
54
54
  const r40CompactTools = [...r36CompactTools, 'desktop_control'].sort();
55
55
  if (r40CompactTools.length !== 42) throw new Error(`R40 compact catalog constant drift: ${r40CompactTools.length}`);
56
+ const r44CompactTools = [...r40CompactTools, 'skill_catalog'].sort();
57
+ if (r44CompactTools.length !== 43) throw new Error(`R44 compact catalog constant drift: ${r44CompactTools.length}`);
56
58
  const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
57
59
  const allowedCatalogs = [];
58
60
  for (const machine of [false, true]) for (const host of [false, true]) for (const agent of [false, true]) {
@@ -98,7 +100,8 @@ try {
98
100
  const names = listed.tools.map(t => t.name).sort();
99
101
  const exactR36Compact = JSON.stringify(names) === JSON.stringify(r36CompactTools);
100
102
  const exactR40Compact = JSON.stringify(names) === JSON.stringify(r40CompactTools);
101
- const compactProfile = exactR36Compact || exactR40Compact;
103
+ const exactR44Compact = JSON.stringify(names) === JSON.stringify(r44CompactTools);
104
+ const compactProfile = exactR36Compact || exactR40Compact || exactR44Compact;
102
105
  const catalogOk = compactProfile || allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected));
103
106
  if (!catalogOk) throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
104
107
  for (const tool of listed.tools) {
@@ -123,7 +126,7 @@ try {
123
126
  if (compactProfile) {
124
127
  const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
125
128
  const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
126
- const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && exactR40Compact;
129
+ const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && (exactR40Compact || exactR44Compact);
127
130
  if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
128
131
  !Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
129
132
  throw new Error('compact capability manifest malformed');
@@ -132,7 +135,7 @@ try {
132
135
  const expectedCatalogSha = sha256(Buffer.from(JSON.stringify(names), 'utf8'));
133
136
  if (manifest.tool_count !== names.length || JSON.stringify(manifest.tool_catalog) !== JSON.stringify(names) ||
134
137
  manifest.catalog_sha256 !== expectedCatalogSha) {
135
- throw new Error('R40 compact capability manifest catalog attestation mismatch');
138
+ throw new Error('compact v2 capability manifest catalog attestation mismatch');
136
139
  }
137
140
  }
138
141
  const required = manifest.capabilities.filter(item => item?.production === true && item?.host_only !== true);