deadbyte-mcp 0.15.74 → 0.15.76
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 +108 -42
- package/PROVENANCE.json +20 -22
- package/README.txt +23 -18
- package/RELEASE.json +69 -18
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +32 -2
- package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
- package/bin/deadbyte-desktop-uia-r199.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199.obj +0 -0
- package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
- package/bin/deadbyte-fs-read-r198.exe +0 -0
- package/bin/deadbyte-fs-read-r198.obj +0 -0
- package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +2 -8
- package/native/deadbyte-desktop-uia-r199.cpp +345 -0
- package/native/deadbyte-fs-read-r197.cpp +174 -0
- package/native/deadbyte-fs-read-r198.cpp +181 -0
- package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
- package/package.json +5 -2
- package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
- package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
- package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
- package/scripts/build-native-fs-read-r197.ps1 +28 -0
- package/scripts/build-native-fs-read-r198.ps1 +28 -0
- package/scripts/build-native-root-probe-r197.ps1 +28 -0
- package/scripts/gate-windows-r195.ps1 +114 -0
- package/scripts/gate-windows-r206.ps1 +116 -0
- package/scripts/gate-windows-r207.ps1 +104 -0
- package/scripts/init-desktop-policy-r199.ps1 +85 -0
- package/scripts/npm-release-r48.mjs +27 -0
- package/scripts/npm-release-readiness-r195.mjs +72 -0
- package/scripts/npm-release-readiness-r206.mjs +76 -0
- package/scripts/npm-release-readiness-r207.mjs +76 -0
- package/scripts/r193-owner-request-live.mjs +8 -7
- package/scripts/r193-owner-uac-broker.mjs +6 -5
- package/scripts/r205-candidate-selection.mjs +22 -0
- package/src/candidate-selection-evidence-r206.mjs +71 -0
- package/src/candidate-selection-r205.mjs +96 -0
- package/src/desktop-postcondition-r196.mjs +30 -0
- package/src/desktop-runtime-r40.mjs +44 -7
- package/src/desktop-uia-identity-r199.mjs +51 -0
- package/src/desktop-uia-r40.mjs +9 -1
- package/src/machine-fs-handle-read-r196.mjs +43 -0
- package/src/machine-fs-native-read-r197.mjs +73 -0
- package/src/machine-fs-native-read-r198.mjs +76 -0
- package/src/machine-fs-read-worker.mjs +5 -25
- package/src/machine-fs.mjs +31 -16
- package/src/r195-canonical-owner-observation.mjs +15 -0
- package/src/r207-owner-enrollment-io.mjs +30 -0
- package/src/remote-destination-journal-r202.mjs +120 -0
- package/src/remote-device-r44.mjs +89 -14
- package/src/remote-payload-boundary-r201.mjs +71 -0
- package/src/remote-terminal-evidence-r200.mjs +60 -0
- package/src/version.mjs +1 -1
- package/test/desktop-runtime-r40.test.mjs +111 -0
- package/test/desktop-uia-r40.test.mjs +15 -0
- package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
- package/test/fixtures/r203-journal-writer.mjs +6 -0
- package/test/process-policy-refresh.test.mjs +4 -4
- package/test/r130-release-security-contract.test.mjs +8 -8
- package/test/r132-release-identity.test.mjs +7 -7
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +3 -3
- package/test/r165-live-cli-skew.test.mjs +1 -1
- package/test/r167-release-contract.test.mjs +11 -11
- package/test/r168-release-contract.test.mjs +4 -4
- package/test/r172-release-contract.test.mjs +6 -6
- package/test/r173-release-contract.test.mjs +16 -16
- package/test/r179-release-contract.test.mjs +12 -12
- package/test/r190-release-contract.test.mjs +10 -10
- package/test/r191-release-contract.test.mjs +12 -12
- package/test/r192-release-identity.test.mjs +9 -9
- package/test/r193-release-identity.test.mjs +24 -22
- package/test/r193-release-readiness.test.mjs +2 -2
- package/test/r193-windows-gate.test.mjs +10 -10
- package/test/r194-release-identity.test.mjs +19 -15
- package/test/r194-release-readiness.test.mjs +3 -3
- package/test/r194-windows-gate.test.mjs +9 -8
- package/test/r195-canonical-owner-observation.test.mjs +32 -0
- package/test/r195-release-readiness.test.mjs +22 -0
- package/test/r196-desktop-postcondition.test.mjs +53 -0
- package/test/r196-handle-bound-read.test.mjs +99 -0
- package/test/r196-remote-outcome.test.mjs +94 -0
- package/test/r197-namespace-race.test.mjs +41 -0
- package/test/r197-native-dispatch-race.test.mjs +49 -0
- package/test/r197-native-file-read.test.mjs +60 -0
- package/test/r197-native-root-open.test.mjs +61 -0
- package/test/r198-root-identity.test.mjs +88 -0
- package/test/r199-native-uia-worker.test.mjs +28 -0
- package/test/r199-uia-identity.test.mjs +43 -0
- package/test/r200-remote-reconcile.test.mjs +106 -0
- package/test/r201-payload-boundary.test.mjs +78 -0
- package/test/r201-payload-fuzz.test.mjs +37 -0
- package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
- package/test/r202-destination-journal.test.mjs +92 -0
- package/test/r203-atomic-journal.test.mjs +86 -0
- package/test/r205-candidate-selection-cli.test.mjs +31 -0
- package/test/r205-candidate-selection.test.mjs +83 -0
- package/test/r206-candidate-evidence.test.mjs +64 -0
- package/test/r206-release-contract.test.mjs +42 -0
- package/test/r206-release-readiness.test.mjs +23 -0
- package/test/r207-owner-enrollment-io.test.mjs +37 -0
- package/test/r207-release-contract.test.mjs +51 -0
- package/test/r207-release-readiness.test.mjs +23 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-machine-fs-large-read.test.mjs +26 -12
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, createPublicKey, randomBytes } from 'node:crypto';
|
|
2
2
|
import { lstat, mkdir, open, readFile, realpath, rename } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import {verifySignedRemoteTerminalR200} from './remote-terminal-evidence-r200.mjs';
|
|
5
|
+
import {normalizeRemotePayloadR201} from './remote-payload-boundary-r201.mjs';
|
|
4
6
|
|
|
5
7
|
const STATE_SCHEMA='deadbyte.remote-device-state.v1';
|
|
6
8
|
const CREDENTIAL_SCHEMA='deadbyte.remote-credential.v1';
|
|
@@ -36,6 +38,9 @@ function validateState(value){
|
|
|
36
38
|
insist(HEX64.test(id)&&r.request_id===id,'remote request identity invalid');
|
|
37
39
|
insist(HEX64.test(r.request_sha256??''),'remote request hash invalid');
|
|
38
40
|
insist(['accepted','executing','completed','failed'].includes(r.state),'remote request state invalid');
|
|
41
|
+
if(r.transport_state!==undefined)insist(['prepared','acknowledged','ack_missing','timed_out','transport_error','terminal_received','reconciled'].includes(r.transport_state),'remote transport state invalid');
|
|
42
|
+
if(r.effect_state!==undefined)insist(['unknown','confirmed','failed'].includes(r.effect_state),'remote effect state invalid');
|
|
43
|
+
if(r.reconciliation_state!==undefined)insist(['signed_terminal','signed_terminal_effect_unknown'].includes(r.reconciliation_state),'remote reconciliation state invalid');
|
|
39
44
|
}
|
|
40
45
|
return value;
|
|
41
46
|
}
|
|
@@ -69,12 +74,13 @@ async function withTimeout(operation,timeoutMs,label){
|
|
|
69
74
|
try{
|
|
70
75
|
return await Promise.race([
|
|
71
76
|
Promise.resolve().then(operation),
|
|
72
|
-
new Promise((_,reject)=>{timer=setTimeout(()=>
|
|
77
|
+
new Promise((_,reject)=>{timer=setTimeout(()=>{const error=new Error(`${label} timed out after ${timeoutMs}ms`);error.code='REMOTE_TIMEOUT';reject(error);},timeoutMs);timer.unref?.();})
|
|
73
78
|
]);
|
|
74
79
|
}finally{if(timer)clearTimeout(timer);}
|
|
75
80
|
}
|
|
76
81
|
export function payloadHash({deviceId,epoch,capabilityId,payload}){
|
|
77
|
-
|
|
82
|
+
const safePayload=normalizeRemotePayloadR201(payload);
|
|
83
|
+
return sha(Buffer.from(canonicalJson({device_id:deviceId,epoch,capability_id:capabilityId,payload:safePayload}),'utf8'));
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapability,onCredentialSuperseded=null,relayTimeoutMs=15000,probeRetries=1,reachableTtlMs=75000}={}){
|
|
@@ -160,9 +166,9 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
160
166
|
async function dispatch({deviceId,epoch,capabilityId,payload={},requestId}={}){
|
|
161
167
|
insist(HEX64.test(requestId??''),'remote request id invalid');
|
|
162
168
|
insist(typeof capabilityId==='string'&&CAP.test(capabilityId),'remote capability id invalid');
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const requestSha=payloadHash({deviceId,epoch,capabilityId,payload});
|
|
169
|
+
const safePayload=normalizeRemotePayloadR201(payload);
|
|
170
|
+
const payloadJson=canonicalJson(safePayload);
|
|
171
|
+
const requestSha=payloadHash({deviceId,epoch,capabilityId,payload:safePayload});
|
|
166
172
|
const existing=state.requests[requestId];
|
|
167
173
|
if(existing){
|
|
168
174
|
insist(existing.request_sha256===requestSha,'remote request id conflict');
|
|
@@ -170,43 +176,112 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
170
176
|
}
|
|
171
177
|
const allowed=await authorizeCapability({deviceId,epoch,capabilityId});
|
|
172
178
|
insist(allowed===true,'remote capability denied by local policy');
|
|
179
|
+
let claimedNew=false;
|
|
173
180
|
await withLock(async()=>{
|
|
174
181
|
const d=getDevice(deviceId,epoch);insist(d.reported_online&&fresh(d),'remote device not reachable or readiness probe is stale');
|
|
175
182
|
const again=state.requests[requestId];
|
|
176
183
|
if(again){insist(again.request_sha256===requestSha,'remote request id conflict');return;}
|
|
177
184
|
state.requests[requestId]={request_id:requestId,request_sha256:requestSha,device_id:deviceId,epoch,capability_id:capabilityId,
|
|
178
|
-
payload_sha256:sha(Buffer.from(payloadJson,'utf8')),state:'accepted',
|
|
179
|
-
|
|
185
|
+
payload_sha256:sha(Buffer.from(payloadJson,'utf8')),state:'accepted',transport_state:'prepared',effect_state:'unknown',
|
|
186
|
+
created_at_utc:now(),updated_at_utc:now()};
|
|
187
|
+
await persist();claimedNew=true;
|
|
180
188
|
});
|
|
181
|
-
if(
|
|
189
|
+
if(!claimedNew)return {...structuredClone(state.requests[requestId]),replay:true};
|
|
182
190
|
let response=null;
|
|
183
191
|
try{
|
|
184
192
|
response=await withTimeout(
|
|
185
|
-
()=>relay.request({device_id:deviceId,epoch,capability_id:capabilityId,payload,request_id:requestId,request_sha256:requestSha}),
|
|
193
|
+
()=>relay.request({device_id:deviceId,epoch,capability_id:capabilityId,payload:safePayload,request_id:requestId,request_sha256:requestSha}),
|
|
186
194
|
relayTimeoutMs,'remote dispatch'
|
|
187
195
|
);
|
|
188
196
|
}catch(error){
|
|
189
197
|
return withLock(async()=>{
|
|
190
|
-
const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_error';
|
|
198
|
+
const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_error';
|
|
199
|
+
rec.transport_state=error?.code==='REMOTE_TIMEOUT'?'timed_out':'transport_error';rec.effect_state='unknown';
|
|
200
|
+
rec.error=String(error?.message??error);rec.updated_at_utc=now();
|
|
191
201
|
const d=state.devices[deviceId];if(d&&d.epoch===epoch){d.reachable=false;d.updated_at_utc=now();}
|
|
192
202
|
await persist();return structuredClone(rec);
|
|
193
203
|
});
|
|
194
204
|
}
|
|
195
205
|
if(!response||response.ack!=='accepted'||response.request_id!==requestId){
|
|
196
|
-
return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_ack';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
|
|
206
|
+
return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_ack';rec.transport_state='ack_missing';rec.effect_state='unknown';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
|
|
197
207
|
}
|
|
198
|
-
await withLock(async()=>{const rec=state.requests[requestId];rec.state='executing';rec.acknowledged_at_utc=now();rec.updated_at_utc=now();await persist();});
|
|
208
|
+
await withLock(async()=>{const rec=state.requests[requestId];rec.state='executing';rec.transport_state='acknowledged';rec.effect_state='unknown';rec.acknowledged_at_utc=now();rec.updated_at_utc=now();await persist();});
|
|
199
209
|
const terminal=response.terminal;
|
|
200
210
|
if(!terminal||!['completed','failed'].includes(terminal.status)){
|
|
201
|
-
return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_terminal';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
|
|
211
|
+
return withLock(async()=>{const rec=state.requests[requestId];rec.state='failed';rec.error_code='transport_no_terminal';rec.transport_state='acknowledged';rec.effect_state='unknown';rec.updated_at_utc=now();await persist();return structuredClone(rec);});
|
|
202
212
|
}
|
|
203
213
|
return withLock(async()=>{
|
|
204
214
|
const rec=state.requests[requestId];rec.state=terminal.status;
|
|
215
|
+
rec.transport_state='terminal_received';rec.effect_state=terminal.status==='completed'?'confirmed':'failed';
|
|
205
216
|
if(terminal.status==='completed'){insist(HEX64.test(terminal.result_sha256??''),'remote terminal result hash invalid');rec.result_sha256=terminal.result_sha256;}
|
|
206
217
|
else {rec.error_code=typeof terminal.error_code==='string'?terminal.error_code:'remote_failed';}
|
|
207
218
|
rec.updated_at_utc=now();rec.completed_at_utc=now();await persist();return structuredClone(rec);
|
|
208
219
|
});
|
|
209
220
|
}
|
|
221
|
+
// Read-only lookup: never calls relay.request, never creates a new request ID,
|
|
222
|
+
// and never turns a transport timeout into a claim that the mutation failed.
|
|
223
|
+
async function reconcile({requestId}={}){
|
|
224
|
+
insist(HEX64.test(requestId??''),'R200 reconciliation request id invalid');
|
|
225
|
+
insist(typeof relay.lookupTerminal==='function','R200 signed terminal lookup unavailable');
|
|
226
|
+
const snapshot=await withLock(async()=>{
|
|
227
|
+
const rec=state.requests[requestId];
|
|
228
|
+
insist(rec,'R200 reconciliation request unknown');
|
|
229
|
+
const device=getDevice(rec.device_id,rec.epoch);
|
|
230
|
+
return {request:structuredClone(rec),device:{
|
|
231
|
+
device_id:device.device_id,epoch:device.epoch,
|
|
232
|
+
public_key_spki_base64:device.public_key_spki_base64
|
|
233
|
+
}};
|
|
234
|
+
});
|
|
235
|
+
if(snapshot.request.effect_state!=='unknown')
|
|
236
|
+
return {...snapshot.request,replay:true,reconciliation:'already_terminal'};
|
|
237
|
+
let answer=null;
|
|
238
|
+
try{
|
|
239
|
+
answer=await withTimeout(()=>relay.lookupTerminal({
|
|
240
|
+
device_id:snapshot.request.device_id,epoch:snapshot.request.epoch,
|
|
241
|
+
request_id:requestId,request_sha256:snapshot.request.request_sha256
|
|
242
|
+
}),relayTimeoutMs,'remote terminal reconciliation');
|
|
243
|
+
}catch(error){
|
|
244
|
+
return {...snapshot.request,reconciliation:'unresolved',
|
|
245
|
+
reconciliation_error_code:error?.code==='REMOTE_TIMEOUT'?'lookup_timeout':'lookup_transport_error'};
|
|
246
|
+
}
|
|
247
|
+
if(answer===null||answer===undefined)
|
|
248
|
+
return {...snapshot.request,reconciliation:'unresolved'};
|
|
249
|
+
const proof=verifySignedRemoteTerminalR200(answer,snapshot);
|
|
250
|
+
return withLock(async()=>{
|
|
251
|
+
const record=state.requests[requestId];
|
|
252
|
+
insist(record?.request_sha256===snapshot.request.request_sha256,
|
|
253
|
+
'R200 reconciliation record changed before commit');
|
|
254
|
+
const current=getDevice(record.device_id,record.epoch);
|
|
255
|
+
insist(current.public_key_spki_base64===snapshot.device.public_key_spki_base64,
|
|
256
|
+
'R200 reconciliation pairing changed before commit');
|
|
257
|
+
if(record.effect_state!=='unknown')
|
|
258
|
+
return {...structuredClone(record),replay:true,reconciliation:'already_terminal'};
|
|
259
|
+
record.transport_state='reconciled';
|
|
260
|
+
record.terminal_evidence_sha256=proof.terminal_evidence_sha256;
|
|
261
|
+
record.terminal_status=proof.terminal.status;
|
|
262
|
+
record.signed_by_device_id=proof.signed_by_device_id;
|
|
263
|
+
record.reconciled_at_utc=now();
|
|
264
|
+
record.updated_at_utc=now();
|
|
265
|
+
record.reconciliation_state=proof.effective_state==='unknown'
|
|
266
|
+
?'signed_terminal_effect_unknown':'signed_terminal';
|
|
267
|
+
if(proof.effective_state==='confirmed'){
|
|
268
|
+
record.state='completed';record.effect_state='confirmed';
|
|
269
|
+
record.result_sha256=proof.terminal.result_sha256;
|
|
270
|
+
record.completed_at_utc=now();
|
|
271
|
+
}else if(proof.effective_state==='failed'){
|
|
272
|
+
record.state='failed';record.effect_state='failed';
|
|
273
|
+
record.error_code=proof.terminal.error_code;
|
|
274
|
+
record.completed_at_utc=now();
|
|
275
|
+
}else{
|
|
276
|
+
record.state='failed';record.effect_state='unknown';
|
|
277
|
+
record.error_code=proof.terminal.error_code;
|
|
278
|
+
}
|
|
279
|
+
await persist();
|
|
280
|
+
return {...structuredClone(record),reconciliation:
|
|
281
|
+
proof.effective_state==='unknown'?'effect_unknown':'signed_terminal'};
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
210
285
|
async function revoke({deviceId,epoch}={}){
|
|
211
286
|
return withLock(async()=>{
|
|
212
287
|
const d=getDevice(deviceId,epoch);d.revoked=true;d.reported_online=false;d.reachable=false;d.connection_id=null;d.updated_at_utc=now();await persist();
|
|
@@ -239,5 +314,5 @@ export async function createRemoteDevicePlaneR44({stateDir,relay,authorizeCapabi
|
|
|
239
314
|
return {status:'committed',device_id:deviceId,epoch,credential_sha256:credentialSha,previous_credential_sha256:previous?.credential_sha256??null};
|
|
240
315
|
}
|
|
241
316
|
async function list(){return {status:'ok',devices:Object.values(state.devices).map(pub).sort((a,b)=>a.device_id.localeCompare(b.device_id)),requests:structuredClone(state.requests)};}
|
|
242
|
-
return Object.freeze({pair,status,reportOnline,probe,dispatch,revoke,rotateCredential,list,state_file:stateFile});
|
|
317
|
+
return Object.freeze({pair,status,reportOnline,probe,dispatch,reconcile,revoke,rotateCredential,list,state_file:stateFile});
|
|
243
318
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// R201: a remote capability may only receive a detached, bounded JSON value.
|
|
2
|
+
// Never use JSON.stringify on caller-owned objects: getters and toJSON can execute.
|
|
3
|
+
import {types as utilTypes} from 'node:util';
|
|
4
|
+
|
|
5
|
+
const MAX_BYTES=1024*1024;
|
|
6
|
+
const MAX_DEPTH=32;
|
|
7
|
+
const MAX_NODES=4096;
|
|
8
|
+
const MAX_KEYS=256;
|
|
9
|
+
const FORBIDDEN_KEYS=new Set(['__proto__','prototype','constructor','toJSON']);
|
|
10
|
+
function reject(message){throw new Error('remote payload '+message);}
|
|
11
|
+
function checkString(value,label){
|
|
12
|
+
if(Buffer.byteLength(value,'utf8')>MAX_BYTES)reject(label+' too large');
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function normalizeRemotePayloadR201(input){
|
|
16
|
+
let nodes=0;
|
|
17
|
+
const ancestors=new WeakSet();
|
|
18
|
+
function visit(value,depth){
|
|
19
|
+
if(depth>MAX_DEPTH)reject('depth exceeded');
|
|
20
|
+
if(++nodes>MAX_NODES)reject('node budget exceeded');
|
|
21
|
+
if(value===null||typeof value==='boolean')return value;
|
|
22
|
+
if(typeof value==='string')return checkString(value,'string');
|
|
23
|
+
if(typeof value==='number'){
|
|
24
|
+
if(!Number.isFinite(value)||Object.is(value,-0)||
|
|
25
|
+
(Number.isInteger(value)&&!Number.isSafeInteger(value)))reject('number must be finite, safe and unambiguous');
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
if(typeof value!=='object')reject('non-JSON value');
|
|
29
|
+
if(utilTypes.isProxy(value))reject('proxy object not allowed');
|
|
30
|
+
if(ancestors.has(value))reject('cycle detected');
|
|
31
|
+
const isArray=Array.isArray(value);
|
|
32
|
+
if(!isArray && ![null,Object.prototype].includes(Object.getPrototypeOf(value)))
|
|
33
|
+
reject('non-plain object not allowed');
|
|
34
|
+
ancestors.add(value);
|
|
35
|
+
try{
|
|
36
|
+
const descriptors=Object.getOwnPropertyDescriptors(value);
|
|
37
|
+
const names=Object.keys(descriptors);
|
|
38
|
+
if(Reflect.ownKeys(value).length!==names.length)reject('symbol property not allowed');
|
|
39
|
+
if(names.length>MAX_NODES)reject('property budget exceeded');
|
|
40
|
+
const result=isArray?[]:Object.create(null);
|
|
41
|
+
if(isArray){
|
|
42
|
+
const length=descriptors.length?.value;
|
|
43
|
+
if(!Number.isSafeInteger(length)||length<0||length>MAX_NODES)
|
|
44
|
+
reject('array length invalid');
|
|
45
|
+
if(names.length!==length+1)reject('sparse or decorated array not allowed');
|
|
46
|
+
for(let i=0;i<length;i++){
|
|
47
|
+
const desc=descriptors[String(i)];
|
|
48
|
+
if(!desc||!('value' in desc)||!desc.enumerable)reject('sparse or accessor array not allowed');
|
|
49
|
+
result.push(visit(desc.value,depth+1));
|
|
50
|
+
}
|
|
51
|
+
}else{
|
|
52
|
+
if(names.length>MAX_KEYS)reject('object key budget exceeded');
|
|
53
|
+
for(const key of names.sort()){
|
|
54
|
+
if(FORBIDDEN_KEYS.has(key))reject('unsafe key');
|
|
55
|
+
checkString(key,'key');
|
|
56
|
+
const desc=descriptors[key];
|
|
57
|
+
if(!desc?.enumerable||!('value' in desc))reject('accessor or hidden property not allowed');
|
|
58
|
+
result[key]=visit(desc.value,depth+1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze(result);
|
|
62
|
+
}finally{
|
|
63
|
+
ancestors.delete(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const safe=visit(input,0);
|
|
67
|
+
if(!safe||Array.isArray(safe)||typeof safe!=='object')reject('top-level object required');
|
|
68
|
+
const encoded=JSON.stringify(safe);
|
|
69
|
+
if(Buffer.byteLength(encoded,'utf8')>MAX_BYTES)reject('encoded byte budget exceeded');
|
|
70
|
+
return safe;
|
|
71
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {createHash,createPublicKey,verify as edVerify} from 'node:crypto';
|
|
2
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
3
|
+
|
|
4
|
+
export const REMOTE_TERMINAL_EVIDENCE_SCHEMA='deadbyte.remote-terminal-evidence-r200.v1';
|
|
5
|
+
export const REMOTE_TERMINAL_DOMAIN=Buffer.from('DEADBYTE-REMOTE-TERMINAL-R200\0','utf8');
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const CODE=/^[A-Za-z0-9._-]{1,128}$/;
|
|
8
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
9
|
+
function exactKeys(value,keys,label){
|
|
10
|
+
insist(value&&typeof value==='object'&&!Array.isArray(value),label+' invalid');
|
|
11
|
+
insist(Object.keys(value).sort().join('|')===keys.sort().join('|'),label+' contains unexpected or missing fields');
|
|
12
|
+
}
|
|
13
|
+
function canonicalBase64(value,label){
|
|
14
|
+
insist(typeof value==='string'&&value.length<=8192&&value.length>0,label+' invalid');
|
|
15
|
+
const decoded=Buffer.from(value,'base64');
|
|
16
|
+
insist(decoded.length>0&&decoded.toString('base64')===value,label+' noncanonical');
|
|
17
|
+
return decoded;
|
|
18
|
+
}
|
|
19
|
+
export function verifySignedRemoteTerminalR200(response,{request,device}={}){
|
|
20
|
+
exactKeys(response,['evidence','attestation'],'R200 remote terminal envelope');
|
|
21
|
+
const body=response.evidence,signature=response.attestation;
|
|
22
|
+
exactKeys(body,['schema','device_id','epoch','request_id','request_sha256','capability_id','terminal'],'R200 remote terminal body');
|
|
23
|
+
insist(body.schema===REMOTE_TERMINAL_EVIDENCE_SCHEMA,'R200 terminal schema mismatch');
|
|
24
|
+
insist(HEX64.test(body.device_id??'')&&body.device_id===request?.device_id&&body.device_id===device?.device_id,
|
|
25
|
+
'R200 terminal device identity mismatch');
|
|
26
|
+
insist(Number.isSafeInteger(body.epoch)&&body.epoch===request?.epoch&&body.epoch===device?.epoch,
|
|
27
|
+
'R200 terminal epoch mismatch');
|
|
28
|
+
insist(HEX64.test(body.request_id??'')&&body.request_id===request?.request_id,
|
|
29
|
+
'R200 terminal request identity mismatch');
|
|
30
|
+
insist(HEX64.test(body.request_sha256??'')&&body.request_sha256===request?.request_sha256,
|
|
31
|
+
'R200 terminal request hash mismatch');
|
|
32
|
+
insist(body.capability_id===request?.capability_id,'R200 terminal capability mismatch');
|
|
33
|
+
const terminal=body.terminal;
|
|
34
|
+
insist(terminal&&typeof terminal==='object'&&!Array.isArray(terminal),'R200 terminal result missing');
|
|
35
|
+
if(terminal.status==='completed'){
|
|
36
|
+
exactKeys(terminal,['status','result_sha256'],'R200 completed terminal');
|
|
37
|
+
insist(HEX64.test(terminal.result_sha256??''),'R200 terminal result digest invalid');
|
|
38
|
+
}else if(terminal.status==='failed'){
|
|
39
|
+
exactKeys(terminal,['status','error_code','effect_state'],'R200 failed terminal');
|
|
40
|
+
insist(CODE.test(terminal.error_code??''),'R200 terminal error code invalid');
|
|
41
|
+
insist(['unknown','not_started'].includes(terminal.effect_state),
|
|
42
|
+
'R200 failure effect state must be unknown or not_started');
|
|
43
|
+
}else throw new Error('R200 terminal status invalid');
|
|
44
|
+
exactKeys(signature,['algorithm','key_id','signature_base64'],'R200 remote attestation');
|
|
45
|
+
insist(signature.algorithm==='ed25519'&&signature.key_id===device.device_id,'R200 signer identity mismatch');
|
|
46
|
+
const sig=canonicalBase64(signature.signature_base64,'R200 Ed25519 signature');
|
|
47
|
+
insist(sig.length===64,'R200 Ed25519 signature size invalid');
|
|
48
|
+
const der=canonicalBase64(device.public_key_spki_base64,'R200 paired device public key');
|
|
49
|
+
insist(createHash('sha256').update(der).digest('hex')===device.device_id,'R200 paired device key mismatch');
|
|
50
|
+
const key=createPublicKey({key:der,format:'der',type:'spki'});
|
|
51
|
+
insist(key.asymmetricKeyType==='ed25519','R200 paired device is not Ed25519');
|
|
52
|
+
const bytes=Buffer.concat([REMOTE_TERMINAL_DOMAIN,Buffer.from(canonicalJson(body),'utf8')]);
|
|
53
|
+
insist(edVerify(null,bytes,key,sig),'R200 remote terminal signature invalid');
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
terminal:Object.freeze({...terminal}),
|
|
56
|
+
terminal_evidence_sha256:createHash('sha256').update(bytes).digest('hex'),
|
|
57
|
+
signed_by_device_id:device.device_id,
|
|
58
|
+
effective_state:terminal.status==='completed'?'confirmed':terminal.effect_state==='not_started'?'failed':'unknown'
|
|
59
|
+
});
|
|
60
|
+
}
|
package/src/version.mjs
CHANGED
|
@@ -100,6 +100,48 @@ test('desktop runtime performs observe-target-fresh-guard-effect-post chain',asy
|
|
|
100
100
|
}),{ok:true,receipt_sha256:result.receipt_sha256,kind:'desktop_action_completed',receipt_id:result.receipt_id});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
test('R196 Desktop explicit postimage is signed separately from injection completion',async()=>{
|
|
104
|
+
const fx=await fixture();
|
|
105
|
+
const result=await fx.runtime.action({
|
|
106
|
+
intentId:H('r196-postimage'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,
|
|
107
|
+
point:{x:20,y:20},expectedPostcondition:{kind:'frame_image_sha256',expected_sha256:H('after')}
|
|
108
|
+
});
|
|
109
|
+
assert.equal(result.injected,true);
|
|
110
|
+
assert.equal(result.side_effect_state,'confirmed_injected');
|
|
111
|
+
assert.equal(result.task_success,null,'generic task success must remain unknown');
|
|
112
|
+
assert.equal(result.postcondition.status,'satisfied');
|
|
113
|
+
assert.equal(result.postcondition.reason,'exact_post_image_observed');
|
|
114
|
+
assert.match(result.postcondition_sha256,/^[0-9a-f]{64}$/);
|
|
115
|
+
const receipt=JSON.parse(await readFile(path.join(fx.evidence,result.receipt_id+'.json'),'utf8'));
|
|
116
|
+
assert.equal(receipt.payload.postcondition.status,'satisfied');
|
|
117
|
+
assert.equal(receipt.payload.postcondition_sha256,result.postcondition_sha256);
|
|
118
|
+
assert.equal(verifyDesktopReceiptObject(receipt,{
|
|
119
|
+
policySha256:fx.policy.policy_sha256,publicKey:fx.publicKey,keyId:'test-key'
|
|
120
|
+
}).ok,true,'postcondition evidence must be in signed receipt');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('R196 Desktop injected input without expected postimage is not application success',async()=>{
|
|
124
|
+
const before={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
125
|
+
const after={frame:F('2026-09-19T02:00:01.000Z'),backend:'fake',byte_length:4};
|
|
126
|
+
const fx=await fixture({captureFrames:[before,after]});
|
|
127
|
+
const result=await fx.runtime.action({
|
|
128
|
+
intentId:H('r196-disabled-like'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20},
|
|
129
|
+
expectedPostcondition:{kind:'frame_image_sha256',expected_sha256:H('desired-but-unobserved')}
|
|
130
|
+
});
|
|
131
|
+
assert.equal(result.injected,true);
|
|
132
|
+
assert.equal(result.postcondition.status,'unsatisfied');
|
|
133
|
+
assert.equal(result.task_success,null);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('R196 Desktop malformed postcondition fails before input dispatch',async()=>{
|
|
137
|
+
const fx=await fixture();
|
|
138
|
+
await assert.rejects(fx.runtime.action({
|
|
139
|
+
intentId:H('r196-forgery'),kind:'click_primary',expectedFrameId:fx.expected.frame_id,point:{x:20,y:20},
|
|
140
|
+
expectedPostcondition:{kind:'frame_image_sha256',expected_sha256:H('after'),_internal:true}
|
|
141
|
+
}),/unsupported desktop postcondition/);
|
|
142
|
+
assert.equal(fx.calls.length,0);
|
|
143
|
+
});
|
|
144
|
+
|
|
103
145
|
test('desktop runtime refuses effect when expected frame is stale',async()=>{
|
|
104
146
|
const fx=await fixture({times:['2026-09-19T02:00:03.500Z']});
|
|
105
147
|
await assert.rejects(fx.runtime.action({
|
|
@@ -322,3 +364,72 @@ test('T5 UIA grounding re-observes and binds candidate to fresh frame without mu
|
|
|
322
364
|
assert.equal(JSON.stringify(result).includes('Save'),false);
|
|
323
365
|
assert.equal(fx.calls.length,0);
|
|
324
366
|
});
|
|
367
|
+
|
|
368
|
+
test('R199 stale UIA RuntimeId must refuse action before input delivery',async()=>{
|
|
369
|
+
const at='2026-09-19T02:00:00.700Z';
|
|
370
|
+
const f1={frame:F(at),backend:'fake',byte_length:4};
|
|
371
|
+
const f2={frame:F('2026-09-19T02:00:00.750Z'),backend:'fake',byte_length:4};
|
|
372
|
+
const f3={frame:F('2026-09-19T02:00:01.100Z',H('post')),backend:'fake',byte_length:4};
|
|
373
|
+
let lookups=0;
|
|
374
|
+
const grounderProvider=async({query})=>{
|
|
375
|
+
lookups++;
|
|
376
|
+
assert.equal(query.value,'Save');
|
|
377
|
+
return {status:'ok',query_sha256:H('R199-Save'),query_kind:'name_exact',ordinal:0,
|
|
378
|
+
visited:2,truncated:false,provider_receipt_sha256:H('uia-provider'),
|
|
379
|
+
matches:[{rect:{left:100,top:120,width:80,height:30},control_type:50000,
|
|
380
|
+
enabled:true,offscreen:false,runtime_id:lookups===1?[17,222,0]:[17,223,0]}]};
|
|
381
|
+
};
|
|
382
|
+
const fx=await fixture({captureFrames:[f1,f2,f3],grounderProvider});
|
|
383
|
+
const q={kind:'name_exact',value:'Save',max_results:8,ordinal:0};
|
|
384
|
+
const target=await fx.runtime.ground({frameId:fx.expected.frame_id,query:q});
|
|
385
|
+
await assert.rejects(fx.runtime.action({intentId:H('R199-stale'),kind:'click_primary',
|
|
386
|
+
expectedFrameId:target.frame.frame_id,targetId:target.target.target_id}),/UIA.*(identity|stale|changed)/i);
|
|
387
|
+
assert.equal(lookups,2,'re-ground must happen exactly once before effect');
|
|
388
|
+
assert.equal(fx.calls.length,0,'stale UIA target must never dispatch input');
|
|
389
|
+
});
|
|
390
|
+
test('R199 matching RuntimeId permits guarded input but never claims application success',async()=>{
|
|
391
|
+
const f1={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
392
|
+
const f2={frame:F('2026-09-19T02:00:00.750Z'),backend:'fake',byte_length:4};
|
|
393
|
+
const f3={frame:F('2026-09-19T02:00:01.100Z',H('post')),backend:'fake',byte_length:4};
|
|
394
|
+
let lookups=0;
|
|
395
|
+
const grounderProvider=async()=>({status:'ok',query_sha256:H('R199-Save'),query_kind:'name_exact',ordinal:0,
|
|
396
|
+
visited:2,truncated:false,provider_receipt_sha256:H('uia-provider'),
|
|
397
|
+
matches:[{rect:{left:100,top:120,width:80,height:30},control_type:50000,
|
|
398
|
+
enabled:true,offscreen:false,runtime_id:[17,222,0]}]});
|
|
399
|
+
const fx=await fixture({captureFrames:[f1,f2,f3],grounderProvider});
|
|
400
|
+
const target=await fx.runtime.ground({frameId:fx.expected.frame_id,query:{kind:'name_exact',value:'Save',ordinal:0}});
|
|
401
|
+
const result=await fx.runtime.action({intentId:H('R199-valid'),kind:'click_primary',
|
|
402
|
+
expectedFrameId:target.frame.frame_id,targetId:target.target.target_id});
|
|
403
|
+
assert.equal(fx.calls.length,1);
|
|
404
|
+
assert.equal(result.injected,true);
|
|
405
|
+
assert.equal(result.task_success,null);
|
|
406
|
+
assert.equal(result.side_effect_state,'confirmed_injected');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('R199 UIA target without RuntimeId cannot be used to inject coordinates',async()=>{
|
|
410
|
+
const f1={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
411
|
+
const f2={frame:F('2026-09-19T02:00:00.750Z'),backend:'fake',byte_length:4};
|
|
412
|
+
const grounderProvider=async()=>({status:'ok',query_sha256:H('legacy'),query_kind:'name_exact',ordinal:0,
|
|
413
|
+
visited:2,truncated:false,provider_receipt_sha256:H('provider'),
|
|
414
|
+
matches:[{rect:{left:100,top:120,width:80,height:30},control_type:50000,enabled:true,offscreen:false}]});
|
|
415
|
+
const fx=await fixture({captureFrames:[f1,f2],grounderProvider});
|
|
416
|
+
const result=await fx.runtime.ground({frameId:fx.expected.frame_id,query:{kind:'name_exact',value:'Save',ordinal:0}});
|
|
417
|
+
await assert.rejects(fx.runtime.action({intentId:H('legacy-noid'),kind:'click_primary',
|
|
418
|
+
expectedFrameId:result.frame.frame_id,targetId:result.target.target_id}),/UIA.*RuntimeId/i);
|
|
419
|
+
assert.equal(fx.calls.length,0);
|
|
420
|
+
});
|
|
421
|
+
test('R199 recreated UIA element at identical coordinates gets a different target id',async()=>{
|
|
422
|
+
const f1={frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4};
|
|
423
|
+
let count=0;
|
|
424
|
+
const grounderProvider=async()=>({status:'ok',query_sha256:H('stable-query'),query_kind:'name_exact',
|
|
425
|
+
ordinal:0,visited:2,truncated:false,provider_receipt_sha256:H('provider'),
|
|
426
|
+
matches:[{rect:{left:100,top:120,width:80,height:30},control_type:50000,
|
|
427
|
+
enabled:true,offscreen:false,runtime_id:[7,++count,9]}]});
|
|
428
|
+
const fx=await fixture({captureFrames:[f1,f1],grounderProvider});
|
|
429
|
+
const query={kind:'name_exact',value:'Save',ordinal:0};
|
|
430
|
+
const first=await fx.runtime.ground({frameId:fx.expected.frame_id,query});
|
|
431
|
+
const next=await fx.runtime.ground({frameId:fx.expected.frame_id,query});
|
|
432
|
+
assert.notEqual(first.target.target_id,next.target.target_id);
|
|
433
|
+
assert.notEqual(first.grounding.element_token_sha256,next.grounding.element_token_sha256);
|
|
434
|
+
assert.equal(fx.calls.length,0);
|
|
435
|
+
});
|
|
@@ -40,3 +40,18 @@ test('T5 UIA response rejects foreground substitution and oversized query',()=>{
|
|
|
40
40
|
assert.throws(()=>parseDesktopUiaResponse(raw,{expectedWindow:win,expectedBounds:bounds,query:{kind:'root',value:'',max_results:1,ordinal:0}}),/hwnd mismatch/);
|
|
41
41
|
assert.throws(()=>serializeDesktopUiaRequest({expectedWindow:win,query:{kind:'name_exact',value:'x'.repeat(513)}}),/bytes invalid/);
|
|
42
42
|
});
|
|
43
|
+
|
|
44
|
+
test('R199 UIA parser keeps bounded native RuntimeId and refuses malformed identities',()=>{
|
|
45
|
+
const query={kind:'name_exact',value:'Save',max_results:2,ordinal:0};
|
|
46
|
+
const make=id=>Buffer.from(JSON.stringify({
|
|
47
|
+
schema:'deadbyte.desktop-uia-ground.v1',foreground_hwnd:win.hwnd,foreground_pid:win.pid,
|
|
48
|
+
query_kind:query.kind,visited:2,truncated:false,matches:[
|
|
49
|
+
{left:10,top:20,width:100,height:30,control_type:50000,enabled:true,offscreen:false,runtime_id:id}
|
|
50
|
+
]}));
|
|
51
|
+
const parse=id=>parseDesktopUiaResponse(make(id),{expectedWindow:win,expectedBounds:bounds,query});
|
|
52
|
+
assert.deepEqual(parse([17,-2147483648,2147483647]).matches[0].runtime_id,[17,-2147483648,2147483647]);
|
|
53
|
+
assert.throws(()=>parse([]),/RuntimeId missing/);
|
|
54
|
+
assert.throws(()=>parse(Array(65).fill(1)),/RuntimeId shape/);
|
|
55
|
+
assert.throws(()=>parse([1,2.3]),/component invalid/);
|
|
56
|
+
assert.throws(()=>parse([1,2147483648]),/component invalid/);
|
|
57
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {rename,symlink,realpath,open,writeFile} from 'node:fs/promises';
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {readRangeFromHandle} from '../../src/machine-fs-handle-read-r196.mjs';
|
|
5
|
+
const [file,off,len,doHash]=process.argv.slice(2);
|
|
6
|
+
const dir=path.dirname(file),project=path.dirname(dir),tmp=path.dirname(project);
|
|
7
|
+
const external=path.join(tmp,'external'),backup=path.join(project,'swapped-original');
|
|
8
|
+
try{
|
|
9
|
+
await writeFile(path.join(tmp,'r197-worker-entered.marker'),'entered\n',{flag:'wx'});
|
|
10
|
+
await rename(dir,backup);
|
|
11
|
+
await symlink(external,dir,'junction');
|
|
12
|
+
const resolved=await realpath(file);
|
|
13
|
+
if(!resolved.toLowerCase().startsWith((external+path.sep).toLowerCase()))throw Error('R197_SWAP_NOT_OUTSIDE');
|
|
14
|
+
const handle=await open(file,'r');
|
|
15
|
+
let result;
|
|
16
|
+
try{result=await readRangeFromHandle(handle,{offset:Number(off),length:Number(len),hashFile:doHash==='1'});}
|
|
17
|
+
finally{await handle.close();}
|
|
18
|
+
const hash=createHash('sha256').update(Buffer.from(result.data_base64,'base64')).digest('hex');
|
|
19
|
+
await writeFile(path.join(tmp,'r197-escaped-open.marker'),hash+'\n',{flag:'wx'});
|
|
20
|
+
process.stdout.write(JSON.stringify(result));
|
|
21
|
+
}catch(e){process.stderr.write('R197_RACE_WORKER '+String(e?.stack??e));process.exitCode=1;}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import {openRemoteDestinationJournalR202} from '../../src/remote-destination-journal-r202.mjs';
|
|
3
|
+
const fixture=JSON.parse(await fs.readFile(process.argv[2],'utf8'));
|
|
4
|
+
const journal=await openRemoteDestinationJournalR202({directory:fixture.directory,device:fixture.device});
|
|
5
|
+
const result=await journal.commitSignedTerminal({request:fixture.request,envelope:fixture.envelope});
|
|
6
|
+
process.stdout.write(JSON.stringify({status:result.status})+'\n');
|
|
@@ -70,13 +70,13 @@ test('R167 process policy binds coherently to the explicit development root', as
|
|
|
70
70
|
// The distributed policy is pinned to its explicit development provenance,
|
|
71
71
|
// not to the physical extraction path of the exact immutable npm tarball.
|
|
72
72
|
const release = JSON.parse(await readFile(path.join(repoRoot, 'RELEASE.json'), 'utf8'));
|
|
73
|
-
const approvedRoot = release.
|
|
74
|
-
assert.equal(typeof approvedRoot, 'string', '
|
|
75
|
-
assert.ok(path.isAbsolute(approvedRoot), '
|
|
73
|
+
const approvedRoot = release.r207?.development_policy_root;
|
|
74
|
+
assert.equal(typeof approvedRoot, 'string', 'R207 development policy root must be explicit in release metadata');
|
|
75
|
+
assert.ok(path.isAbsolute(approvedRoot), 'R207 development policy root must be absolute');
|
|
76
76
|
const expectedRoot = path.resolve(approvedRoot);
|
|
77
77
|
if (process.env.DEADBYTE_EXPECTED_DEVELOPMENT_ROOT) {
|
|
78
78
|
assert.equal(path.resolve(process.env.DEADBYTE_EXPECTED_DEVELOPMENT_ROOT), expectedRoot,
|
|
79
|
-
'caller-supplied development root cannot override
|
|
79
|
+
'caller-supplied development root cannot override R207 release provenance');
|
|
80
80
|
}
|
|
81
81
|
const policyPath = path.join(repoRoot, 'controller', 'deadbyte-process-policy.json');
|
|
82
82
|
const policy = JSON.parse(await readFile(policyPath, 'utf8'));
|
|
@@ -14,14 +14,14 @@ test('current successor retains the R130 security baseline with truthful R190 id
|
|
|
14
14
|
read('SBOM.cdx.json'),readReleaseLock(root),read('TEST-MATRIX.json')]);
|
|
15
15
|
assert.equal(pkg.name,'deadbyte-mcp');
|
|
16
16
|
for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
|
|
17
|
-
assert.equal(actual,'0.15.
|
|
18
|
-
assert.equal(release.train,'
|
|
19
|
-
assert.equal(matrix.train,'
|
|
20
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
21
|
-
assert.equal(release.source_predecessor.train,'
|
|
22
|
-
assert.match(release.source_predecessor.source_path,/
|
|
23
|
-
assert.equal(release.source_predecessor.manifest_status,'
|
|
24
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
17
|
+
assert.equal(actual,'0.15.76');
|
|
18
|
+
assert.equal(release.train,'R207');
|
|
19
|
+
assert.equal(matrix.train,'R207');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.75');
|
|
21
|
+
assert.equal(release.source_predecessor.train,'R206');
|
|
22
|
+
assert.match(release.source_predecessor.source_path,/r206-release-closure/i);
|
|
23
|
+
assert.equal(release.source_predecessor.manifest_status,'R206_MANIFEST_VERIFIED');
|
|
24
|
+
assert.equal(release.runtime_predecessor.version,'0.15.75');
|
|
25
25
|
assert.equal(provenance.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
26
26
|
assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
|
|
27
27
|
assert.equal(pkg.dependencies['@modelcontextprotocol/server'],'2.3.1');
|
|
@@ -16,17 +16,17 @@ test('R190 package identity preserves R132 evidence and published R179 source pr
|
|
|
16
16
|
for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
|
|
17
17
|
assert.equal(value,pkg.version);
|
|
18
18
|
}
|
|
19
|
-
assert.equal(release.train,'
|
|
20
|
-
assert.equal(matrix.train,'
|
|
21
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
22
|
-
assert.equal(release.source_predecessor.train,'
|
|
23
|
-
assert.match(release.source_predecessor.source_path,/
|
|
19
|
+
assert.equal(release.train,'R207');
|
|
20
|
+
assert.equal(matrix.train,'R207');
|
|
21
|
+
assert.equal(release.source_predecessor.version,'0.15.75');
|
|
22
|
+
assert.equal(release.source_predecessor.train,'R206');
|
|
23
|
+
assert.match(release.source_predecessor.source_path,/r206-release-closure/i);
|
|
24
24
|
assert.equal(prov.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
25
25
|
assert.equal(release.r165.published_source_manifest_sha256,'4adf7d94682fbb71c4a91eb3147ecc48076bbc5d79cb67fcb875cde6a1bb4805');
|
|
26
26
|
assert.equal(release.r165.published_source_tarball_sha256,'812e97609b7c51353ed25f2202129737e43bb4fbe0778c3adec43ec065352641');
|
|
27
|
-
assert.equal(release.source_predecessor.tarball_sha256,'
|
|
27
|
+
assert.equal(release.source_predecessor.tarball_sha256,'c2ebb079e7725f3a2f2c7d8f98ef42059e94e51f402d99c1825f46e020237ee9');
|
|
28
28
|
assert.notEqual(release.r179.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
|
|
29
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
29
|
+
assert.equal(release.runtime_predecessor.version,'0.15.75');
|
|
30
30
|
assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
|
|
31
31
|
assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
|
|
32
32
|
assert.equal(prov.runtime_predecessor.generation_manifest_sha256,release.runtime_predecessor.generation_manifest_sha256);
|
|
@@ -26,7 +26,7 @@ test('R153 historical verifier refuses current successor package identity',async
|
|
|
26
26
|
const state={pkg:JSON.parse(await readFile(path.join(root,'package.json'),'utf8')),
|
|
27
27
|
release:JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8')),
|
|
28
28
|
bridge:JSON.parse(await readFile(path.join(root,'bridge/package.json'),'utf8'))};
|
|
29
|
-
assert.equal(state.pkg.version,'0.15.
|
|
29
|
+
assert.equal(state.pkg.version,'0.15.76');
|
|
30
30
|
assert.throws(()=>verifyR153Lineage(state),/package version/);
|
|
31
31
|
await assert.rejects(verifyR153IdentityTree(root),/package version/);
|
|
32
32
|
});
|
|
@@ -8,9 +8,9 @@ const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
|
8
8
|
|
|
9
9
|
test('R160 historical contract remains distinct from the current package identity',async()=>{
|
|
10
10
|
const [release,provenance]=await Promise.all([read('RELEASE.json'),read('PROVENANCE.json')]);
|
|
11
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
12
|
-
assert.equal(release.runtime_predecessor.release_id,'v0.15.
|
|
13
|
-
assert.equal(release.runtime_predecessor.manifest_sha256,'
|
|
11
|
+
assert.equal(release.runtime_predecessor.version,'0.15.75');
|
|
12
|
+
assert.equal(release.runtime_predecessor.release_id,'v0.15.75-9039fff265696091');
|
|
13
|
+
assert.equal(release.runtime_predecessor.manifest_sha256,'9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248');
|
|
14
14
|
assert.equal(release.r160.runtime_predecessor_version,'0.15.61');
|
|
15
15
|
assert.equal(release.r160.source_predecessor_train,'R159');
|
|
16
16
|
assert.equal(release.r160.public_tool_delta,0);
|
|
@@ -17,7 +17,7 @@ const hashMarker=c=>c.repeat(64);
|
|
|
17
17
|
async function fixture(t,kind){
|
|
18
18
|
assert.equal(typeof envRoot,'string'); assert.ok(path.isAbsolute(envRoot));
|
|
19
19
|
assert.match(sha(await readFile(path.join(envRoot,'MANIFEST.SHA256'))),/^[0-9a-f]{64}$/);
|
|
20
|
-
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.
|
|
20
|
+
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.76');
|
|
21
21
|
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r164-skew-e2e-'));
|
|
22
22
|
t.after(async()=>{await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:150});});
|
|
23
23
|
const state=path.join(dir,'state'),bin=path.join(state,'bin');
|