deadbyte-mcp 0.9.0 → 0.11.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.
- package/MANIFEST.SHA256 +134 -92
- package/README.txt +19 -15
- package/bench/fixtures/corpus.json +32 -13
- package/bench/fixtures/r36/case-map.json +8 -0
- package/bin/WORKER-REGISTRY.txt +3 -3
- package/bin/appcontainer-stage.exe +0 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.exe +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.exe +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.exe +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.exe +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.exe +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.exe +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.exe +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.exe +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.exe +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.exe +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.exe +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +8 -6
- package/controller/deadbyte-controller.ps1 +48 -2
- package/controller/deadbyte-process-policy.json +5 -5
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
- package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
- package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
- package/package.json +1 -1
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/deferred-slot-operation.mjs +53 -15
- package/scripts/final-closure-r36.mjs +113 -0
- package/scripts/final-closure-verify-r36.mjs +208 -0
- package/scripts/final-closure-verify.mjs +88 -39
- package/scripts/final-closure.mjs +63 -56
- package/scripts/gate-windows-r36.ps1 +42 -0
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-r36.mjs +200 -0
- package/scripts/release-parity-tests.ps1 +9 -0
- package/scripts/release-parity.mjs +149 -139
- package/scripts/windows-gate-evidence-r36.mjs +52 -0
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/agent-memory-plane.mjs +98 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-mcp-tools.mjs +5 -1
- package/src/autonomous-output-contracts.mjs +9 -6
- package/src/autonomous-planner-contracts.mjs +36 -1
- package/src/autonomous-planner.mjs +25 -3
- package/src/autonomous-runtime.mjs +337 -43
- package/src/autonomous-timeout-recovery.mjs +27 -0
- package/src/capability-benchmark-runner.mjs +86 -1
- package/src/capability-benchmark.mjs +45 -0
- package/src/capability-ledger.mjs +158 -0
- package/src/coding-mcp-tools.mjs +31 -17
- package/src/coding-output-contracts.mjs +48 -10
- package/src/coding-plane.mjs +45 -9
- package/src/coding-search.mjs +130 -47
- package/src/compact-gateway-mcp-tools.mjs +128 -0
- package/src/compact-gateway-output-contracts.mjs +16 -0
- package/src/deadbyte-cli.mjs +62 -78
- package/src/effect-ledger.mjs +47 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +51 -8
- package/src/mcp-server.mjs +32 -9
- package/src/native-diff-v2.mjs +337 -0
- package/src/native-diff.mjs +50 -2
- package/src/observation-lease.mjs +32 -0
- package/src/observation-smoke-client.mjs +1 -1
- package/src/operator-surface.mjs +7 -1
- package/src/process-runtime.mjs +98 -11
- package/src/process-smoke-client.mjs +11 -3
- package/src/r36-compact-disarmed-probe.mjs +50 -0
- package/src/r36-full-surface-probe.mjs +56 -0
- package/src/remote-live-log.mjs +135 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +22 -12
- package/src/remote-mcp-coding-smoke-client.mjs +1 -1
- package/src/remote-mcp-r36-parity-client.mjs +175 -0
- package/src/remote-mcp-smoke-client.mjs +112 -56
- package/src/runtime-update.mjs +51 -1
- package/src/search-cursor.mjs +45 -0
- package/src/semantic-code.mjs +72 -5
- package/src/semantic-lsp-backend.mjs +39 -3
- package/src/version.mjs +1 -1
- package/src/worker-scheduler.mjs +53 -0
- package/test/autonomous-capability-r36.test.mjs +151 -0
- package/test/autonomous-output-contract.test.mjs +1 -1
- package/test/autonomous-runtime.test.mjs +2 -2
- package/test/capability-benchmark-r36.test.mjs +107 -0
- package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
- package/test/capability-claims-r36.test.mjs +23 -0
- package/test/capability-ledger-r36.test.mjs +102 -0
- package/test/coding-catalog-consistency.test.mjs +2 -2
- package/test/coding-plane.test.mjs +2 -2
- package/test/coding-runtime.test.mjs +16 -0
- package/test/coding-search-r36.test.mjs +90 -0
- package/test/compact-catalog-r36.test.mjs +37 -0
- package/test/compact-gateway-r36.test.mjs +66 -0
- package/test/contained.test.mjs +1 -1
- package/test/core.test.mjs +1 -1
- package/test/deferred-slot-operation.test.mjs +9 -0
- package/test/fixtures/lsp-framed-server.mjs +2 -0
- package/test/helpers/autonomous-r34-fixture.mjs +5 -3
- package/test/multi-file-read.test.mjs +3 -0
- package/test/native-diff-topology-r36.test.mjs +105 -0
- package/test/package-boundary.test.mjs +6 -0
- package/test/process-longrun-r36.test.mjs +82 -0
- package/test/process-release-gate.test.mjs +9 -0
- package/test/production-docs.test.mjs +5 -5
- package/test/prompts-resources.test.mjs +7 -1
- package/test/r33-closeout-regression.test.mjs +4 -3
- package/test/r33-finalization.test.mjs +14 -9
- package/test/r36-finalization.test.mjs +104 -0
- package/test/r36-release-identity.test.mjs +45 -0
- package/test/r37-agent-fabric.test.mjs +92 -0
- package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
- package/test/r37-finalization.test.mjs +74 -0
- package/test/release-version.test.mjs +42 -33
- package/test/remote-live-log-r36.test.mjs +37 -0
- package/test/remote-live-log-r37.test.mjs +47 -0
- package/test/remote-live-log-recovery-r36.test.mjs +41 -0
- package/test/remote-r24-catalog.test.mjs +13 -0
- package/test/remote-session-cli.test.mjs +53 -66
- package/test/runtime-self-update.test.mjs +58 -1
- package/test/semantic-lsp-backend.test.mjs +9 -0
- package/test/semantic-r36-rename.test.mjs +82 -0
- package/deadbyte-mcp-0.8.9.tgz +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
|
|
12
|
+
export const R36_EXPECTED_TOOLS=Object.freeze([...new Set([
|
|
13
|
+
...R34_EXPECTED_TOOLS,
|
|
14
|
+
'coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_rename_preview'
|
|
15
|
+
])].sort());
|
|
16
|
+
if(R36_EXPECTED_TOOLS.length !== 76) throw new Error(`R36 full catalog identity mismatch: ${R36_EXPECTED_TOOLS.length}`);
|
|
17
|
+
export const R36_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'].sort());
|
|
18
|
+
export const R36_COMPACT_TOOL_LIMIT=42;
|
|
19
|
+
|
|
20
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const repoRoot=path.resolve(here,'..');
|
|
22
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
23
|
+
const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
|
|
24
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
25
|
+
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
26
|
+
|
|
27
|
+
function structured(result,label){
|
|
28
|
+
if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
|
|
29
|
+
if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
|
|
30
|
+
return result.structuredContent;
|
|
31
|
+
}
|
|
32
|
+
function gateway(result,label){
|
|
33
|
+
const value=structured(result,label);
|
|
34
|
+
if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)} error=${String(value.error??'')}`);
|
|
35
|
+
return value.result;
|
|
36
|
+
}
|
|
37
|
+
async function fileSha(relative){return sha256(await readFile(path.join(repoRoot,...relative.split('/'))));}
|
|
38
|
+
async function verifyMachineReceipt(policy,receiptId,operation){
|
|
39
|
+
const checked=await verifyMachineFsReceiptFile({policy,verifyKeyPath,receiptId});
|
|
40
|
+
if(!checked.ok||checked.signature_valid!==true||checked.operation!==operation) throw new Error(`machine-fs receipt verification failed for ${operation}`);
|
|
41
|
+
return checked;
|
|
42
|
+
}
|
|
43
|
+
async function verifyObservationReceipt(policy,receiptId){
|
|
44
|
+
const checked=await verifyObservationReceiptFile({policy,verifyKeyPath,receiptId});
|
|
45
|
+
if(!checked.ok||checked.signature_valid!==true) throw new Error('observation receipt verification failed');
|
|
46
|
+
return checked;
|
|
47
|
+
}
|
|
48
|
+
async function waitSearch(client,searchId){
|
|
49
|
+
for(let attempt=0;attempt<200;attempt+=1){
|
|
50
|
+
const status=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_status',search_id:searchId}}),'search_status');
|
|
51
|
+
if(['completed','cancelled','failed'].includes(status.state)) return status;
|
|
52
|
+
await sleep(25);
|
|
53
|
+
}
|
|
54
|
+
throw new Error('R36 compact search timeout');
|
|
55
|
+
}
|
|
56
|
+
async function waitProcessOutput(client,sessionId){
|
|
57
|
+
for(let attempt=0;attempt<100;attempt+=1){
|
|
58
|
+
const page=gateway(await client.callTool({name:'process_session',arguments:{op:'read',session_id:sessionId,offset:0,max_bytes:65536}}),'process read');
|
|
59
|
+
if(Buffer.from(page.data_base64??'','base64').toString('utf8').includes('DEADBYTE_ACTION_PING')) return page;
|
|
60
|
+
await sleep(50);
|
|
61
|
+
}
|
|
62
|
+
throw new Error('R36 compact process output timeout');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function runR36CompactParity(endpointText){
|
|
66
|
+
const endpoint=new URL(endpointText);
|
|
67
|
+
if(endpoint.protocol!=='https:') throw new Error('R36 compact parity requires HTTPS');
|
|
68
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
69
|
+
const policy=await loadCodingPolicy(path.join(pointer.generation_root,'coding-policy.json'));
|
|
70
|
+
const client=new Client({name:'deadbyte-r36-compact-parity',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
71
|
+
const transport=new StreamableHTTPClientTransport(endpoint);
|
|
72
|
+
const tag=randomBytes(8).toString('hex');
|
|
73
|
+
const scratchRoot='parity_scratch';
|
|
74
|
+
const target=`.deadbyte-r36-parity-${tag}.txt`;
|
|
75
|
+
let processSession=null,targetPresent=false;
|
|
76
|
+
const machineReceipts=[],observationReceipts=[];
|
|
77
|
+
try{
|
|
78
|
+
await client.connect(transport);
|
|
79
|
+
const listed=await client.listTools();
|
|
80
|
+
const names=listed.tools.map(tool=>tool.name).sort();
|
|
81
|
+
if(names.length>R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
82
|
+
for(const name of R36_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R36 compact gateway missing ${name}`);
|
|
83
|
+
for(const tool of listed.tools){
|
|
84
|
+
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
85
|
+
}
|
|
86
|
+
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
87
|
+
if(manifest.schema!=='deadbyte.capability-manifest.v1'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R36 compact capability manifest malformed');
|
|
88
|
+
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
89
|
+
for(const item of required){
|
|
90
|
+
if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact capability route missing: ${String(item.id)}`);
|
|
91
|
+
}
|
|
92
|
+
const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
93
|
+
const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
|
|
94
|
+
if(coding.armed!==true||autonomous.coding_armed!==true||autonomous.autonomous_armed!==true) throw new Error('R36 compact parity requires coding and autonomous standing grants');
|
|
95
|
+
const tree=structured(await client.callTool({name:'coding_tree',arguments:{root_id:'core',path:'workspaces/demo',max_depth:2,max_entries:128}}),'coding_tree');
|
|
96
|
+
if(tree.status!=='ok'||!Array.isArray(tree.entries)) throw new Error('coding_tree malformed');
|
|
97
|
+
const stat=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'stat',root_id:'core',path:'package.json',hash:true}}),'coding stat');
|
|
98
|
+
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');
|
|
99
|
+
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');
|
|
100
|
+
if(!Array.isArray(search.matches)||search.matches.length<1) throw new Error('coding search returned no R36 ledger match');
|
|
101
|
+
|
|
102
|
+
const buildScript='scripts/autonomous-build.ps1';
|
|
103
|
+
const testScript='scripts/release-parity-tests.ps1';
|
|
104
|
+
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');
|
|
105
|
+
if(build.status!=='passed'||build.exit_code!==0) throw new Error('R36 coding build failed');
|
|
106
|
+
const buildVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:build.receipt_id}}),'coding build receipt');
|
|
107
|
+
if(buildVerify.ok!==true||buildVerify.signature_valid!==true) throw new Error('R36 coding build receipt invalid');
|
|
108
|
+
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');
|
|
109
|
+
if(tests.status!=='passed'||tests.exit_code!==0) throw new Error('R36 coding tests failed');
|
|
110
|
+
const testVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:tests.receipt_id}}),'coding test receipt');
|
|
111
|
+
if(testVerify.ok!==true||testVerify.signature_valid!==true) throw new Error('R36 coding test receipt invalid');
|
|
112
|
+
|
|
113
|
+
const initial=`needle-${tag}\n`;
|
|
114
|
+
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');
|
|
115
|
+
targetPresent=true;machineReceipts.push(write.receipt_id);await verifyMachineReceipt(policy,write.receipt_id,'machine_fs_write');
|
|
116
|
+
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');
|
|
117
|
+
if(Buffer.from(bounded.data_base64,'base64').toString('utf8')!==initial||bounded.file_sha256!==write.sha256) throw new Error('R36 bounded machine read mismatch');
|
|
118
|
+
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');
|
|
119
|
+
machineReceipts.push(edit.receipt_id);await verifyMachineReceipt(policy,edit.receipt_id,'machine_fs_edit_exact');
|
|
120
|
+
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}});
|
|
121
|
+
const staleValue=stale.structuredContent;
|
|
122
|
+
if(!staleValue||typeof staleValue!=='object') throw new Error('R36 stale machine-fs denial missing structured output');
|
|
123
|
+
if(stale.isError!==true||staleValue.status!=='denied'||!/precondition mismatch/i.test(String(staleValue.error??''))) {
|
|
124
|
+
throw new Error(`R36 stale machine-fs precondition was not denied: ${JSON.stringify(staleValue)}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
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');
|
|
128
|
+
const searchStatus=await waitSearch(client,searchStart.search_id);
|
|
129
|
+
if(searchStatus.state!=='completed'||!searchStatus.receipt_id) throw new Error('R36 compact observation search failed');
|
|
130
|
+
observationReceipts.push(searchStatus.receipt_id);await verifyObservationReceipt(policy,searchStatus.receipt_id);
|
|
131
|
+
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');
|
|
132
|
+
if(!page.results.some(item=>item.path===target)) throw new Error('R36 compact search did not return parity file');
|
|
133
|
+
const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');
|
|
134
|
+
observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);
|
|
135
|
+
const machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
|
|
136
|
+
if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R36 compact machine status release mismatch');
|
|
137
|
+
|
|
138
|
+
const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
|
|
139
|
+
processSession=started.session_id??started.session?.session_id;
|
|
140
|
+
if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R36 compact process session id missing');
|
|
141
|
+
const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
|
|
142
|
+
const processRead=await waitProcessOutput(client,processSession);
|
|
143
|
+
const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
|
|
144
|
+
const terminalState=terminated.state??terminated.session?.state;
|
|
145
|
+
if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R36 compact process lifecycle failed');
|
|
146
|
+
|
|
147
|
+
const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
|
|
148
|
+
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');
|
|
149
|
+
machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
|
|
150
|
+
|
|
151
|
+
return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
|
|
152
|
+
catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
|
|
153
|
+
capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
|
|
154
|
+
gateway_count:R36_COMPACT_GATEWAYS.length,tree_entries:tree.entries.length,coding_stat_verified:true,coding_search_result_count:search.matches.length,
|
|
155
|
+
machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
|
|
156
|
+
observation_search_result_count:page.results.length,process_session_id:processSession,process_output_bytes:processRead.stream_head,
|
|
157
|
+
process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
|
|
158
|
+
build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
|
|
159
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
160
|
+
} finally {
|
|
161
|
+
if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
|
|
162
|
+
if(targetPresent){
|
|
163
|
+
try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
|
|
164
|
+
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(()=>{});
|
|
165
|
+
}catch{}
|
|
166
|
+
}
|
|
167
|
+
await client.close().catch(()=>{});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
172
|
+
const endpoint=process.argv[2];
|
|
173
|
+
if(!endpoint) throw new Error('usage: node src/remote-mcp-r36-parity-client.mjs https://host/mcp/path');
|
|
174
|
+
runR36CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
175
|
+
}
|
|
@@ -30,7 +30,7 @@ const agentTools = [
|
|
|
30
30
|
const codingTools = [
|
|
31
31
|
'coding_benchmark_run', 'coding_command_run', 'coding_file_read', 'coding_file_write', 'coding_files_read',
|
|
32
32
|
'coding_receipt_verify', 'coding_search', 'coding_session_status', 'coding_stat',
|
|
33
|
-
'coding_symbol_definition', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_replace',
|
|
33
|
+
'coding_symbol_definition', 'coding_symbol_diagnostics', 'coding_symbol_implementations', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_rename_preview', 'coding_symbol_replace',
|
|
34
34
|
'coding_task_approve', 'coding_task_cancel', 'coding_task_events', 'coding_task_run',
|
|
35
35
|
'coding_task_status', 'coding_task_submit', 'coding_task_verify', 'coding_text_replace', 'coding_tree'
|
|
36
36
|
];
|
|
@@ -39,6 +39,18 @@ const autonomousTools = [
|
|
|
39
39
|
'autonomous_goal_status', 'autonomous_goal_submit', 'autonomous_goal_verify', 'autonomous_release_gate',
|
|
40
40
|
'autonomous_session_status', 'native_diff_apply', 'native_diff_preview', 'native_diff_verify'
|
|
41
41
|
];
|
|
42
|
+
const r36CompactTools = [
|
|
43
|
+
'autonomous_goal_approve','autonomous_goal_cancel','autonomous_goal_events','autonomous_goal_run','autonomous_goal_status','autonomous_goal_submit','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status',
|
|
44
|
+
'capability_manifest',
|
|
45
|
+
'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_inspect','coding_receipt_verify','coding_session_status','coding_symbol_replace',
|
|
46
|
+
'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree',
|
|
47
|
+
'evidence_verify',
|
|
48
|
+
'host_exec','host_file_read','host_file_write',
|
|
49
|
+
'machine_fs','machine_list','machine_observe','machine_ping','machine_revoke','machine_status',
|
|
50
|
+
'native_diff_apply','native_diff_preview','native_diff_verify',
|
|
51
|
+
'process_session'
|
|
52
|
+
].sort();
|
|
53
|
+
if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
|
|
42
54
|
const allowedCatalogs = [];
|
|
43
55
|
for (const machine of [false, true]) for (const host of [false, true]) for (const agent of [false, true]) {
|
|
44
56
|
for (const processPlane of [false, true]) for (const machineFs of [false, true]) for (const observation of [false, true]) for (const codingMode of ['none', 'coding', 'autonomous']) {
|
|
@@ -68,13 +80,21 @@ function structured(result, label) {
|
|
|
68
80
|
if (!result.structuredContent || typeof result.structuredContent !== 'object') throw new Error(`${label} returned no structured object`);
|
|
69
81
|
return result.structuredContent;
|
|
70
82
|
}
|
|
83
|
+
function gateway(result, label) {
|
|
84
|
+
const value = structured(result, label);
|
|
85
|
+
if (value.status !== 'ok' || !value.result || typeof value.result !== 'object') {
|
|
86
|
+
throw new Error(`${label} gateway status=${String(value.status)} error=${String(value.error ?? '')}`);
|
|
87
|
+
}
|
|
88
|
+
return value.result;
|
|
89
|
+
}
|
|
71
90
|
|
|
72
91
|
try {
|
|
73
92
|
await client.connect(transport);
|
|
74
93
|
if (client.getProtocolEra() !== 'modern') throw new Error(`expected modern MCP era, got ${String(client.getProtocolEra())}`);
|
|
75
94
|
const listed = await client.listTools();
|
|
76
95
|
const names = listed.tools.map(t => t.name).sort();
|
|
77
|
-
const
|
|
96
|
+
const compactProfile = JSON.stringify(names) === JSON.stringify(r36CompactTools);
|
|
97
|
+
const catalogOk = compactProfile || allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected));
|
|
78
98
|
if (!catalogOk) throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
|
|
79
99
|
for (const tool of listed.tools) {
|
|
80
100
|
if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {
|
|
@@ -90,72 +110,108 @@ try {
|
|
|
90
110
|
const codingRuntime = codingTools.every(name => names.includes(name));
|
|
91
111
|
const autonomousRuntime = autonomousTools.every(name => names.includes(name));
|
|
92
112
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const proofOutput = `remote-proof.${randomBytes(8).toString('hex')}.txt`;
|
|
97
|
-
const proof = structured(await client.callTool({
|
|
98
|
-
name: 'proof_execute', arguments: { workspace_id: 'demo', input: 'input.txt', output: proofOutput }
|
|
99
|
-
}), 'proof_execute');
|
|
100
|
-
if (proof.status !== 'passed' || !/^[0-9a-f]{64}$/.test(proof.request_sha256)) throw new Error('remote proof request identity missing');
|
|
101
|
-
const proofVerify = structured(await client.callTool({
|
|
102
|
-
name: 'evidence_verify', arguments: { workspace_id: 'demo', receipt_path: proof.receipt_path }
|
|
103
|
-
}), 'evidence_verify proof');
|
|
104
|
-
if (proofVerify.ok !== true || proofVerify.signature_valid !== true || proofVerify.receipt_schema !== 'deadbyte.receipt.v2' ||
|
|
105
|
-
proofVerify.request_sha256 !== proof.request_sha256) throw new Error(`remote proof verify failed: ${JSON.stringify(proofVerify)}`);
|
|
106
|
-
|
|
113
|
+
let compact = null;
|
|
114
|
+
let proofOutput = null;
|
|
115
|
+
let proof = null;
|
|
107
116
|
let contained = null;
|
|
108
|
-
if (mode === 'full') {
|
|
109
|
-
const output = `remote-contained.${randomBytes(8).toString('hex')}.txt`;
|
|
110
|
-
const exec = structured(await client.callTool({
|
|
111
|
-
name: 'contained_transform',
|
|
112
|
-
arguments: { workspace_id: 'demo', input: 'input.txt', output, operation: 'uppercase_ascii' }
|
|
113
|
-
}), 'contained_transform');
|
|
114
|
-
if (exec.status !== 'passed' || exec.output_sha256 !== '3e740f6fcf2c6659bc5f99e0706b09f3c127407bbabe74d05285d2e3276946c2' ||
|
|
115
|
-
!/^[0-9a-f]{64}$/.test(exec.request_sha256)) throw new Error(`remote contained result mismatch: ${JSON.stringify(exec)}`);
|
|
116
|
-
const verify = structured(await client.callTool({
|
|
117
|
-
name: 'evidence_verify', arguments: { workspace_id: 'demo', receipt_path: exec.receipt_path }
|
|
118
|
-
}), 'evidence_verify contained');
|
|
119
|
-
if (verify.ok !== true || verify.signature_valid !== true || verify.containment_verified !== true ||
|
|
120
|
-
verify.active_network_denial_verified !== true || verify.receipt_schema !== 'deadbyte.receipt.v2' ||
|
|
121
|
-
verify.request_sha256 !== exec.request_sha256) throw new Error(`remote contained verification failed: ${JSON.stringify(verify)}`);
|
|
122
|
-
contained = { output, receipt_sha256: exec.receipt_sha256, request_sha256: exec.request_sha256, output_sha256: exec.output_sha256 };
|
|
123
|
-
}
|
|
124
117
|
let agent = null;
|
|
125
|
-
if (
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
if (compactProfile) {
|
|
119
|
+
const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
|
|
120
|
+
if (manifest.schema !== 'deadbyte.capability-manifest.v1' || manifest.profile !== 'compact' ||
|
|
121
|
+
!Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
|
|
122
|
+
throw new Error('R36 compact capability manifest malformed');
|
|
123
|
+
}
|
|
124
|
+
const required = manifest.capabilities.filter(item => item?.production === true && item?.host_only !== true);
|
|
125
|
+
for (const item of required) {
|
|
126
|
+
if (typeof item.compact_tool !== 'string' || !names.includes(item.compact_tool)) {
|
|
127
|
+
throw new Error(`R36 compact capability route missing: ${String(item.id)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const machine = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_status' } }), 'machine_status');
|
|
131
|
+
const nonce = `remote-smoke-${randomBytes(16).toString('hex')}`;
|
|
132
|
+
const ping = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_ping', nonce } }), 'machine_ping');
|
|
133
|
+
if (ping.nonce !== nonce || ping.machine_id !== machine.machine_id ||
|
|
134
|
+
ping.release_id !== machine.release_id || ping.release_manifest_sha256 !== machine.release_manifest_sha256) {
|
|
135
|
+
throw new Error('R36 compact machine ping binding mismatch');
|
|
136
|
+
}
|
|
137
|
+
compact = {
|
|
138
|
+
profile: 'compact',
|
|
139
|
+
ledger_sha256: manifest.ledger_sha256,
|
|
140
|
+
capability_count: manifest.capabilities.length,
|
|
141
|
+
production_non_host_capabilities: required.length,
|
|
142
|
+
capability_closure: true,
|
|
143
|
+
machine_id: machine.machine_id,
|
|
144
|
+
release_id: machine.release_id,
|
|
145
|
+
release_manifest_sha256: machine.release_manifest_sha256,
|
|
146
|
+
ping_signature_valid: ping.signature_valid === true
|
|
147
|
+
};
|
|
148
|
+
} else {
|
|
149
|
+
const opened = structured(await client.callTool({ name: 'workspace_open', arguments: { workspace_id: 'demo' } }), 'workspace_open');
|
|
150
|
+
if (opened.status !== 'ok') throw new Error(`workspace_open status=${String(opened.status)}`);
|
|
151
|
+
|
|
152
|
+
proofOutput = `remote-proof.${randomBytes(8).toString('hex')}.txt`;
|
|
153
|
+
proof = structured(await client.callTool({
|
|
154
|
+
name: 'proof_execute', arguments: { workspace_id: 'demo', input: 'input.txt', output: proofOutput }
|
|
155
|
+
}), 'proof_execute');
|
|
156
|
+
if (proof.status !== 'passed' || !/^[0-9a-f]{64}$/.test(proof.request_sha256)) throw new Error('remote proof request identity missing');
|
|
157
|
+
const proofVerify = structured(await client.callTool({
|
|
158
|
+
name: 'evidence_verify', arguments: { workspace_id: 'demo', receipt_path: proof.receipt_path }
|
|
159
|
+
}), 'evidence_verify proof');
|
|
160
|
+
if (proofVerify.ok !== true || proofVerify.signature_valid !== true || proofVerify.receipt_schema !== 'deadbyte.receipt.v2' ||
|
|
161
|
+
proofVerify.request_sha256 !== proof.request_sha256) throw new Error(`remote proof verify failed: ${JSON.stringify(proofVerify)}`);
|
|
162
|
+
|
|
163
|
+
if (mode === 'full') {
|
|
164
|
+
const output = `remote-contained.${randomBytes(8).toString('hex')}.txt`;
|
|
165
|
+
const exec = structured(await client.callTool({
|
|
166
|
+
name: 'contained_transform',
|
|
167
|
+
arguments: { workspace_id: 'demo', input: 'input.txt', output, operation: 'uppercase_ascii' }
|
|
168
|
+
}), 'contained_transform');
|
|
169
|
+
if (exec.status !== 'passed' || exec.output_sha256 !== '3e740f6fcf2c6659bc5f99e0706b09f3c127407bbabe74d05285d2e3276946c2' ||
|
|
170
|
+
!/^[0-9a-f]{64}$/.test(exec.request_sha256)) throw new Error(`remote contained result mismatch: ${JSON.stringify(exec)}`);
|
|
171
|
+
const verify = structured(await client.callTool({
|
|
172
|
+
name: 'evidence_verify', arguments: { workspace_id: 'demo', receipt_path: exec.receipt_path }
|
|
173
|
+
}), 'evidence_verify contained');
|
|
174
|
+
if (verify.ok !== true || verify.signature_valid !== true || verify.containment_verified !== true ||
|
|
175
|
+
verify.active_network_denial_verified !== true || verify.receipt_schema !== 'deadbyte.receipt.v2' ||
|
|
176
|
+
verify.request_sha256 !== exec.request_sha256) throw new Error(`remote contained verification failed: ${JSON.stringify(verify)}`);
|
|
177
|
+
contained = { output, receipt_sha256: exec.receipt_sha256, request_sha256: exec.request_sha256, output_sha256: exec.output_sha256 };
|
|
178
|
+
}
|
|
179
|
+
if (mode === 'full' && agentRuntime) {
|
|
180
|
+
const submissionId = `remote-gate-${randomBytes(8).toString('hex')}`;
|
|
181
|
+
const submitted = structured(await client.callTool({
|
|
182
|
+
name: 'agent_task_submit', arguments: {
|
|
183
|
+
workspace_id: 'demo', submission_id: submissionId, title: 'remote R22 gate',
|
|
184
|
+
steps: [
|
|
185
|
+
{ step_id: 'proof', kind: 'proof_execute', input: { kind: 'workspace', path: 'input.txt' }, retry_limit: 0 },
|
|
186
|
+
{ step_id: 'contained', kind: 'contained_transform', operation: 'uppercase_ascii', input: { kind: 'step_output', step_id: 'proof' }, retry_limit: 0 }
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
}), 'agent_task_submit');
|
|
190
|
+
const run = structured(await client.callTool({
|
|
191
|
+
name: 'agent_task_run', arguments: { workspace_id: 'demo', task_id: submitted.task_id, max_steps: 8 }
|
|
192
|
+
}), 'agent_task_run');
|
|
193
|
+
if (run.state !== 'succeeded' || run.completed_steps !== 2) throw new Error(`agent task run failed: ${JSON.stringify(run)}`);
|
|
194
|
+
const verified = structured(await client.callTool({
|
|
195
|
+
name: 'agent_task_verify', arguments: { workspace_id: 'demo', task_id: submitted.task_id }
|
|
196
|
+
}), 'agent_task_verify');
|
|
197
|
+
if (verified.ok !== true || verified.signature_valid !== true || verified.step_receipts_verified !== 2) {
|
|
198
|
+
throw new Error(`agent task verify failed: ${JSON.stringify(verified)}`);
|
|
134
199
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
name: 'agent_task_run', arguments: { workspace_id: 'demo', task_id: submitted.task_id, max_steps: 8 }
|
|
138
|
-
}), 'agent_task_run');
|
|
139
|
-
if (run.state !== 'succeeded' || run.completed_steps !== 2) throw new Error(`agent task run failed: ${JSON.stringify(run)}`);
|
|
140
|
-
const verified = structured(await client.callTool({
|
|
141
|
-
name: 'agent_task_verify', arguments: { workspace_id: 'demo', task_id: submitted.task_id }
|
|
142
|
-
}), 'agent_task_verify');
|
|
143
|
-
if (verified.ok !== true || verified.signature_valid !== true || verified.step_receipts_verified !== 2) {
|
|
144
|
-
throw new Error(`agent task verify failed: ${JSON.stringify(verified)}`);
|
|
200
|
+
agent = { task_id: submitted.task_id, plan_sha256: submitted.plan_sha256,
|
|
201
|
+
task_receipt_sha256: verified.task_receipt_sha256, step_receipts_verified: verified.step_receipts_verified };
|
|
145
202
|
}
|
|
146
|
-
agent = { task_id: submitted.task_id, plan_sha256: submitted.plan_sha256,
|
|
147
|
-
task_receipt_sha256: verified.task_receipt_sha256, step_receipts_verified: verified.step_receipts_verified };
|
|
148
203
|
}
|
|
149
204
|
console.log(JSON.stringify({
|
|
150
|
-
status: 'passed', mode,
|
|
205
|
+
status: 'passed', mode, profile: compactProfile ? 'compact' : 'legacy-full',
|
|
151
206
|
protocol_revision: '2026-07-28', protocol_era: 'modern', tools: names,
|
|
152
207
|
output_schemas_strict: true,
|
|
153
208
|
machine_runtime: machineRuntime, privileged_host_plane: privilegedHostPlane,
|
|
154
209
|
agent_runtime: agentRuntime, process_runtime: processRuntime,
|
|
155
210
|
machine_fs_runtime: machineFsRuntime, observation_runtime: observationRuntime,
|
|
156
211
|
coding_runtime: codingRuntime, autonomous_runtime: autonomousRuntime,
|
|
157
|
-
|
|
158
|
-
|
|
212
|
+
compact,
|
|
213
|
+
proof: proof ? { output: proofOutput, receipt_sha256: proof.receipt_sha256,
|
|
214
|
+
request_sha256: proof.request_sha256, signature_valid: true } : null,
|
|
159
215
|
contained, agent
|
|
160
216
|
}, null, 2));
|
|
161
217
|
} finally {
|
package/src/runtime-update.mjs
CHANGED
|
@@ -497,6 +497,34 @@ export function runControllerStartNoCapture(ctx,timeoutMs=45000){
|
|
|
497
497
|
const call=controllerInvocation(ctx,'start');
|
|
498
498
|
return controlResult(call.exe,call.args,{cwd:call.cwd,timeoutMs,stdio:'ignore'});
|
|
499
499
|
}
|
|
500
|
+
export async function readControllerStartFailure(ctx){
|
|
501
|
+
const file=path.join(ctx.runtime_root,'controller-start-failure.json');
|
|
502
|
+
let bytes;
|
|
503
|
+
try { bytes=await readFile(file); }
|
|
504
|
+
catch(error){ if(error?.code==='ENOENT') return null; throw error; }
|
|
505
|
+
const text=bytes.toString('utf8').replace(/^\uFEFF/,'');
|
|
506
|
+
let value;
|
|
507
|
+
try { value=JSON.parse(text); } catch(error){ throw new Error(`controller start failure evidence JSON invalid: ${error.message}`); }
|
|
508
|
+
if(value?.schema!=='deadbyte.controller-start-failure.v1'||!['start','resume','restart'].includes(String(value?.command??''))||
|
|
509
|
+
typeof value?.message!=='string'||value.message.length<1||typeof value?.failed_at_utc!=='string') {
|
|
510
|
+
throw new Error('controller start failure evidence schema invalid');
|
|
511
|
+
}
|
|
512
|
+
return {path:file,sha256:sha256Bytes(bytes),...value};
|
|
513
|
+
}
|
|
514
|
+
export async function preserveControllerStartFailure(ctx,evidenceDir){
|
|
515
|
+
const evidence=await readControllerStartFailure(ctx);
|
|
516
|
+
if(!evidence) return null;
|
|
517
|
+
const source=path.join(ctx.runtime_root,'controller-start-failure.json');
|
|
518
|
+
const bytes=await readFile(source);
|
|
519
|
+
const sourceSha256=sha256Bytes(bytes);
|
|
520
|
+
if(sourceSha256!==evidence.sha256) throw new Error('controller start failure evidence changed before preservation');
|
|
521
|
+
const destination=path.join(path.resolve(evidenceDir),'controller-start-failure.json');
|
|
522
|
+
await atomicBytes(destination,bytes);
|
|
523
|
+
const persistedSha256=sha256Bytes(await readFile(destination));
|
|
524
|
+
if(persistedSha256!==sourceSha256) throw new Error('preserved controller start failure evidence SHA-256 mismatch');
|
|
525
|
+
return {path:destination,sha256:persistedSha256,schema:evidence.schema,command:evidence.command,
|
|
526
|
+
failed_at_utc:evidence.failed_at_utc,message:evidence.message};
|
|
527
|
+
}
|
|
500
528
|
async function readLedger(ctx,name){
|
|
501
529
|
const file=path.join(ctx.runtime_root,`${name}.instance.json`);
|
|
502
530
|
const value=JSON.parse((await readFile(file,'utf8')).replace(/^\uFEFF/,''));
|
|
@@ -554,12 +582,20 @@ async function waitCoreGone(runtime,timeoutMs=10000){
|
|
|
554
582
|
}
|
|
555
583
|
async function startAndVerify(ctx,oldRuntime){
|
|
556
584
|
let start=null;
|
|
585
|
+
await rm(path.join(ctx.runtime_root,'controller-start-failure.json'),{force:true});
|
|
557
586
|
for(let attempt=1;attempt<=3;attempt+=1){
|
|
558
587
|
start=runControllerStartNoCapture(ctx,45000);
|
|
559
588
|
if(!start.error&&start.status===0) break;
|
|
560
589
|
if(attempt<3) await sleep(1500);
|
|
561
590
|
}
|
|
562
|
-
if(start.error||start.status!==0)
|
|
591
|
+
if(start.error||start.status!==0){
|
|
592
|
+
let detail='';
|
|
593
|
+
try {
|
|
594
|
+
const evidence=await readControllerStartFailure(ctx);
|
|
595
|
+
if(evidence) detail=` controller=${evidence.message} evidence_sha256=${evidence.sha256}`;
|
|
596
|
+
} catch(error) { detail=` controller_evidence_error=${error.message}`; }
|
|
597
|
+
throw new Error(`controller start transport failed status=${start.status} error=${start.error?.message??'none'}${detail}`);
|
|
598
|
+
}
|
|
563
599
|
let statusText=''; let status=null;
|
|
564
600
|
const started=Date.now();
|
|
565
601
|
while(Date.now()-started<45000){
|
|
@@ -879,6 +915,18 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
879
915
|
return {ok:true,update_id:updateId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
|
|
880
916
|
pointer:newPointer,new_runtime:newRuntime,final_authority:finalAuthority,workspace_root:workspace.target};
|
|
881
917
|
} catch(error){
|
|
918
|
+
let controllerStartFailure=null,controllerStartFailurePreservationError=null;
|
|
919
|
+
if(pointerCommitted&&newCtx){
|
|
920
|
+
try {
|
|
921
|
+
controllerStartFailure=await preserveControllerStartFailure(newCtx,evidenceDir);
|
|
922
|
+
const claimed=/evidence_sha256=([0-9a-f]{64})/.exec(String(error?.message??error));
|
|
923
|
+
if(claimed&&(!controllerStartFailure||controllerStartFailure.sha256!==claimed[1])) {
|
|
924
|
+
controllerStartFailurePreservationError=`controller start failure evidence claim mismatch expected=${claimed[1]} preserved=${controllerStartFailure?.sha256??'missing'}`;
|
|
925
|
+
}
|
|
926
|
+
} catch(preserveError) {
|
|
927
|
+
controllerStartFailurePreservationError=String(preserveError?.message??preserveError);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
882
930
|
const rollback={attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,
|
|
883
931
|
mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]};
|
|
884
932
|
try {
|
|
@@ -916,6 +964,8 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
916
964
|
} catch(rollbackError){ rollback.errors.push(`rollback:${rollbackError.message}`); }
|
|
917
965
|
const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
|
|
918
966
|
failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
|
|
967
|
+
controller_start_failure:controllerStartFailure,
|
|
968
|
+
controller_start_failure_preservation_error:controllerStartFailurePreservationError,
|
|
919
969
|
predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),rollback};
|
|
920
970
|
await writeState(statePath,journal,'failed',{failure,rollback}).catch(()=>{});
|
|
921
971
|
let failureReceipt=null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
3
|
+
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
4
|
+
|
|
5
|
+
const DOMAIN=Buffer.from('DEADBYTE-SEARCH-CURSOR-V1\0','utf8');
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const SCHEMA='deadbyte.search-cursor.v1';
|
|
8
|
+
|
|
9
|
+
function validateBody(body){
|
|
10
|
+
if(!body||typeof body!=='object'||Array.isArray(body)||body.schema!==SCHEMA) throw new Error('search cursor body invalid');
|
|
11
|
+
if(!HEX64.test(body.query_sha256??'')) throw new Error('search cursor query identity invalid');
|
|
12
|
+
if(!HEX64.test(body.snapshot_sha256??'')) throw new Error('search cursor snapshot identity invalid');
|
|
13
|
+
if(!Number.isInteger(body.offset)||body.offset<0||body.offset>1_000_000) throw new Error('search cursor offset invalid');
|
|
14
|
+
return {schema:SCHEMA,query_sha256:body.query_sha256,snapshot_sha256:body.snapshot_sha256,offset:body.offset};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createSearchCursorCodec({signingKeyPath,verifyKeyPath}){
|
|
18
|
+
if(!signingKeyPath||!verifyKeyPath) throw new Error('search cursor codec requires signing and verify keys');
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
async encode(input){
|
|
21
|
+
const body=validateBody({...input,schema:SCHEMA});
|
|
22
|
+
const signing=await loadPrivateSigningKey(signingKeyPath);
|
|
23
|
+
const bytes=Buffer.from(canonicalJson(body),'utf8');
|
|
24
|
+
const signature=cryptoSign(null,Buffer.concat([DOMAIN,bytes]),signing.privateKey).toString('base64');
|
|
25
|
+
const envelope={body,attestation:{algorithm:'ed25519',key_id:signing.keyId,signature_base64:signature}};
|
|
26
|
+
return Buffer.from(canonicalJson(envelope),'utf8').toString('base64url');
|
|
27
|
+
},
|
|
28
|
+
async decode(cursor){
|
|
29
|
+
if(typeof cursor!=='string'||cursor.length<1||cursor.length>8192) throw new Error('search cursor invalid');
|
|
30
|
+
let envelope;
|
|
31
|
+
try{envelope=JSON.parse(Buffer.from(cursor,'base64url').toString('utf8'));}catch{throw new Error('search cursor invalid');}
|
|
32
|
+
const body=validateBody(envelope?.body);
|
|
33
|
+
const verifyKey=await loadPublicVerifyKey(verifyKeyPath);
|
|
34
|
+
const att=envelope?.attestation;
|
|
35
|
+
if(att?.algorithm!=='ed25519'||att?.key_id!==verifyKey.keyId||typeof att?.signature_base64!=='string') throw new Error('search cursor attestation invalid');
|
|
36
|
+
const signature=Buffer.from(att.signature_base64,'base64');
|
|
37
|
+
if(signature.length!==64||signature.toString('base64')!==att.signature_base64) throw new Error('search cursor signature encoding invalid');
|
|
38
|
+
const bytes=Buffer.from(canonicalJson(body),'utf8');
|
|
39
|
+
if(!cryptoVerify(null,Buffer.concat([DOMAIN,bytes]),verifyKey.publicKey,signature)) throw new Error('search cursor signature invalid');
|
|
40
|
+
return body;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const SEARCH_CURSOR_SCHEMA=SCHEMA;
|