deadbyte-mcp 0.15.27 → 0.15.29
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 +41 -25
- package/PROVENANCE.json +16 -10
- package/README.txt +32 -14
- package/RELEASE.json +55 -15
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +147 -23
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/controller/deadbyte-controller.ps1 +4 -1
- package/controller/deadbyte-process-policy.json +1 -1
- package/docs/R51-PROVENANCE.json +18 -0
- package/docs/R51-RELEASE.json +115 -0
- package/docs/R51-TEST-MATRIX.json +166 -0
- package/docs/R52-ACTIVATION-FORENSICS.md +85 -0
- package/native/deadbyte-process-host.cpp +5 -3
- package/package.json +95 -93
- package/scripts/deploy-r48.mjs +1 -1
- package/scripts/gate-windows-r51.ps1 +58 -0
- package/scripts/gate-windows-r52.ps1 +58 -0
- package/scripts/windows-gate-evidence-r51.mjs +84 -0
- package/scripts/windows-gate-evidence-r52.mjs +94 -0
- package/src/coding-plane.mjs +190 -28
- package/src/coding-task-runtime.mjs +10 -4
- package/src/deadbyte-cli.mjs +88 -28
- package/src/remote-live-log.mjs +17 -5
- package/src/remote-session-guardian.mjs +1 -1
- package/src/runtime-coherence.mjs +50 -0
- package/src/runtime-update.mjs +8 -2
- package/src/version.mjs +1 -1
- package/test/coding-runtime.test.mjs +104 -1
- package/test/helpers/coding-disconnect-server.mjs +11 -0
- package/test/production-docs.test.mjs +5 -3
- package/test/r48-release-contract.test.mjs +7 -30
- package/test/r49-windows-gate.test.mjs +17 -11
- package/test/r51-live-log-polling.test.mjs +28 -0
- package/test/r51-release-contract.test.mjs +58 -0
- package/test/r51-runtime-coherence.test.mjs +59 -0
- package/test/r52-host-revocation.test.mjs +65 -0
- package/test/r52-offline-predecessor-recovery.test.mjs +77 -0
- package/test/r52-release-contract.test.mjs +58 -0
- package/test/remote-live-log-r37.test.mjs +14 -0
- package/test/remote-session-cli.test.mjs +24 -24
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const SEMVER=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
3
|
+
|
|
4
|
+
function parseSemver(value){
|
|
5
|
+
const match=SEMVER.exec(String(value??''));
|
|
6
|
+
if(!match) throw new Error(`runtime version invalid: ${String(value)}`);
|
|
7
|
+
return {major:Number(match[1]),minor:Number(match[2]),patch:Number(match[3]),prerelease:match[4]??null};
|
|
8
|
+
}
|
|
9
|
+
export function compareRuntimeVersions(a,b){
|
|
10
|
+
const left=parseSemver(a),right=parseSemver(b);
|
|
11
|
+
for(const key of ['major','minor','patch']) if(left[key]!==right[key]) return left[key]<right[key]?-1:1;
|
|
12
|
+
if(left.prerelease===right.prerelease) return 0;
|
|
13
|
+
if(left.prerelease===null) return 1;
|
|
14
|
+
if(right.prerelease===null) return -1;
|
|
15
|
+
return left.prerelease.localeCompare(right.prerelease,'en',{numeric:true,sensitivity:'case'});
|
|
16
|
+
}
|
|
17
|
+
function assertManifest(value,label){
|
|
18
|
+
if(!HEX64.test(String(value??''))) throw new Error(`${label} must be lowercase SHA-256`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export async function ensureRuntimeCoherence({
|
|
22
|
+
cliVersion,packageManifestSha256,readActive,disarmPrivileged,activateCurrentPackage,onEvent=()=>{}
|
|
23
|
+
}={}){
|
|
24
|
+
parseSemver(cliVersion);assertManifest(packageManifestSha256,'package manifest');
|
|
25
|
+
if(typeof readActive!=='function'||typeof disarmPrivileged!=='function'||typeof activateCurrentPackage!=='function'||typeof onEvent!=='function') {
|
|
26
|
+
throw new Error('runtime coherence callbacks invalid');
|
|
27
|
+
}
|
|
28
|
+
const initial=await readActive();
|
|
29
|
+
if(!initial) return {status:'unbound',active:null};
|
|
30
|
+
const activeVersion=String(initial.version??'');
|
|
31
|
+
const relation=compareRuntimeVersions(cliVersion,activeVersion);
|
|
32
|
+
if(relation===0){
|
|
33
|
+
if(String(initial.manifest_sha256??'')!==packageManifestSha256) {
|
|
34
|
+
throw new Error(`runtime manifest skew for ${cliVersion}: package ${packageManifestSha256}; active ${String(initial.manifest_sha256??'missing')}`);
|
|
35
|
+
}
|
|
36
|
+
return {status:'coherent',active:initial};
|
|
37
|
+
}
|
|
38
|
+
if(relation<0) throw new Error(`active runtime ${activeVersion} is newer than CLI package ${cliVersion}; refusing automatic downgrade`);
|
|
39
|
+
onEvent({phase:'upgrade_start',from_version:activeVersion,to_version:cliVersion,from_release_id:initial.release_id??null});
|
|
40
|
+
await disarmPrivileged();
|
|
41
|
+
await activateCurrentPackage();
|
|
42
|
+
const active=await readActive();
|
|
43
|
+
if(!active) throw new Error('runtime coherence activation completed without active release pointer');
|
|
44
|
+
if(String(active.version??'')!==cliVersion) throw new Error(`runtime coherence activation version mismatch: expected ${cliVersion}, found ${String(active.version??'missing')}`);
|
|
45
|
+
if(String(active.manifest_sha256??'')!==packageManifestSha256) {
|
|
46
|
+
throw new Error(`runtime coherence activation manifest mismatch: expected ${packageManifestSha256}, found ${String(active.manifest_sha256??'missing')}`);
|
|
47
|
+
}
|
|
48
|
+
onEvent({phase:'upgrade_complete',from_version:activeVersion,to_version:cliVersion,release_id:active.release_id??null});
|
|
49
|
+
return {status:'upgraded',active};
|
|
50
|
+
}
|
package/src/runtime-update.mjs
CHANGED
|
@@ -704,7 +704,7 @@ async function captureRuntime(ctx){
|
|
|
704
704
|
const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
|
|
705
705
|
return {status,status_text:statusText.trim(),bridge,cloud,core};
|
|
706
706
|
}
|
|
707
|
-
async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
707
|
+
export async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
708
708
|
let statusText,status;
|
|
709
709
|
try{
|
|
710
710
|
statusText=controllerCaptured(ctx,'status',15000);
|
|
@@ -738,7 +738,13 @@ async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
|
738
738
|
}
|
|
739
739
|
if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
|
|
740
740
|
if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery requires clean OFFLINE controller state: ${statusText}`);
|
|
741
|
-
if(status.host_armed)
|
|
741
|
+
if(status.host_armed){
|
|
742
|
+
controllerCaptured(ctx,'disarmhost',15000);
|
|
743
|
+
statusText=controllerCaptured(ctx,'status',15000);
|
|
744
|
+
status=parseControllerStatus(statusText);
|
|
745
|
+
if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery changed controller state while revoking Host authority: ${statusText}`);
|
|
746
|
+
if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
|
|
747
|
+
}
|
|
742
748
|
if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
|
|
743
749
|
return {mode:'offline-recovery',status,status_text:statusText.trim(),runtime:null};
|
|
744
750
|
}
|
package/src/version.mjs
CHANGED
|
@@ -7,6 +7,75 @@ import path from 'node:path';
|
|
|
7
7
|
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
8
|
import { assertCodingArmed, createCodingRuntime, loadCodingPolicy, recheckCodingAuthority } from '../src/coding-plane.mjs';
|
|
9
9
|
import { createCodingTaskRuntime } from '../src/coding-task-runtime.mjs';
|
|
10
|
+
import { fork } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
test('R52 caller disconnect retains one durable execution, status, attempts and terminal receipt',async()=>{
|
|
14
|
+
const f=await fixture();
|
|
15
|
+
let server;
|
|
16
|
+
try{
|
|
17
|
+
const script=path.join(f.project,'disconnect.mjs');
|
|
18
|
+
await writeFile(script,"import {appendFileSync} from 'node:fs';appendFileSync('disconnect-runs.txt',process.pid+'\\n');await new Promise(r=>setTimeout(r,750));process.stdout.write('survived-disconnect');");
|
|
19
|
+
const submission={submissionId:'caller-disconnect',steps:[{step_id:'gate',kind:'command',root_id:'project',command_id:'node_script',script_path:'disconnect.mjs',script_sha256:await sha256File(script),timeout_ms:10000}]};
|
|
20
|
+
const task=await f.tasks.submit(submission);
|
|
21
|
+
server=fork(fileURLToPath(new URL('./helpers/coding-disconnect-server.mjs',import.meta.url)),
|
|
22
|
+
[f.policyPath,f.keys.privateKeyPath,f.keys.publicKeyPath],{stdio:['ignore','ignore','ignore','ipc'],windowsHide:true,execArgv:[]});
|
|
23
|
+
const closed=new Promise(resolve=>server.once('exit',resolve));
|
|
24
|
+
server.send({taskId:task.task_id});
|
|
25
|
+
const deadline=Date.now()+10000;
|
|
26
|
+
let started=false;
|
|
27
|
+
while(Date.now()<deadline){
|
|
28
|
+
try{await readFile(path.join(f.project,'disconnect-runs.txt'));started=true;break;}catch{}
|
|
29
|
+
await new Promise(r=>setTimeout(r,20));
|
|
30
|
+
}
|
|
31
|
+
assert.ok(started,'independent runtime did not start');
|
|
32
|
+
server.disconnect(); // Caller transport goes away; execution owner stays live.
|
|
33
|
+
assert.doesNotThrow(()=>process.kill(server.pid,0));
|
|
34
|
+
const duplicate=await f.tasks.submit(submission);
|
|
35
|
+
assert.equal(duplicate.task_id,task.task_id);
|
|
36
|
+
assert.equal(duplicate.status,'existing');
|
|
37
|
+
assert.equal((await f.tasks.status({taskId:task.task_id})).steps[0].attempts,1);
|
|
38
|
+
await closed;
|
|
39
|
+
assert.equal(server.exitCode,0);
|
|
40
|
+
const recovered=createCodingTaskRuntime({codingRuntime:f.runtime,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
|
|
41
|
+
const status=await recovered.status({taskId:task.task_id});
|
|
42
|
+
assert.equal(status.state,'succeeded');
|
|
43
|
+
assert.equal(status.steps[0].attempts,1);
|
|
44
|
+
assert.equal((await recovered.verify({taskId:task.task_id})).ok,true);
|
|
45
|
+
assert.equal((await recovered.run({taskId:task.task_id})).status,'terminal');
|
|
46
|
+
assert.equal((await readFile(path.join(f.project,'disconnect-runs.txt'),'utf8')).trim().split(/\r?\n/).length,1);
|
|
47
|
+
}finally{if(server?.exitCode===null){server.kill();}await rm(f.root,{recursive:true,force:true});}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('R52 cancelling a running durable command stops its owned descendants before terminal state',async()=>{
|
|
51
|
+
const f=await fixture();
|
|
52
|
+
let running;
|
|
53
|
+
try{
|
|
54
|
+
const script=path.join(f.project,'cancel-tree.mjs');
|
|
55
|
+
await writeFile(script,"import {spawn} from 'node:child_process'; import {writeFileSync} from 'node:fs'; const child=spawn(process.execPath,['-e',\"setTimeout(()=>require('node:fs').writeFileSync('escaped.txt','escaped'),4000)\"],{stdio:'inherit'}); writeFileSync('started.txt',String(child.pid)); setTimeout(()=>{},4000);");
|
|
56
|
+
const submission={submissionId:'cancel-running-tree',steps:[{step_id:'gate',kind:'command',root_id:'project',command_id:'node_script',script_path:'cancel-tree.mjs',script_sha256:await sha256File(script),timeout_ms:10000}]};
|
|
57
|
+
const task=await f.tasks.submit(submission);
|
|
58
|
+
running=f.tasks.run({taskId:task.task_id});
|
|
59
|
+
let descendant;
|
|
60
|
+
const deadline=Date.now()+3000;
|
|
61
|
+
while(Date.now()<deadline){
|
|
62
|
+
try{descendant=Number(await readFile(path.join(f.project,'started.txt'),'utf8'));break;}catch{}
|
|
63
|
+
await new Promise(r=>setTimeout(r,20));
|
|
64
|
+
}
|
|
65
|
+
assert.ok(descendant,'fixture did not start descendant');
|
|
66
|
+
const cancelledAt=Date.now();
|
|
67
|
+
await f.tasks.cancel({taskId:task.task_id,reason:'owned-tree-test'});
|
|
68
|
+
const result=await running;
|
|
69
|
+
assert.equal(result.status,'cancelled');
|
|
70
|
+
assert.ok(Date.now()-cancelledAt<2000,'cancellation waited for command to finish');
|
|
71
|
+
assert.throws(()=>process.kill(descendant,0),'owned descendant is still alive after terminal cancellation');
|
|
72
|
+
await assert.rejects(readFile(path.join(f.project,'escaped.txt')),e=>e.code==='ENOENT');
|
|
73
|
+
const status=await f.tasks.status({taskId:task.task_id});
|
|
74
|
+
assert.equal(status.steps[0].attempts,1);
|
|
75
|
+
const executionReceipt=await f.runtime.receiptVerify({receiptId:status.steps[0].receipt_id});
|
|
76
|
+
assert.equal(executionReceipt.ok,true,'cancelled execution must retain its signed terminal observation');
|
|
77
|
+
}finally{await running?.catch(()=>{});await rm(f.root,{recursive:true,force:true});}
|
|
78
|
+
});
|
|
10
79
|
|
|
11
80
|
function sha256Bytes(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
|
|
12
81
|
async function sha256File(file) { return sha256Bytes(await readFile(file)); }
|
|
@@ -39,7 +108,7 @@ async function fixture({ armed = true } = {}) {
|
|
|
39
108
|
}
|
|
40
109
|
const runtime = createCodingRuntime({ policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath });
|
|
41
110
|
const tasks = createCodingTaskRuntime({ codingRuntime: runtime, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath });
|
|
42
|
-
return { root, project, policy, runtime, tasks };
|
|
111
|
+
return { root, project, policyPath, policy, runtime, tasks, keys };
|
|
43
112
|
}
|
|
44
113
|
test('coding plane edits files under lease and verifies signed action receipts', async () => {
|
|
45
114
|
const f = await fixture();
|
|
@@ -233,3 +302,37 @@ test('coding task rejects recomputed journal content without the private signing
|
|
|
233
302
|
await assert.rejects(() => f.tasks.status({ taskId: submitted.task_id }), /signature verification failed|hash-chain mismatch/);
|
|
234
303
|
} finally { await rm(f.root, { recursive: true, force: true }); }
|
|
235
304
|
});
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
test('R51 coding command retries reuse one in-flight execution and completed receipt', async () => {
|
|
308
|
+
const f = await fixture();
|
|
309
|
+
try {
|
|
310
|
+
const script = path.join(f.project, 'dedup.mjs');
|
|
311
|
+
await writeFile(script, "import { appendFileSync } from 'node:fs'; appendFileSync('runs.txt', process.pid+'\\n'); await new Promise(r=>setTimeout(r,250)); process.stdout.write('dedup-ok');");
|
|
312
|
+
const args={rootId:'project',commandId:'node_script',scriptPath:'dedup.mjs',scriptSha256:await sha256File(script),timeoutMs:5000};
|
|
313
|
+
const [a,b]=await Promise.all([f.runtime.commandRun(args),f.runtime.commandRun(args)]);
|
|
314
|
+
assert.equal(a.status,'passed',a.stderr);assert.equal(b.status,'passed',b.stderr);
|
|
315
|
+
assert.equal(a.request_sha256,b.request_sha256);
|
|
316
|
+
assert.equal(a.receipt_id,b.receipt_id,'same logical request must reuse the same signed execution receipt');
|
|
317
|
+
const c=await f.runtime.commandRun(args);
|
|
318
|
+
assert.equal(c.receipt_id,a.receipt_id,'completed retry must reuse bounded cached execution result');
|
|
319
|
+
const runs=(await readFile(path.join(f.project,'runs.txt'),'utf8')).trim().split(/\\r?\\n/).filter(Boolean);
|
|
320
|
+
assert.equal(runs.length,1,'retry spawned duplicate child process');
|
|
321
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('R51 coding command single-flight spans independent runtime instances sharing evidence state', async () => {
|
|
325
|
+
const f = await fixture();
|
|
326
|
+
try {
|
|
327
|
+
const script = path.join(f.project, 'cross-runtime-dedup.mjs');
|
|
328
|
+
await writeFile(script, "import { appendFileSync } from 'node:fs'; appendFileSync('cross-runs.txt', process.pid+'\\n'); await new Promise(r=>setTimeout(r,500)); process.stdout.write('cross-ok');");
|
|
329
|
+
const second = createCodingRuntime({ policy:f.policy, signingKeyPath:f.keys.privateKeyPath, verifyKeyPath:f.keys.publicKeyPath });
|
|
330
|
+
const args={rootId:'project',commandId:'node_script',scriptPath:'cross-runtime-dedup.mjs',scriptSha256:await sha256File(script),timeoutMs:5000};
|
|
331
|
+
const [a,b]=await Promise.all([f.runtime.commandRun(args),second.commandRun(args)]);
|
|
332
|
+
assert.equal(a.status,'passed',a.stderr);assert.equal(b.status,'passed',b.stderr);
|
|
333
|
+
assert.equal(a.request_sha256,b.request_sha256);
|
|
334
|
+
assert.equal(a.receipt_id,b.receipt_id,'shared evidence state must converge on one signed execution receipt');
|
|
335
|
+
const runs=(await readFile(path.join(f.project,'cross-runs.txt'),'utf8')).trim().split(/\\r?\\n/).filter(Boolean);
|
|
336
|
+
assert.equal(runs.length,1,'independent runtime instances spawned duplicate child processes');
|
|
337
|
+
} finally { await rm(f.root,{recursive:true,force:true}); }
|
|
338
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { loadCodingPolicy,createCodingRuntime } from '../../src/coding-plane.mjs';
|
|
2
|
+
import { createCodingTaskRuntime } from '../../src/coding-task-runtime.mjs';
|
|
3
|
+
const [policyPath,signingKeyPath,verifyKeyPath]=process.argv.slice(2);
|
|
4
|
+
const policy=await loadCodingPolicy(policyPath);
|
|
5
|
+
const codingRuntime=createCodingRuntime({policy,signingKeyPath,verifyKeyPath});
|
|
6
|
+
const tasks=createCodingTaskRuntime({codingRuntime,signingKeyPath,verifyKeyPath});
|
|
7
|
+
process.once('message',async({taskId})=>{
|
|
8
|
+
try{await tasks.run({taskId});process.exitCode=0;}
|
|
9
|
+
catch(error){console.error(error);process.exitCode=1;}
|
|
10
|
+
finally{if(process.connected)process.disconnect();}
|
|
11
|
+
});
|
|
@@ -3,15 +3,17 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { DEADBYTE_VERSION } from '../src/version.mjs';
|
|
6
7
|
|
|
7
8
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
8
9
|
const readme=()=>readFile(path.join(root,'README.txt'),'utf8');
|
|
10
|
+
const versionPattern=DEADBYTE_VERSION.replace(/[.*+?^$()|[\]\\]/g,'\\$&');
|
|
9
11
|
|
|
10
12
|
test('production README documents one-command stdio configs for major MCP clients',async()=>{
|
|
11
13
|
const text=await readme();
|
|
12
|
-
assert.match(text
|
|
14
|
+
assert.match(text,new RegExp(`npx -y deadbyte-mcp@${versionPattern} mcp`));
|
|
13
15
|
assert.match(text,/CLAUDE DESKTOP/);
|
|
14
|
-
assert.match(text
|
|
16
|
+
assert.match(text,new RegExp(`"mcpServers"[\\s\\S]*?"command"\\s*:\\s*"npx"[\\s\\S]*?"-y"[\\s\\S]*?"deadbyte-mcp@${versionPattern}"[\\s\\S]*?"mcp"`));
|
|
15
17
|
assert.match(text,/CURSOR/);
|
|
16
18
|
assert.match(text,/\.cursor\/mcp\.json/);
|
|
17
19
|
assert.match(text,/VS CODE/);
|
|
@@ -20,7 +22,7 @@ test('production README documents one-command stdio configs for major MCP client
|
|
|
20
22
|
assert.match(text,/CODEX/);
|
|
21
23
|
assert.match(text,/\[mcp_servers\.deadbyte\]/);
|
|
22
24
|
assert.match(text,/command\s*=\s*"npx"/);
|
|
23
|
-
assert.match(text
|
|
25
|
+
assert.match(text,new RegExp(`args\\s*=\\s*\\["-y",\\s*"deadbyte-mcp@${versionPattern}",\\s*"mcp"\\]`));
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
test('production README records tracer bullet, semantic scope, remote UX, safety and document extension',async()=>{
|
|
@@ -5,37 +5,18 @@ import { DEADBYTE_VERSION } from '../src/version.mjs';
|
|
|
5
5
|
|
|
6
6
|
const root = new URL('../', import.meta.url);
|
|
7
7
|
const read = rel => readFile(new URL(rel, root), 'utf8');
|
|
8
|
+
const json = rel => read(rel).then(JSON.parse);
|
|
8
9
|
|
|
9
|
-
test('
|
|
10
|
+
test('R48 compatibility release contract remains coherent in the current successor', async () => {
|
|
10
11
|
const [pkg, lock, release, provenance, matrix] = await Promise.all([
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
read('RELEASE.json').then(JSON.parse),
|
|
14
|
-
read('PROVENANCE.json').then(JSON.parse),
|
|
15
|
-
read('TEST-MATRIX.json').then(JSON.parse)
|
|
12
|
+
json('package.json'), json('package-lock.json'), json('RELEASE.json'),
|
|
13
|
+
json('PROVENANCE.json'), json('TEST-MATRIX.json')
|
|
16
14
|
]);
|
|
17
15
|
|
|
18
16
|
for (const value of [
|
|
19
|
-
pkg.version,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
DEADBYTE_VERSION,
|
|
23
|
-
release.version,
|
|
24
|
-
provenance.version,
|
|
25
|
-
matrix.version
|
|
26
|
-
]) assert.equal(value, '0.15.27');
|
|
27
|
-
|
|
28
|
-
assert.equal(release.train, 'R50');
|
|
29
|
-
assert.equal(release.label, 'PUBLIC PACKAGE BOUNDARY HOTFIX');
|
|
30
|
-
assert.equal(release.parent.release_id, 'v0.15.26-669ea65d2307ee2c');
|
|
31
|
-
assert.equal(release.parent.manifest_sha256, '669ea65d2307ee2cce06be960e0a8c7243dc6135f7f129a88d99f53096433cc8');
|
|
32
|
-
assert.equal(release.parent.generation_manifest_sha256, '08364adaf1dab9730c314e29121632c6371ed76fc677e83cb7f6aa6b244eab94');
|
|
33
|
-
|
|
34
|
-
assert.equal(release.source_predecessor.version, '0.15.26');
|
|
35
|
-
assert.equal(release.source_predecessor.tarball_sha256, '3272679e3a18610d47e83dc8d3161e949d75a57da145a43c2ab50c865a83bb86');
|
|
36
|
-
assert.equal(release.source_predecessor.manifest_file_sha256, '669ea65d2307ee2cce06be960e0a8c7243dc6135f7f129a88d99f53096433cc8');
|
|
37
|
-
assert.equal(release.source_predecessor.reference_server_sha256, 'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305');
|
|
38
|
-
assert.equal(release.source_predecessor.reference_schemas_sha256, 'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140');
|
|
17
|
+
pkg.version, lock.version, lock.packages[''].version, DEADBYTE_VERSION,
|
|
18
|
+
release.version, provenance.version, matrix.version
|
|
19
|
+
]) assert.equal(value, DEADBYTE_VERSION);
|
|
39
20
|
|
|
40
21
|
assert.equal(release.capabilities.full_tool_count, 117);
|
|
41
22
|
assert.equal(release.capabilities.desktop_bounded_semantic_wait, true);
|
|
@@ -50,8 +31,4 @@ test('R50 0.15.27 successor binds exact published immutable 0.15.26 parent', asy
|
|
|
50
31
|
assert.equal(provenance.parent_manifest_sha256, release.parent.manifest_sha256);
|
|
51
32
|
assert.equal(provenance.parent_generation_manifest_sha256, release.parent.generation_manifest_sha256);
|
|
52
33
|
assert.equal(provenance.source_predecessor_tarball_sha256, release.source_predecessor.tarball_sha256);
|
|
53
|
-
|
|
54
|
-
assert.equal(matrix.train, 'R50');
|
|
55
|
-
assert.equal(matrix.predecessor_r48_full_regression_partition_proof.files, 211);
|
|
56
|
-
assert.equal(matrix.predecessor_r48_full_regression_partition_proof.not_valid_as_r50_proof, true);
|
|
57
34
|
});
|
|
@@ -4,27 +4,33 @@ import test from 'node:test';
|
|
|
4
4
|
|
|
5
5
|
const read = path => readFile(new URL(`../${path}`, import.meta.url), 'utf8');
|
|
6
6
|
|
|
7
|
-
test('
|
|
8
|
-
const [pkgText, gate, evidence,
|
|
7
|
+
test('R52 canonical Windows gate is successor-bound and preserves R51/R50/R49 historical gates', async () => {
|
|
8
|
+
const [pkgText, gate, evidence, historicalR51, historicalEvidenceR51, historicalR50, historicalR49, deferred] = await Promise.all([
|
|
9
9
|
read('package.json'),
|
|
10
|
+
read('scripts/gate-windows-r52.ps1'),
|
|
11
|
+
read('scripts/windows-gate-evidence-r52.mjs'),
|
|
12
|
+
read('scripts/gate-windows-r51.ps1'),
|
|
13
|
+
read('scripts/windows-gate-evidence-r51.mjs'),
|
|
10
14
|
read('scripts/gate-windows-r50.ps1'),
|
|
11
|
-
read('scripts/windows-gate-evidence-r50.mjs'),
|
|
12
15
|
read('scripts/gate-windows-r49.ps1'),
|
|
13
|
-
read('scripts/gate-windows-r48.ps1'),
|
|
14
16
|
read('scripts/deferred-slot-operation-r46.mjs')
|
|
15
17
|
]);
|
|
16
18
|
const pkg = JSON.parse(pkgText);
|
|
17
|
-
assert.match(pkg.scripts['gate:windows'], /gate-windows-
|
|
18
|
-
assert.match(gate, /DEADBYTE 0\.15\.
|
|
19
|
-
assert.match(gate, /
|
|
19
|
+
assert.match(pkg.scripts['gate:windows'], /gate-windows-r52\.ps1$/);
|
|
20
|
+
assert.match(gate, /DEADBYTE 0\.15\.29 \/ R52 WINDOWS GATE: PASS/);
|
|
21
|
+
assert.match(gate, /r52-package-boundary/);
|
|
22
|
+
assert.match(gate, /r52-focused-contracts/);
|
|
20
23
|
assert.match(gate, /r49-remote-control-donors\.test\.mjs/);
|
|
21
24
|
assert.match(gate, /desktop-runtime-r40\.test\.mjs/);
|
|
22
25
|
assert.match(gate, /manifest-final-verify/);
|
|
23
|
-
assert.match(evidence, /deadbyte\.
|
|
24
|
-
assert.match(evidence, /pkg\.version === '0\.15\.
|
|
25
|
-
assert.match(evidence, /release\.train === '
|
|
26
|
+
assert.match(evidence, /deadbyte\.r52-windows-gate\.v1/);
|
|
27
|
+
assert.match(evidence, /pkg\.version === '0\.15\.29'/);
|
|
28
|
+
assert.match(evidence, /release\.train === 'R52'/);
|
|
29
|
+
assert.match(evidence, /source_predecessor\.version === '0\.15\.28'/);
|
|
30
|
+
assert.match(historicalR51, /DEADBYTE 0\.15\.28 \/ R51 WINDOWS GATE: PASS/);
|
|
31
|
+
assert.match(historicalEvidenceR51, /deadbyte\.r51-windows-gate\.v1/);
|
|
32
|
+
assert.match(historicalR50, /DEADBYTE 0\.15\.27 \/ R50 WINDOWS GATE: PASS/);
|
|
26
33
|
assert.match(historicalR49, /DEADBYTE 0\.15\.26 \/ R49 WINDOWS GATE: PASS/);
|
|
27
|
-
assert.match(historicalR48, /DEADBYTE 0\.15\.25 \/ R48 WINDOWS GATE: PASS/);
|
|
28
34
|
assert.match(deferred, /const expectedVersion=packageIdentity\.version/);
|
|
29
35
|
assert.match(deferred, /'--expected-version',expectedVersion/);
|
|
30
36
|
assert.match(deferred, /async function awaitHandoffCompletion/);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { selectLiveLogNames } from '../src/remote-live-log.mjs';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
test('R51 live log discovery tails only current and previous audit segments plus runtime logs',()=>{
|
|
7
|
+
const selected=selectLiveLogNames({
|
|
8
|
+
auditNames:['segment-00000001.jsonl','junk.txt','segment-00000003.jsonl','segment-00000002.jsonl'],
|
|
9
|
+
runtimeNames:['bridge.stdout.log','bridge.stderr.log','note.txt']
|
|
10
|
+
});
|
|
11
|
+
assert.deepEqual(selected.audit,['segment-00000002.jsonl','segment-00000003.jsonl']);
|
|
12
|
+
assert.deepEqual(selected.runtime,['bridge.stderr.log','bridge.stdout.log']);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('R51 live tail caches directory discovery instead of rescanning every 250ms poll',async()=>{
|
|
16
|
+
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
17
|
+
assert.match(cli,/LIVE_LOG_DISCOVERY_INTERVAL_MS\s*=\s*1000/);
|
|
18
|
+
assert.match(cli,/nextDiscoveryAt/);
|
|
19
|
+
assert.doesNotMatch(cli,/for\(const file of await listLogFiles\(root\)\)/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('R51 live tail parses each audit line once and reuses the decoded event',async()=>{
|
|
23
|
+
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
24
|
+
assert.match(cli,/parseAuditLogLine/);
|
|
25
|
+
assert.match(cli,/auditEvent/);
|
|
26
|
+
assert.match(cli,/auditTimelineRecord\(file,line,auditEvent\)/);
|
|
27
|
+
assert.match(cli,/formatLogLine\(file,line,\{auditEvent,/);
|
|
28
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
const root=new URL('../',import.meta.url);
|
|
7
|
+
const read=rel=>readFile(new URL(rel,root));
|
|
8
|
+
const json=rel=>read(rel).then(bytes=>JSON.parse(bytes.toString('utf8')));
|
|
9
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
test('R51 historical release metadata remains byte-frozen at published 0.15.28',async()=>{
|
|
12
|
+
const [releaseBytes,provenanceBytes,matrixBytes]=await Promise.all([
|
|
13
|
+
read('docs/R51-RELEASE.json'),
|
|
14
|
+
read('docs/R51-PROVENANCE.json'),
|
|
15
|
+
read('docs/R51-TEST-MATRIX.json')
|
|
16
|
+
]);
|
|
17
|
+
assert.equal(sha256(releaseBytes),'b89d0eaf4f07d704d2170782f10e4647daec55b5c5bdc70473865c0dae88650b');
|
|
18
|
+
assert.equal(sha256(provenanceBytes),'78e6798e92c2d9134b2e66557b2de7863367124efbcecdd5c91012bfed19a48f');
|
|
19
|
+
assert.equal(sha256(matrixBytes),'eb4d8047fe4ead69a73b3d89f2d443b6fdd88bd39bd8455b80ecaf512cb49abf');
|
|
20
|
+
const release=JSON.parse(releaseBytes.toString('utf8'));
|
|
21
|
+
const provenance=JSON.parse(provenanceBytes.toString('utf8'));
|
|
22
|
+
const matrix=JSON.parse(matrixBytes.toString('utf8'));
|
|
23
|
+
assert.equal(release.version,'0.15.28');
|
|
24
|
+
assert.equal(release.train,'R51');
|
|
25
|
+
assert.equal(provenance.version,'0.15.28');
|
|
26
|
+
assert.equal(matrix.version,'0.15.28');
|
|
27
|
+
assert.equal(matrix.train,'R51');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('R51 historical lineage binds exact 0.15.27 parent and 0.15.27 source predecessor',async()=>{
|
|
31
|
+
const [release,provenance]=await Promise.all([json('docs/R51-RELEASE.json'),json('docs/R51-PROVENANCE.json')]);
|
|
32
|
+
const expected={
|
|
33
|
+
release_id:'v0.15.27-1b1dbe24b45ad06a',
|
|
34
|
+
manifest_sha256:'1b1dbe24b45ad06a62771d15f510662952f3d7c3e5aa08053c6c61511c523e47',
|
|
35
|
+
generation_manifest_sha256:'f87a1637b3c55ba050d079d8a5e0862a2244b306be7e985880708e0d8ded3db0'
|
|
36
|
+
};
|
|
37
|
+
assert.deepEqual(release.parent,expected);
|
|
38
|
+
assert.equal(release.source_predecessor.version,'0.15.27');
|
|
39
|
+
assert.equal(release.source_predecessor.tarball_sha256,'e728031d097d8678dfef80f8648cce65e72a96611052c31969c80f884506d1d7');
|
|
40
|
+
assert.equal(release.source_predecessor.manifest_file_sha256,expected.manifest_sha256);
|
|
41
|
+
assert.equal(provenance.parent_release_id,expected.release_id);
|
|
42
|
+
assert.equal(provenance.parent_manifest_sha256,expected.manifest_sha256);
|
|
43
|
+
assert.equal(provenance.parent_generation_manifest_sha256,expected.generation_manifest_sha256);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('R51 historical capability claims remain frozen',async()=>{
|
|
47
|
+
const release=await json('docs/R51-RELEASE.json');
|
|
48
|
+
for(const key of [
|
|
49
|
+
'runtime_package_auto_coherence',
|
|
50
|
+
'coding_command_singleflight',
|
|
51
|
+
'coding_command_result_dedup_cache',
|
|
52
|
+
'foreground_desktop_host_fail_closed',
|
|
53
|
+
'human_livelog_slow_call_marker',
|
|
54
|
+
'human_livelog_peak_inflight',
|
|
55
|
+
'human_livelog_bounded_discovery',
|
|
56
|
+
'human_livelog_single_parse'
|
|
57
|
+
]) assert.equal(release.capabilities[key],true,key);
|
|
58
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ensureRuntimeCoherence } from '../src/runtime-coherence.mjs';
|
|
4
|
+
|
|
5
|
+
const hex=value=>value.repeat(64).slice(0,64);
|
|
6
|
+
|
|
7
|
+
test('R51 runtime coherence upgrades older active slot exactly once and verifies final identity',async()=>{
|
|
8
|
+
let active={version:'0.15.27',manifest_sha256:hex('1'),release_id:'old'};
|
|
9
|
+
let disarms=0,activations=0;
|
|
10
|
+
const events=[];
|
|
11
|
+
const result=await ensureRuntimeCoherence({
|
|
12
|
+
cliVersion:'0.15.28',packageManifestSha256:hex('2'),
|
|
13
|
+
readActive:async()=>active,
|
|
14
|
+
disarmPrivileged:async()=>{disarms++;},
|
|
15
|
+
activateCurrentPackage:async()=>{activations++;active={version:'0.15.28',manifest_sha256:hex('2'),release_id:'new'};},
|
|
16
|
+
onEvent:event=>events.push(event)
|
|
17
|
+
});
|
|
18
|
+
assert.equal(result.status,'upgraded');
|
|
19
|
+
assert.equal(result.active.version,'0.15.28');
|
|
20
|
+
assert.equal(disarms,1);assert.equal(activations,1);
|
|
21
|
+
assert.deepEqual(events.map(event=>event.phase),['upgrade_start','upgrade_complete']);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('R51 runtime coherence is non-mutating when version and manifest already match',async()=>{
|
|
25
|
+
let mutations=0;
|
|
26
|
+
const active={version:'0.15.28',manifest_sha256:hex('a'),release_id:'same'};
|
|
27
|
+
const result=await ensureRuntimeCoherence({cliVersion:'0.15.28',packageManifestSha256:hex('a'),readActive:async()=>active,
|
|
28
|
+
disarmPrivileged:async()=>{mutations++;},activateCurrentPackage:async()=>{mutations++;}});
|
|
29
|
+
assert.equal(result.status,'coherent');assert.equal(mutations,0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('R51 runtime coherence refuses downgrade and same-version manifest drift',async()=>{
|
|
33
|
+
await assert.rejects(()=>ensureRuntimeCoherence({cliVersion:'0.15.28',packageManifestSha256:hex('a'),
|
|
34
|
+
readActive:async()=>({version:'0.15.29',manifest_sha256:hex('b')}),disarmPrivileged:async()=>{},activateCurrentPackage:async()=>{}}),/newer than CLI/i);
|
|
35
|
+
await assert.rejects(()=>ensureRuntimeCoherence({cliVersion:'0.15.28',packageManifestSha256:hex('a'),
|
|
36
|
+
readActive:async()=>({version:'0.15.28',manifest_sha256:hex('b')}),disarmPrivileged:async()=>{},activateCurrentPackage:async()=>{}}),/manifest skew/i);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
test('R51 bare remote wires coherence before live generation binding and deploy honors state root override',async()=>{
|
|
41
|
+
const {readFile}=await import('node:fs/promises');
|
|
42
|
+
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
43
|
+
const deploy=await readFile(new URL('../scripts/deploy-r48.mjs',import.meta.url),'utf8');
|
|
44
|
+
assert.match(cli,/ensureRuntimeCoherence/);
|
|
45
|
+
assert.match(cli,/synchronizeForegroundRuntime/);
|
|
46
|
+
const syncAt=cli.indexOf('await synchronizeForegroundRuntime');
|
|
47
|
+
const rootsAt=cli.indexOf('await readLiveRootPaths');
|
|
48
|
+
assert.ok(syncAt>=0&&rootsAt>syncAt,'runtime coherence must complete before binding generation root paths');
|
|
49
|
+
assert.match(deploy,/process\.env\.DEADBYTE_STATE_ROOT/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
test('R51 foreground authority contract explicitly verifies and cleans desktop plus host',async()=>{
|
|
54
|
+
const {readFile}=await import('node:fs/promises');
|
|
55
|
+
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
56
|
+
assert.match(cli,/\['desktopstatus',\/\^Desktop plane/);
|
|
57
|
+
assert.match(cli,/SESSION_CLEANUP=.*disarmdesktop.*disarmhost/s);
|
|
58
|
+
assert.match(cli,/desktop, host DISARMED/);
|
|
59
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
async function runRevoke(t,prelude){
|
|
7
|
+
if(process.platform!=='win32'){t.skip('Windows controller behavior');return;}
|
|
8
|
+
const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
|
|
9
|
+
const begin=source.indexOf('function Disarm-Host(');
|
|
10
|
+
const end=source.indexOf('function Show-HostStatus(',begin);
|
|
11
|
+
assert.ok(begin>=0&&end>begin);
|
|
12
|
+
// Execute the maintained function, not a copied implementation. Simulate a
|
|
13
|
+
// filesystem that refuses deletion while Test-Path still sees the grant.
|
|
14
|
+
const script=`$ErrorActionPreference='Stop'
|
|
15
|
+
function Get-HostPolicyMetadata($Cfg) { return @{ArmFile='fixture-host-grant'} }
|
|
16
|
+
${prelude}
|
|
17
|
+
${source.slice(begin,end)}
|
|
18
|
+
try { Disarm-Host @{}; exit 0 } catch { Write-Output $_.Exception.Message; exit 7 }
|
|
19
|
+
`;
|
|
20
|
+
return spawnSync('C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
|
21
|
+
['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(script,'utf16le').toString('base64')],
|
|
22
|
+
{encoding:'utf8',windowsHide:true,timeout:15000});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('R52 controller never reports successful Host revoke while grant removal failed',async t=>{
|
|
26
|
+
const result=await runRevoke(t,`
|
|
27
|
+
function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) if($ErrorAction -eq 'Stop'){throw 'fixture access denied'} }
|
|
28
|
+
function Test-Path { param($LiteralPath) return $true }
|
|
29
|
+
`);
|
|
30
|
+
if(!result)return;
|
|
31
|
+
assert.equal(result.status,7,`Host revoke falsely succeeded: ${result.stdout} ${result.stderr}`);
|
|
32
|
+
assert.doesNotMatch(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R52 controller rejects a grant that remains after nominally successful removal',async t=>{
|
|
36
|
+
const result=await runRevoke(t,`
|
|
37
|
+
function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) }
|
|
38
|
+
function Test-Path { param($LiteralPath) return $true }
|
|
39
|
+
`);
|
|
40
|
+
if(!result)return;
|
|
41
|
+
assert.equal(result.status,7);
|
|
42
|
+
assert.match(result.stdout,/grant remains present/);
|
|
43
|
+
assert.doesNotMatch(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('R52 controller revocation is idempotent when no grant exists',async t=>{
|
|
47
|
+
const result=await runRevoke(t,`
|
|
48
|
+
function Remove-Item { throw 'must not remove absent grant' }
|
|
49
|
+
function Test-Path { param($LiteralPath) return $false }
|
|
50
|
+
`);
|
|
51
|
+
if(!result)return;
|
|
52
|
+
assert.equal(result.status,0);
|
|
53
|
+
assert.match(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('R52 controller reports disarmed only after observing successful removal',async t=>{
|
|
57
|
+
const result=await runRevoke(t,`
|
|
58
|
+
$script:present=$true
|
|
59
|
+
function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) $script:present=$false }
|
|
60
|
+
function Test-Path { param($LiteralPath) return $script:present }
|
|
61
|
+
`);
|
|
62
|
+
if(!result)return;
|
|
63
|
+
assert.equal(result.status,0);
|
|
64
|
+
assert.match(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
|
|
65
|
+
});
|