deadbyte-mcp 0.9.0 → 0.10.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 +119 -91
- package/README.txt +16 -11
- 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-verify.mjs +88 -39
- package/scripts/final-closure.mjs +63 -56
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-tests.ps1 +9 -0
- package/scripts/release-parity.mjs +149 -139
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-mcp-tools.mjs +5 -1
- package/src/autonomous-output-contracts.mjs +4 -4
- package/src/autonomous-planner-contracts.mjs +36 -1
- package/src/autonomous-planner.mjs +25 -3
- package/src/autonomous-runtime.mjs +191 -21
- 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 +51 -28
- 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-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 +76 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +7 -3
- 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/test/autonomous-capability-r36.test.mjs +151 -0
- 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 +3 -3
- 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 +52 -0
- package/test/release-version.test.mjs +18 -15
- package/test/remote-live-log-r36.test.mjs +37 -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 +40 -30
- 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,337 @@
|
|
|
1
|
+
import { createHash, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { access, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
|
+
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
|
+
import { assertAutonomousArmed } from './autonomous-policy.mjs';
|
|
8
|
+
|
|
9
|
+
export const NATIVE_DIFF_V2_SCHEMA='deadbyte.native-diff.v2';
|
|
10
|
+
export const NATIVE_DIFF_V2_PREIMAGE_SCHEMA='deadbyte.native-diff-preimage.v2';
|
|
11
|
+
export const NATIVE_DIFF_V2_EVENT_SCHEMA='deadbyte.native-diff-event.v2';
|
|
12
|
+
export const NATIVE_DIFF_V2_RECEIPT_SCHEMA='deadbyte.native-diff-receipt.v2';
|
|
13
|
+
const SHA256_RE=/^[0-9a-f]{64}$/;
|
|
14
|
+
const DIFF_ID_RE=/^[0-9a-f]{64}$/;
|
|
15
|
+
const ZERO_HASH='0'.repeat(64);
|
|
16
|
+
const DIFF_DOMAIN=Buffer.from('DEADBYTE-NATIVE-DIFF-V2\0');
|
|
17
|
+
const PREIMAGE_DOMAIN=Buffer.from('DEADBYTE-NATIVE-DIFF-PREIMAGE-V2\0');
|
|
18
|
+
const EVENT_DOMAIN=Buffer.from('DEADBYTE-NATIVE-DIFF-EVENT-V2\0');
|
|
19
|
+
const RECEIPT_DOMAIN=Buffer.from('DEADBYTE-NATIVE-DIFF-RECEIPT-V2\0');
|
|
20
|
+
const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
21
|
+
const sha256Object=value=>sha256Bytes(Buffer.from(canonicalJson(value),'utf8'));
|
|
22
|
+
const exists=file=>access(file).then(()=>true,()=>false);
|
|
23
|
+
const emptyDirectoryIdentity=()=>sha256Object({kind:'directory',entries:[]});
|
|
24
|
+
|
|
25
|
+
function signEnvelope(key,domain,body){
|
|
26
|
+
const bytes=Buffer.from(canonicalJson(body),'utf8');
|
|
27
|
+
const signature=cryptoSign(null,Buffer.concat([domain,bytes]),key.privateKey).toString('base64');
|
|
28
|
+
return {...body,attestation:{algorithm:'ed25519',key_id:key.keyId,signature_base64:signature}};
|
|
29
|
+
}
|
|
30
|
+
function verifyEnvelope(key,domain,envelope,label){
|
|
31
|
+
const {attestation,...body}=envelope??{};
|
|
32
|
+
if(attestation?.algorithm!=='ed25519'||attestation?.key_id!==key.keyId||typeof attestation?.signature_base64!=='string') throw new Error(`${label} signature metadata mismatch`);
|
|
33
|
+
const signature=Buffer.from(attestation.signature_base64,'base64');
|
|
34
|
+
if(signature.length!==64||signature.toString('base64')!==attestation.signature_base64) throw new Error(`${label} signature encoding mismatch`);
|
|
35
|
+
const bytes=Buffer.from(canonicalJson(body),'utf8');
|
|
36
|
+
if(!cryptoVerify(null,Buffer.concat([domain,bytes]),key.publicKey,signature)) throw new Error(`${label} signature verification failed`);
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
function receiptIdFor(diffId,index,phase){return sha256Object({schema:'deadbyte.native-diff-v2-operation-receipt-id.v1',diff_id:diffId,index,phase}).slice(0,24);}
|
|
40
|
+
function portable(value,label='path'){
|
|
41
|
+
if(typeof value!=='string') throw new Error(`native diff ${label} invalid`);
|
|
42
|
+
validatePortableRelativePath(value);
|
|
43
|
+
return value.replaceAll('\\','/');
|
|
44
|
+
}
|
|
45
|
+
function decodeWriteContent(content,encoding){
|
|
46
|
+
if(typeof content!=='string') throw new Error('native diff write content must be string');
|
|
47
|
+
if(encoding==='utf8') return Buffer.from(content,'utf8');
|
|
48
|
+
if(encoding==='base64'&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(content)) return Buffer.from(content,'base64');
|
|
49
|
+
throw new Error('native diff write encoding invalid');
|
|
50
|
+
}
|
|
51
|
+
function normalizeRawOperations(operations,policy){
|
|
52
|
+
if(!Array.isArray(operations)||operations.length<1||operations.length>policy.limits.max_files_per_diff) throw new Error(`native diff operations must contain 1-${policy.limits.max_files_per_diff} entries`);
|
|
53
|
+
let payloadBytes=0; const claimed=new Set();
|
|
54
|
+
const claim=(value,label)=>{const key=value.toLowerCase();if(claimed.has(key))throw new Error(`native diff topology path repeated '${value}' (${label})`);claimed.add(key);};
|
|
55
|
+
const normalized=operations.map((raw,index)=>{
|
|
56
|
+
if(!raw||typeof raw!=='object'||Array.isArray(raw)) throw new Error(`native diff operation ${index} malformed`);
|
|
57
|
+
if(raw.kind==='move'){
|
|
58
|
+
const source_path=portable(raw.source_path,'source_path'); const destination_path=portable(raw.destination_path,'destination_path');
|
|
59
|
+
if(source_path.toLowerCase()===destination_path.toLowerCase()) throw new Error('native diff move source and destination must differ');
|
|
60
|
+
claim(source_path,'move source'); claim(destination_path,'move destination');
|
|
61
|
+
if(!SHA256_RE.test(raw.expected_source_identity??'')) throw new Error('native diff move expected_source_identity invalid');
|
|
62
|
+
const expected_destination_identity=raw.expected_destination_identity??'absent';
|
|
63
|
+
if(expected_destination_identity!=='absent'&&!SHA256_RE.test(expected_destination_identity)) throw new Error('native diff move expected_destination_identity invalid');
|
|
64
|
+
if(expected_destination_identity!=='absent') throw new Error('native diff V2 existing-destination replacement is not allowed; use an explicit preconditioned removal before move');
|
|
65
|
+
return {kind:'move',source_path,destination_path,expected_source_identity:raw.expected_source_identity,expected_destination_identity};
|
|
66
|
+
}
|
|
67
|
+
const pathValue=portable(raw.path);
|
|
68
|
+
claim(pathValue,raw.kind??'unknown');
|
|
69
|
+
if(raw.kind==='mkdir') return {kind:'mkdir',path:pathValue,expected:'absent'};
|
|
70
|
+
if(raw.kind==='rmdir'){
|
|
71
|
+
if(!SHA256_RE.test(raw.expected_source_identity??'')) throw new Error('native diff rmdir expected_source_identity invalid');
|
|
72
|
+
return {kind:'rmdir',path:pathValue,expected_source_identity:raw.expected_source_identity};
|
|
73
|
+
}
|
|
74
|
+
if(raw.kind==='write'){
|
|
75
|
+
const encoding=raw.encoding??'utf8'; const bytes=decodeWriteContent(raw.content,encoding); payloadBytes+=bytes.length;
|
|
76
|
+
if(raw.expected_sha256!=='absent'&&!SHA256_RE.test(raw.expected_sha256??'')) throw new Error('native diff write expected_sha256 invalid');
|
|
77
|
+
return {kind:'write',path:pathValue,content:raw.content,encoding,expected_sha256:raw.expected_sha256,content_sha256:sha256Bytes(bytes)};
|
|
78
|
+
}
|
|
79
|
+
if(raw.kind==='replace'){
|
|
80
|
+
if(!SHA256_RE.test(raw.expected_sha256??'')) throw new Error('native diff replace expected_sha256 invalid');
|
|
81
|
+
if(typeof raw.old_string!=='string'||raw.old_string.length<1||raw.old_string.length>262144) throw new Error('native diff old_string invalid');
|
|
82
|
+
if(typeof raw.new_string!=='string'||raw.new_string.length>524288) throw new Error('native diff new_string invalid');
|
|
83
|
+
const expected_replacements=raw.expected_replacements??1;
|
|
84
|
+
if(!Number.isInteger(expected_replacements)||expected_replacements<1||expected_replacements>64) throw new Error('native diff expected_replacements invalid');
|
|
85
|
+
payloadBytes+=Buffer.byteLength(raw.old_string)+Buffer.byteLength(raw.new_string);
|
|
86
|
+
return {kind:'replace',path:pathValue,expected_sha256:raw.expected_sha256,old_string:raw.old_string,new_string:raw.new_string,expected_replacements,old_sha256:sha256Bytes(Buffer.from(raw.old_string)),new_sha256:sha256Bytes(Buffer.from(raw.new_string))};
|
|
87
|
+
}
|
|
88
|
+
if(raw.kind==='delete'){
|
|
89
|
+
if(!SHA256_RE.test(raw.expected_sha256??'')) throw new Error('native diff delete expected_sha256 invalid');
|
|
90
|
+
return {kind:'delete',path:pathValue,expected_sha256:raw.expected_sha256};
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`native diff operation ${index} kind invalid`);
|
|
93
|
+
});
|
|
94
|
+
if(payloadBytes>policy.limits.max_diff_bytes) throw new Error(`native diff payload exceeds ${policy.limits.max_diff_bytes} bytes`);
|
|
95
|
+
return {normalized,payloadBytes};
|
|
96
|
+
}
|
|
97
|
+
function diffContext(policy,diffId){
|
|
98
|
+
if(!DIFF_ID_RE.test(diffId??'')) throw new Error('invalid native diff_id');
|
|
99
|
+
const dir=path.join(policy.evidence_dir,'native-diffs',diffId);
|
|
100
|
+
return {dir,diffPath:path.join(dir,'diff.json'),preimagePath:path.join(dir,'preimage.json'),eventsDir:path.join(dir,'events'),receiptPath:path.join(dir,'apply-receipt.json')};
|
|
101
|
+
}
|
|
102
|
+
async function writeExclusive(file,value){await writeFile(file,`${canonicalJson(value)}\n`,{flag:'wx'});}
|
|
103
|
+
async function readEnvelope(file,key,domain,label){const parsed=JSON.parse(await readFile(file,'utf8'));verifyEnvelope(key,domain,parsed,label);return parsed;}
|
|
104
|
+
|
|
105
|
+
export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime,signingKeyPath,verifyKeyPath}){
|
|
106
|
+
if(!policy||!codingRuntime?.policy) throw new Error('native diff V2 requires autonomous and coding policy');
|
|
107
|
+
if(!machineFsRuntime?.state||!machineFsRuntime?.removeEmptyDir) throw new Error('native diff V2 requires machine fs topology runtime');
|
|
108
|
+
if(!signingKeyPath||!verifyKeyPath) throw new Error('native diff V2 requires trust keys');
|
|
109
|
+
|
|
110
|
+
async function materializeOperations(rootId,rawOperations){
|
|
111
|
+
if(!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
|
|
112
|
+
await assertAutonomousArmed(policy,rootId);
|
|
113
|
+
const {normalized,payloadBytes}=normalizeRawOperations(rawOperations,policy);
|
|
114
|
+
const operations=[];
|
|
115
|
+
for(const op of normalized){
|
|
116
|
+
if(op.kind==='mkdir'){
|
|
117
|
+
const state=await machineFsRuntime.state({rootId,relativePath:op.path});
|
|
118
|
+
if(state.exists) throw new Error(`native diff mkdir precondition '${op.path}' expected absent`);
|
|
119
|
+
operations.push({...op,before_identity_sha256:null,after_identity_sha256:emptyDirectoryIdentity()});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if(op.kind==='move'){
|
|
123
|
+
const source=await machineFsRuntime.state({rootId,relativePath:op.source_path});
|
|
124
|
+
const destination=await machineFsRuntime.state({rootId,relativePath:op.destination_path});
|
|
125
|
+
if(!source.exists||source.identity_sha256!==op.expected_source_identity) throw new Error(`native diff move source identity precondition mismatch '${op.source_path}'`);
|
|
126
|
+
if(destination.exists) throw new Error(`native diff move destination precondition expected absent '${op.destination_path}'`);
|
|
127
|
+
operations.push({...op,source_kind:source.kind,before_source_identity_sha256:source.identity_sha256,before_destination_identity_sha256:null,after_source_identity_sha256:null,after_destination_identity_sha256:source.identity_sha256});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if(op.kind==='rmdir'){
|
|
131
|
+
const state=await machineFsRuntime.state({rootId,relativePath:op.path});
|
|
132
|
+
if(!state.exists||state.kind!=='directory'||state.identity_sha256!==op.expected_source_identity) throw new Error(`native diff rmdir identity precondition mismatch '${op.path}'`);
|
|
133
|
+
const page=await machineFsRuntime.list({rootId,relativePath:op.path,limit:1});
|
|
134
|
+
if(page.entries.length!==0) throw new Error(`native diff rmdir requires empty directory '${op.path}'`);
|
|
135
|
+
operations.push({...op,before_identity_sha256:state.identity_sha256,after_identity_sha256:null});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const current=await codingRuntime.fileState({rootId,relativePath:op.path});
|
|
139
|
+
if(op.expected_sha256==='absent'){
|
|
140
|
+
if(current.exists) throw new Error(`native diff precondition '${op.path}' expected absent, found ${current.sha256}`);
|
|
141
|
+
}else if(!current.exists||current.sha256!==op.expected_sha256) throw new Error(`native diff precondition '${op.path}' mismatch`);
|
|
142
|
+
if(op.kind==='write') operations.push({...op,before_sha256:current.sha256,after_sha256:op.content_sha256});
|
|
143
|
+
else if(op.kind==='delete') operations.push({...op,before_sha256:current.sha256,after_sha256:null});
|
|
144
|
+
else{
|
|
145
|
+
const read=await codingRuntime.fileRead({rootId,relativePath:op.path,encoding:'utf8'});
|
|
146
|
+
const count=read.content.split(op.old_string).length-1;
|
|
147
|
+
if(count!==op.expected_replacements) throw new Error(`native diff replace '${op.path}' expected ${op.expected_replacements} matches, found ${count}`);
|
|
148
|
+
const next=read.content.split(op.old_string).join(op.new_string);
|
|
149
|
+
operations.push({...op,before_sha256:current.sha256,after_sha256:sha256Bytes(Buffer.from(next,'utf8'))});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const finalPresentPaths=[];
|
|
153
|
+
for(const op of operations){
|
|
154
|
+
if(['write','replace','mkdir'].includes(op.kind)) finalPresentPaths.push(op.path);
|
|
155
|
+
else if(op.kind==='move') finalPresentPaths.push(op.destination_path);
|
|
156
|
+
}
|
|
157
|
+
const finalized=operations.map(op=>{
|
|
158
|
+
if(op.kind!=='mkdir') return op;
|
|
159
|
+
const terminal_children=finalPresentPaths
|
|
160
|
+
.filter(candidate=>candidate!==op.path&&path.posix.dirname(candidate)===op.path)
|
|
161
|
+
.map(candidate=>path.posix.basename(candidate))
|
|
162
|
+
.sort((a,b)=>a.localeCompare(b,'en'));
|
|
163
|
+
return {...op,terminal_children};
|
|
164
|
+
});
|
|
165
|
+
return {operations:finalized,payloadBytes};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function preview({rootId,operations:rawOperations,title='',applicationScope=null}){
|
|
169
|
+
if(typeof title!=='string'||title.length>160) throw new Error('native diff title must be <=160 characters');
|
|
170
|
+
if(applicationScope!==null&&!SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
|
|
171
|
+
const {operations,payloadBytes}=await materializeOperations(rootId,rawOperations);
|
|
172
|
+
const identity={schema:NATIVE_DIFF_V2_SCHEMA,policy_sha256:policy.policy_sha256,coding_policy_sha256:policy.coding_policy_sha256,root_id:rootId,title,operations,...(applicationScope===null?{}:{application_scope:applicationScope})};
|
|
173
|
+
const diffId=sha256Object(identity); const ctx=diffContext(policy,diffId); const signing=await loadPrivateSigningKey(signingKeyPath);
|
|
174
|
+
const envelope=signEnvelope(signing,DIFF_DOMAIN,{...identity,diff_id:diffId,created_at_utc:new Date().toISOString()});
|
|
175
|
+
await mkdir(ctx.dir,{recursive:true}); await mkdir(ctx.eventsDir,{recursive:true});
|
|
176
|
+
if(await exists(ctx.diffPath)){
|
|
177
|
+
const verifyKey=await loadPublicVerifyKey(verifyKeyPath); const existing=await readEnvelope(ctx.diffPath,verifyKey,DIFF_DOMAIN,'native diff V2');
|
|
178
|
+
const {created_at_utc:_a,attestation:_b,...a}=existing; const {created_at_utc:_c,attestation:_d,...b}=envelope;
|
|
179
|
+
if(canonicalJson(a)!==canonicalJson(b)) throw new Error('native diff V2 id collision');
|
|
180
|
+
}else await writeExclusive(ctx.diffPath,envelope);
|
|
181
|
+
return {status:'ready',schema:NATIVE_DIFF_V2_SCHEMA,diff_id:diffId,root_id:rootId,operation_count:operations.length,payload_bytes:payloadBytes,
|
|
182
|
+
paths:operations.flatMap(op=>op.kind==='move'?[op.source_path,op.destination_path]:[op.path]),
|
|
183
|
+
operations:operations.map(({content,old_string,new_string,...op})=>op)};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function loadDiff(diffId){
|
|
187
|
+
const ctx=diffContext(policy,diffId); const verifyKey=await loadPublicVerifyKey(verifyKeyPath); const envelope=await readEnvelope(ctx.diffPath,verifyKey,DIFF_DOMAIN,'native diff V2');
|
|
188
|
+
const {attestation:_a,created_at_utc:_b,diff_id:claimed,...identity}=envelope;
|
|
189
|
+
if(claimed!==diffId||identity.schema!==NATIVE_DIFF_V2_SCHEMA||identity.policy_sha256!==policy.policy_sha256||identity.coding_policy_sha256!==policy.coding_policy_sha256||sha256Object(identity)!==diffId) throw new Error('native diff V2 identity/hash mismatch');
|
|
190
|
+
return {ctx,envelope,identity};
|
|
191
|
+
}
|
|
192
|
+
async function readEvents(ctx){
|
|
193
|
+
const verifyKey=await loadPublicVerifyKey(verifyKeyPath); const names=(await readdir(ctx.eventsDir)).filter(x=>/^\d{6}\.json$/.test(x)).sort(); const events=[]; let previous=ZERO_HASH;
|
|
194
|
+
for(let i=0;i<names.length;i+=1){const expected=`${String(i+1).padStart(6,'0')}.json`;if(names[i]!==expected)throw new Error(`native diff V2 event gap at ${expected}`);const event=await readEnvelope(path.join(ctx.eventsDir,names[i]),verifyKey,EVENT_DOMAIN,'native diff V2 event');const {attestation:_a,event_sha256:claimed,...body}=event;if(event.schema!==NATIVE_DIFF_V2_EVENT_SCHEMA||event.seq!==i+1||event.prev_sha256!==previous||!SHA256_RE.test(claimed??'')||sha256Object(body)!==claimed)throw new Error(`native diff V2 event ${expected} invalid`);events.push(event);previous=claimed;}
|
|
195
|
+
return events;
|
|
196
|
+
}
|
|
197
|
+
async function appendEvent(ctx,events,diffId,type,payload={}){
|
|
198
|
+
const body={schema:NATIVE_DIFF_V2_EVENT_SCHEMA,diff_id:diffId,seq:events.length+1,created_at_utc:new Date().toISOString(),prev_sha256:events.at(-1)?.event_sha256??ZERO_HASH,type,payload};
|
|
199
|
+
const signing=await loadPrivateSigningKey(signingKeyPath); const event=signEnvelope(signing,EVENT_DOMAIN,{...body,event_sha256:sha256Object(body)}); await writeExclusive(path.join(ctx.eventsDir,`${String(event.seq).padStart(6,'0')}.json`),event);events.push(event);return event;
|
|
200
|
+
}
|
|
201
|
+
async function snapshotFor(rootId,op){
|
|
202
|
+
if(['write','replace','delete'].includes(op.kind)){
|
|
203
|
+
const state=await codingRuntime.fileState({rootId,relativePath:op.path});
|
|
204
|
+
if(state.exists){const read=await codingRuntime.fileRead({rootId,relativePath:op.path,encoding:'base64'});return {kind:op.kind,path:op.path,existed:true,sha256:state.sha256,bytes:read.bytes,content_base64:read.content};}
|
|
205
|
+
return {kind:op.kind,path:op.path,existed:false,sha256:null,bytes:0,content_base64:null};
|
|
206
|
+
}
|
|
207
|
+
if(op.kind==='move'){
|
|
208
|
+
const source=await machineFsRuntime.state({rootId,relativePath:op.source_path}); const destination=await machineFsRuntime.state({rootId,relativePath:op.destination_path});
|
|
209
|
+
return {kind:'move',source_path:op.source_path,destination_path:op.destination_path,source_exists:source.exists,source_identity_sha256:source.identity_sha256,destination_exists:destination.exists,destination_identity_sha256:destination.identity_sha256};
|
|
210
|
+
}
|
|
211
|
+
const state=await machineFsRuntime.state({rootId,relativePath:op.path});
|
|
212
|
+
return {kind:op.kind,path:op.path,exists:state.exists,object_kind:state.kind,identity_sha256:state.identity_sha256};
|
|
213
|
+
}
|
|
214
|
+
async function snapshotMatchesBefore(rootId,op,snap){
|
|
215
|
+
if(['write','replace','delete'].includes(op.kind)){
|
|
216
|
+
const state=await codingRuntime.fileState({rootId,relativePath:op.path}); return snap.existed?state.exists&&state.sha256===snap.sha256:!state.exists;
|
|
217
|
+
}
|
|
218
|
+
if(op.kind==='move'){
|
|
219
|
+
const s=await machineFsRuntime.state({rootId,relativePath:op.source_path}); const d=await machineFsRuntime.state({rootId,relativePath:op.destination_path});
|
|
220
|
+
return s.exists===snap.source_exists&&s.identity_sha256===snap.source_identity_sha256&&d.exists===snap.destination_exists&&d.identity_sha256===snap.destination_identity_sha256;
|
|
221
|
+
}
|
|
222
|
+
const s=await machineFsRuntime.state({rootId,relativePath:op.path}); return s.exists===snap.exists&&s.identity_sha256===snap.identity_sha256;
|
|
223
|
+
}
|
|
224
|
+
async function ensurePreimage(ctx,diffId,identity){
|
|
225
|
+
const verifyKey=await loadPublicVerifyKey(verifyKeyPath);
|
|
226
|
+
if(await exists(ctx.preimagePath)){const stored=await readEnvelope(ctx.preimagePath,verifyKey,PREIMAGE_DOMAIN,'native diff V2 preimage');const {attestation:_a,preimage_sha256:claimed,...body}=stored;if(body.schema!==NATIVE_DIFF_V2_PREIMAGE_SCHEMA||body.diff_id!==diffId||!SHA256_RE.test(claimed??'')||sha256Object(body)!==claimed)throw new Error('native diff V2 preimage hash mismatch');return stored;}
|
|
227
|
+
const snapshots=[]; for(const op of identity.operations){const snap=await snapshotFor(identity.root_id,op);if(!(await snapshotMatchesBefore(identity.root_id,op,snap)))throw new Error('native diff V2 preimage changed while capturing');snapshots.push(snap);}
|
|
228
|
+
const body={schema:NATIVE_DIFF_V2_PREIMAGE_SCHEMA,diff_id:diffId,policy_sha256:policy.policy_sha256,root_id:identity.root_id,created_at_utc:new Date().toISOString(),snapshots};
|
|
229
|
+
const signing=await loadPrivateSigningKey(signingKeyPath); const envelope=signEnvelope(signing,PREIMAGE_DOMAIN,{...body,preimage_sha256:sha256Object(body)}); await writeExclusive(ctx.preimagePath,envelope); return envelope;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function fileStateMatches(rootId,op,which){const state=await codingRuntime.fileState({rootId,relativePath:op.path});const expected=which==='before'?op.before_sha256:op.after_sha256;return expected===null?!state.exists:state.exists&&state.sha256===expected;}
|
|
233
|
+
async function topologyStateMatches(rootId,op,which){
|
|
234
|
+
if(op.kind==='move'){
|
|
235
|
+
const s=await machineFsRuntime.state({rootId,relativePath:op.source_path}); const d=await machineFsRuntime.state({rootId,relativePath:op.destination_path});
|
|
236
|
+
if(which==='before') return s.exists&&s.identity_sha256===op.before_source_identity_sha256&&!d.exists;
|
|
237
|
+
return !s.exists&&d.exists&&d.identity_sha256===op.after_destination_identity_sha256;
|
|
238
|
+
}
|
|
239
|
+
const s=await machineFsRuntime.state({rootId,relativePath:op.path}); const expected=which==='before'?op.before_identity_sha256:op.after_identity_sha256;
|
|
240
|
+
return expected===null?!s.exists:s.exists&&s.identity_sha256===expected;
|
|
241
|
+
}
|
|
242
|
+
const stateMatches=(rootId,op,which)=>['write','replace','delete'].includes(op.kind)?fileStateMatches(rootId,op,which):topologyStateMatches(rootId,op,which);
|
|
243
|
+
async function terminalStateMatches(rootId,op){
|
|
244
|
+
if(op.kind!=='mkdir') return stateMatches(rootId,op,'after');
|
|
245
|
+
const state=await machineFsRuntime.state({rootId,relativePath:op.path});
|
|
246
|
+
if(!state.exists||state.kind!=='directory') return false;
|
|
247
|
+
const names=[]; let cursor=null;
|
|
248
|
+
do{
|
|
249
|
+
const page=await machineFsRuntime.list({rootId,relativePath:op.path,limit:1000,cursor});
|
|
250
|
+
names.push(...page.entries.map(item=>path.posix.basename(item.path)));
|
|
251
|
+
cursor=page.next_cursor;
|
|
252
|
+
if(names.length>4096) return false;
|
|
253
|
+
}while(cursor!==null);
|
|
254
|
+
names.sort((a,b)=>a.localeCompare(b,'en'));
|
|
255
|
+
const expected=[...(op.terminal_children??[])].sort((a,b)=>a.localeCompare(b,'en'));
|
|
256
|
+
return canonicalJson(names)===canonicalJson(expected);
|
|
257
|
+
}
|
|
258
|
+
const receiptClass=op=>['mkdir','move','rmdir'].includes(op.kind)?'machine_fs':'coding';
|
|
259
|
+
async function verifyOperationReceipt(op,receiptId){return receiptClass(op)==='coding'?codingRuntime.verifyReceiptDetailed(receiptId):machineFsRuntime.verifyReceipt(receiptId);}
|
|
260
|
+
async function executeOperation(diffId,rootId,op,index,phase='apply'){
|
|
261
|
+
const receiptId=receiptIdFor(diffId,index,phase); const existing=await verifyOperationReceipt(op,receiptId);
|
|
262
|
+
if(existing.ok) return {recovered:true,receipt_id:receiptId,receipt_sha256:existing.receipt_sha256,request_sha256:existing.request_sha256,operation:existing.operation,receipt_class:receiptClass(op)};
|
|
263
|
+
let result;
|
|
264
|
+
if(op.kind==='write') result=await codingRuntime.fileWrite({rootId,relativePath:op.path,content:op.content,encoding:op.encoding,expectedSha256:op.expected_sha256,receiptId});
|
|
265
|
+
else if(op.kind==='replace') result=await codingRuntime.textReplace({rootId,relativePath:op.path,oldString:op.old_string,newString:op.new_string,expectedSha256:op.expected_sha256,expectedReplacements:op.expected_replacements,receiptId});
|
|
266
|
+
else if(op.kind==='delete') result=await codingRuntime.fileDelete({rootId,relativePath:op.path,expectedSha256:op.expected_sha256,receiptId});
|
|
267
|
+
else if(op.kind==='mkdir') result=await machineFsRuntime.mkdir({rootId,relativePath:op.path,receiptId});
|
|
268
|
+
else if(op.kind==='move') result=await machineFsRuntime.move({rootId,sourcePath:op.source_path,destinationPath:op.destination_path,expectedSourceIdentity:op.expected_source_identity,receiptId});
|
|
269
|
+
else result=await machineFsRuntime.removeEmptyDir({rootId,relativePath:op.path,expectedSourceIdentity:op.expected_source_identity,receiptId});
|
|
270
|
+
const checked=await verifyOperationReceipt(op,receiptId); if(!checked.ok||checked.receipt_sha256!==result.receipt_sha256||checked.request_sha256!==result.request_sha256)throw new Error(`native diff V2 operation receipt verification failed index ${index}`);
|
|
271
|
+
return {recovered:false,receipt_id:receiptId,receipt_sha256:checked.receipt_sha256,request_sha256:checked.request_sha256,operation:checked.operation,receipt_class:receiptClass(op)};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function rollbackToPreimage(diffId,identity,preimage){
|
|
275
|
+
const receipts=[];
|
|
276
|
+
for(let index=identity.operations.length-1;index>=0;index-=1){
|
|
277
|
+
const op=identity.operations[index]; const snap=preimage.snapshots[index];
|
|
278
|
+
if(await snapshotMatchesBefore(identity.root_id,op,snap)) continue;
|
|
279
|
+
if(!(await stateMatches(identity.root_id,op,'after'))) throw new Error(`native diff V2 rollback_conflict index ${index}`);
|
|
280
|
+
const receiptId=receiptIdFor(diffId,index,'rollback'); let result; let klass=receiptClass(op);
|
|
281
|
+
if(['write','replace','delete'].includes(op.kind)){
|
|
282
|
+
const current=await codingRuntime.fileState({rootId:identity.root_id,relativePath:op.path});
|
|
283
|
+
if(snap.existed) result=await codingRuntime.fileWrite({rootId:identity.root_id,relativePath:op.path,content:snap.content_base64,encoding:'base64',expectedSha256:current.exists?current.sha256:'absent',receiptId});
|
|
284
|
+
else result=await codingRuntime.fileDelete({rootId:identity.root_id,relativePath:op.path,expectedSha256:current.sha256,receiptId});
|
|
285
|
+
}else if(op.kind==='mkdir'){
|
|
286
|
+
result=await machineFsRuntime.removeEmptyDir({rootId:identity.root_id,relativePath:op.path,expectedSourceIdentity:op.after_identity_sha256,receiptId});
|
|
287
|
+
}else if(op.kind==='move'){
|
|
288
|
+
result=await machineFsRuntime.move({rootId:identity.root_id,sourcePath:op.destination_path,destinationPath:op.source_path,expectedSourceIdentity:op.after_destination_identity_sha256,receiptId});
|
|
289
|
+
}else{
|
|
290
|
+
result=await machineFsRuntime.mkdir({rootId:identity.root_id,relativePath:op.path,receiptId});
|
|
291
|
+
const restored=await machineFsRuntime.state({rootId:identity.root_id,relativePath:op.path});
|
|
292
|
+
if(!restored.exists||restored.identity_sha256!==snap.identity_sha256) throw new Error(`native diff V2 rmdir rollback identity mismatch '${op.path}'`);
|
|
293
|
+
}
|
|
294
|
+
receipts.push({index,receipt_class:klass,receipt_id:receiptId,receipt_sha256:result.receipt_sha256});
|
|
295
|
+
}
|
|
296
|
+
return receipts;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function createApplyReceipt(ctx,diffId,identity,preimage,events){
|
|
300
|
+
const committed=events.filter(e=>e.type==='operation_committed'); if(committed.length!==identity.operations.length)throw new Error('native diff V2 cannot receipt incomplete transaction');
|
|
301
|
+
const finalStates=[];
|
|
302
|
+
for(const op of identity.operations){
|
|
303
|
+
if(!(await terminalStateMatches(identity.root_id,op))) throw new Error(`native diff V2 final state mismatch '${op.path??op.source_path}'`);
|
|
304
|
+
if(op.kind==='move') finalStates.push({kind:'move',source_path:op.source_path,destination_path:op.destination_path,source_exists:false,destination_identity_sha256:op.after_destination_identity_sha256});
|
|
305
|
+
else if(op.kind==='mkdir') finalStates.push({kind:'mkdir',path:op.path,exists:true,terminal_children:op.terminal_children??[]});
|
|
306
|
+
else finalStates.push({kind:op.kind,path:op.path,exists:op.kind!=='delete'&&op.kind!=='rmdir',sha256:op.after_sha256??null,identity_sha256:op.after_identity_sha256??null});
|
|
307
|
+
}
|
|
308
|
+
const body={schema:NATIVE_DIFF_V2_RECEIPT_SCHEMA,diff_id:diffId,policy_sha256:policy.policy_sha256,coding_policy_sha256:policy.coding_policy_sha256,preimage_sha256:preimage.preimage_sha256,event_head_sha256:events.at(-1)?.event_sha256??ZERO_HASH,completed_at_utc:new Date().toISOString(),operation_receipts:committed.map(e=>({index:e.payload.index,kind:e.payload.kind,receipt_class:e.payload.receipt_class,receipt_id:e.payload.receipt_id,receipt_sha256:e.payload.receipt_sha256,request_sha256:e.payload.request_sha256})),final_states:finalStates};
|
|
309
|
+
const receiptSha=sha256Object(body); const signing=await loadPrivateSigningKey(signingKeyPath); const receipt=signEnvelope(signing,RECEIPT_DOMAIN,{...body,receipt_sha256:receiptSha}); await writeExclusive(ctx.receiptPath,receipt); await appendEvent(ctx,events,diffId,'transaction_completed',{receipt_sha256:receiptSha}); return receipt;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function apply({diffId,expectedOperationCount=null}){
|
|
313
|
+
const {ctx,identity}=await loadDiff(diffId);
|
|
314
|
+
if(expectedOperationCount!==null&&(!Number.isInteger(expectedOperationCount)||expectedOperationCount<1||expectedOperationCount>policy.limits.max_files_per_diff||identity.operations.length!==expectedOperationCount))throw new Error(`native diff V2 operation count mismatch: expected ${String(expectedOperationCount)}, signed ${identity.operations.length}`);
|
|
315
|
+
await assertAutonomousArmed(policy,identity.root_id); const codingStatus=await codingRuntime.sessionStatus(); if(!codingStatus.armed)throw new Error(`coding plane is disarmed: ${codingStatus.reason}`);
|
|
316
|
+
if(await exists(ctx.receiptPath)){const checked=await verify({diffId});if(!checked.ok)throw new Error(`existing native diff V2 receipt invalid: ${checked.reason}`);return {status:'existing',diff_id:diffId,receipt_sha256:checked.receipt_sha256,operation_count:identity.operations.length,current_state_matches:checked.current_state_matches};}
|
|
317
|
+
const preimage=await ensurePreimage(ctx,diffId,identity); const events=await readEvents(ctx); if(!events.some(e=>e.type==='transaction_started'))await appendEvent(ctx,events,diffId,'transaction_started',{operation_count:identity.operations.length,preimage_sha256:preimage.preimage_sha256});
|
|
318
|
+
try{
|
|
319
|
+
for(let index=0;index<identity.operations.length;index+=1){const op=identity.operations[index];const prior=events.find(e=>e.type==='operation_committed'&&e.payload?.index===index);if(prior){if(!(await stateMatches(identity.root_id,op,'after')))throw new Error(`native diff V2 committed state changed index ${index}`);continue;}let executed;if(await stateMatches(identity.root_id,op,'after')){const receiptId=receiptIdFor(diffId,index,'apply');const checked=await verifyOperationReceipt(op,receiptId);if(!checked.ok)throw new Error(`native diff V2 unreceipted mutation index ${index}`);executed={recovered:true,receipt_id:receiptId,receipt_sha256:checked.receipt_sha256,request_sha256:checked.request_sha256,operation:checked.operation,receipt_class:receiptClass(op)};}else if(await stateMatches(identity.root_id,op,'before')) executed=await executeOperation(diffId,identity.root_id,op,index);else throw new Error(`native diff V2 concurrent state conflict index ${index}`);if(!(await stateMatches(identity.root_id,op,'after')))throw new Error(`native diff V2 post-operation mismatch index ${index}`);await appendEvent(ctx,events,diffId,'operation_committed',{index,kind:op.kind,path:op.path??null,source_path:op.source_path??null,destination_path:op.destination_path??null,receipt_class:executed.receipt_class,receipt_id:executed.receipt_id,receipt_sha256:executed.receipt_sha256,request_sha256:executed.request_sha256,recovered:executed.recovered});}
|
|
320
|
+
}catch(error){try{const rollbackReceipts=await rollbackToPreimage(diffId,identity,preimage);await appendEvent(ctx,events,diffId,'transaction_rolled_back',{reason:String(error).slice(0,2048),rollback_receipts:rollbackReceipts});}catch(rollbackError){await appendEvent(ctx,events,diffId,'transaction_paused',{reason:String(error).slice(0,1024),rollback_error:String(rollbackError).slice(0,1024)}).catch(()=>{});throw new Error(`native diff V2 apply failed and rollback incomplete: ${String(error)}; ${String(rollbackError)}`);}throw new Error(`native diff V2 apply rolled back: ${String(error)}`);}
|
|
321
|
+
const receipt=await createApplyReceipt(ctx,diffId,identity,preimage,events);return {status:'committed',diff_id:diffId,receipt_sha256:receipt.receipt_sha256,operation_count:identity.operations.length,operation_receipts:receipt.operation_receipts.map(x=>({index:x.index,kind:x.kind,receipt_class:x.receipt_class,receipt_id:x.receipt_id,receipt_sha256:x.receipt_sha256}))};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function verify({diffId}){
|
|
325
|
+
try{
|
|
326
|
+
const {ctx,identity}=await loadDiff(diffId);const verifyKey=await loadPublicVerifyKey(verifyKeyPath);const preimage=await readEnvelope(ctx.preimagePath,verifyKey,PREIMAGE_DOMAIN,'native diff V2 preimage');const events=await readEvents(ctx);const receipt=await readEnvelope(ctx.receiptPath,verifyKey,RECEIPT_DOMAIN,'native diff V2 receipt');const {attestation:_a,receipt_sha256:claimed,...body}=receipt;
|
|
327
|
+
if(body.schema!==NATIVE_DIFF_V2_RECEIPT_SCHEMA||body.diff_id!==diffId||body.policy_sha256!==policy.policy_sha256||body.coding_policy_sha256!==policy.coding_policy_sha256||!SHA256_RE.test(claimed??'')||sha256Object(body)!==claimed)return {ok:false,reason:'native diff V2 receipt identity/hash mismatch'};
|
|
328
|
+
const completion=events.at(-1);if(completion?.type!=='transaction_completed'||completion.prev_sha256!==receipt.event_head_sha256||completion.payload?.receipt_sha256!==claimed||receipt.preimage_sha256!==preimage.preimage_sha256)return {ok:false,reason:'native diff V2 receipt/event/preimage binding mismatch'};
|
|
329
|
+
if(!Array.isArray(receipt.operation_receipts)||receipt.operation_receipts.length!==identity.operations.length)return {ok:false,reason:'native diff V2 operation receipt count mismatch'};
|
|
330
|
+
let verified=0,codingVerified=0;for(let index=0;index<identity.operations.length;index+=1){const op=identity.operations[index];const bound=receipt.operation_receipts.find(x=>x.index===index);if(!bound||bound.kind!==op.kind||bound.receipt_class!==receiptClass(op))return {ok:false,reason:`native diff V2 receipt binding mismatch index ${index}`};const checked=await verifyOperationReceipt(op,bound.receipt_id);if(!checked.ok||checked.receipt_sha256!==bound.receipt_sha256||checked.request_sha256!==bound.request_sha256)return {ok:false,reason:`native diff V2 operation receipt invalid index ${index}`};verified+=1;if(bound.receipt_class==='coding')codingVerified+=1;}
|
|
331
|
+
let currentMatches=true;for(const op of identity.operations)if(!(await terminalStateMatches(identity.root_id,op)))currentMatches=false;
|
|
332
|
+
return {ok:true,diff_id:diffId,receipt_sha256:claimed,signature_valid:true,root_id:identity.root_id,operation_count:identity.operations.length,coding_receipts_verified:codingVerified,operation_receipts_verified:verified,final_states:receipt.final_states,current_state_matches:currentMatches,journal_head_sha256:completion.event_sha256};
|
|
333
|
+
}catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return Object.freeze({preview,apply,verify,policy});
|
|
337
|
+
}
|
package/src/native-diff.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { canonicalJson } from './canonical-json.mjs';
|
|
|
5
5
|
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
6
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
7
|
import { assertAutonomousArmed } from './autonomous-policy.mjs';
|
|
8
|
+
import { createNativeDiffV2Runtime, NATIVE_DIFF_V2_SCHEMA } from './native-diff-v2.mjs';
|
|
8
9
|
|
|
9
10
|
export const NATIVE_DIFF_SCHEMA = 'deadbyte.native-diff.v1';
|
|
10
11
|
export const NATIVE_DIFF_PREIMAGE_SCHEMA = 'deadbyte.native-diff-preimage.v1';
|
|
@@ -97,7 +98,7 @@ async function readEnvelope(file, key, domain, label) {
|
|
|
97
98
|
async function writeExclusive(file, value) {
|
|
98
99
|
await writeFile(file, `${canonicalJson(value)}\n`, { flag:'wx' });
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
+
function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, verifyKeyPath }) {
|
|
101
102
|
if (!policy || !codingRuntime?.policy) throw new Error('native diff runtime requires autonomous and coding policy');
|
|
102
103
|
if (!signingKeyPath || !verifyKeyPath) throw new Error('native diff runtime requires trust keys');
|
|
103
104
|
|
|
@@ -288,8 +289,11 @@ export function createNativeDiffRuntime({ policy, codingRuntime, signingKeyPath,
|
|
|
288
289
|
return receipt;
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
async function apply({ diffId }) {
|
|
292
|
+
async function apply({ diffId, expectedOperationCount = null }) {
|
|
292
293
|
const { ctx, identity } = await loadDiff(diffId);
|
|
294
|
+
if (expectedOperationCount !== null && (!Number.isInteger(expectedOperationCount) || expectedOperationCount < 1 || expectedOperationCount > policy.limits.max_files_per_diff || identity.operations.length !== expectedOperationCount)) {
|
|
295
|
+
throw new Error(`native diff operation count mismatch: expected ${String(expectedOperationCount)}, signed ${identity.operations.length}`);
|
|
296
|
+
}
|
|
293
297
|
await assertAutonomousArmed(policy, identity.root_id);
|
|
294
298
|
const codingStatus = await codingRuntime.sessionStatus();
|
|
295
299
|
if (!codingStatus.armed) throw new Error(`coding plane is disarmed: ${codingStatus.reason}`);
|
|
@@ -401,3 +405,47 @@ export function createNativeDiffRuntime({ policy, codingRuntime, signingKeyPath,
|
|
|
401
405
|
|
|
402
406
|
return Object.freeze({ preview, apply, verify, policy });
|
|
403
407
|
}
|
|
408
|
+
|
|
409
|
+
export { NATIVE_DIFF_V2_SCHEMA };
|
|
410
|
+
|
|
411
|
+
export function createNativeDiffRuntime(options) {
|
|
412
|
+
const v1=createNativeDiffRuntimeV1(options);
|
|
413
|
+
const v2=options.machineFsRuntime ? createNativeDiffV2Runtime(options) : null;
|
|
414
|
+
const isTopology=operations=>Array.isArray(operations)&&operations.some(op=>['mkdir','move','rmdir'].includes(op?.kind));
|
|
415
|
+
const diffPath=diffId=>{
|
|
416
|
+
if(!/^[0-9a-f]{64}$/.test(diffId??'')) throw new Error('invalid native diff_id');
|
|
417
|
+
return path.join(options.policy.evidence_dir,'native-diffs',diffId,'diff.json');
|
|
418
|
+
};
|
|
419
|
+
async function schemaFor(diffId){
|
|
420
|
+
const parsed=JSON.parse(await readFile(diffPath(diffId),'utf8'));
|
|
421
|
+
return parsed?.schema;
|
|
422
|
+
}
|
|
423
|
+
async function preview(args){
|
|
424
|
+
if(!isTopology(args?.operations)) return v1.preview(args);
|
|
425
|
+
if(!v2) throw new Error('native diff V2 topology requires machine fs runtime');
|
|
426
|
+
return v2.preview(args);
|
|
427
|
+
}
|
|
428
|
+
async function apply(args){
|
|
429
|
+
const schema=await schemaFor(args?.diffId);
|
|
430
|
+
if(schema===NATIVE_DIFF_SCHEMA) return v1.apply(args);
|
|
431
|
+
if(schema===NATIVE_DIFF_V2_SCHEMA){
|
|
432
|
+
if(!v2) throw new Error('native diff V2 topology requires machine fs runtime');
|
|
433
|
+
return v2.apply(args);
|
|
434
|
+
}
|
|
435
|
+
throw new Error(`unsupported native diff schema: ${String(schema)}`);
|
|
436
|
+
}
|
|
437
|
+
async function verify(args){
|
|
438
|
+
try {
|
|
439
|
+
const schema=await schemaFor(args?.diffId);
|
|
440
|
+
if(schema===NATIVE_DIFF_SCHEMA) return v1.verify(args);
|
|
441
|
+
if(schema===NATIVE_DIFF_V2_SCHEMA){
|
|
442
|
+
if(!v2) return {ok:false,reason:'native diff V2 topology requires machine fs runtime'};
|
|
443
|
+
return v2.verify(args);
|
|
444
|
+
}
|
|
445
|
+
return {ok:false,reason:`unsupported native diff schema: ${String(schema)}`};
|
|
446
|
+
} catch(error) {
|
|
447
|
+
return {ok:false,reason:String(error).slice(0,2048)};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return Object.freeze({preview,apply,verify,policy:options.policy});
|
|
451
|
+
}
|
|
@@ -54,7 +54,7 @@ const stderr=[]; transport.stderr?.on('data',chunk=>stderr.push(Buffer.from(chun
|
|
|
54
54
|
const base=['contained_transform','evidence_verify','file_read','proof_execute','workspace_open'];
|
|
55
55
|
const machine=['machine_list','machine_ping','machine_revoke','machine_status'];
|
|
56
56
|
const coding=['coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read','coding_receipt_verify','coding_search',
|
|
57
|
-
'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_outline','coding_symbol_references','coding_symbol_replace',
|
|
57
|
+
'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
|
|
58
58
|
'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status',
|
|
59
59
|
'coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'];
|
|
60
60
|
const observation=['machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'];
|
package/src/operator-surface.mjs
CHANGED
|
@@ -14,8 +14,9 @@ function workflow(goal,verb){
|
|
|
14
14
|
].join('\n');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function registerOperatorSurface(server,{packageRoot}){
|
|
17
|
+
export function registerOperatorSurface(server,{packageRoot,surfaceProfile='full',compactToolLimit=42}){
|
|
18
18
|
if(!server||!packageRoot) throw new Error('operator surface requires server and packageRoot');
|
|
19
|
+
if(!['full','compact'].includes(surfaceProfile)) throw new Error('operator surface profile must be full or compact');
|
|
19
20
|
const contextPath=path.join(packageRoot,'CONTEXT.md');
|
|
20
21
|
const architecturePath=path.join(packageRoot,'docs','ADR-0001-PRODUCTION-BASELINE-SEAMS.md');
|
|
21
22
|
const argSchema=z.strictObject({goal:z.string().min(1).max(2000)});
|
|
@@ -34,4 +35,9 @@ export function registerOperatorSurface(server,{packageRoot}){
|
|
|
34
35
|
server.registerResource('deadbyte-architecture','deadbyte://architecture',{
|
|
35
36
|
title:'DEADBYTE MCP Architecture',description:'Production module seams, tracer bullet, policy, telemetry, semantic and remote decisions.',mimeType:'text/markdown'
|
|
36
37
|
},async uri=>({contents:[{uri:uri.href,mimeType:'text/markdown',text:await readFile(architecturePath,'utf8')}]}));
|
|
38
|
+
server.registerResource('deadbyte-surface-profile','deadbyte://surface-profile',{
|
|
39
|
+
title:'DEADBYTE MCP Surface Profile',description:'Active R36 MCP surface profile and compact tool ceiling.',mimeType:'application/json'
|
|
40
|
+
},async uri=>({contents:[{uri:uri.href,mimeType:'application/json',text:JSON.stringify({
|
|
41
|
+
schema:'deadbyte.surface-profile.v1',profile:surfaceProfile,compact_tool_limit:compactToolLimit
|
|
42
|
+
})}]}));
|
|
37
43
|
}
|