deadbyte-mcp 0.15.28 → 0.15.30
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 +76 -54
- package/PROVENANCE.json +21 -10
- package/README.txt +16 -14
- package/RELEASE.json +29 -10
- package/SBOM.cdx.json +3 -3
- package/STABILIZATION-PROGRESS.md +60 -0
- package/TEST-MATRIX.json +51 -93
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/deadbyte-controller.ps1 +4 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- package/controller/deadbyte-host-policy.json +170 -0
- package/controller/deadbyte-process-policy.json +5 -5
- 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 +96 -94
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/gate-windows-r52.ps1 +58 -0
- package/scripts/gate-windows-r53.ps1 +58 -0
- package/scripts/verify-r42-parent.mjs +1 -1
- package/scripts/verify-r43-parent.mjs +1 -1
- package/scripts/verify-r44-parent.mjs +1 -1
- package/scripts/verify-r45-parent.mjs +1 -1
- package/scripts/windows-gate-evidence-r52.mjs +94 -0
- package/scripts/windows-gate-evidence-r53.mjs +102 -0
- package/src/authority-smoke.mjs +1 -0
- package/src/coding-plane.mjs +43 -12
- package/src/coding-task-runtime.mjs +10 -4
- package/src/deadbyte-cli.mjs +4 -2
- package/src/doctor-r45.mjs +2 -1
- package/src/machine-fs-smoke-client.mjs +1 -0
- package/src/mcp-contained-smoke-client.mjs +1 -0
- package/src/mcp-host-smoke-client.mjs +1 -0
- package/src/mcp-smoke-client.mjs +1 -0
- package/src/process-smoke-client.mjs +1 -0
- package/src/release-slot.mjs +5 -2
- package/src/remote-session-guardian.mjs +12 -4
- package/src/runtime-coherence.mjs +13 -4
- package/src/runtime-update.mjs +28 -7
- package/src/state-root.mjs +8 -0
- package/src/trust.mjs +2 -1
- package/src/tunnel-stdio-compat-smoke.mjs +1 -0
- package/src/version.mjs +1 -1
- package/test/coding-runtime.test.mjs +70 -1
- package/test/commander-biff-reference-r48.test.mjs +1 -1
- package/test/helpers/coding-disconnect-server.mjs +11 -0
- package/test/r44-finalizer.test.mjs +4 -3
- package/test/r49-windows-gate.test.mjs +19 -9
- package/test/r51-release-contract.test.mjs +22 -20
- 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 +64 -0
- package/test/r53-release-contract.test.mjs +66 -0
- package/test/release-slot.test.mjs +2 -1
- package/test/skill-mcp-r44.test.mjs +3 -0
- package/test/stabilization-guardian.test.mjs +25 -0
- package/test/stabilization-parent-context.test.mjs +18 -0
- package/test/stabilization-powershell.test.mjs +26 -0
- package/test/stabilization-semver.test.mjs +23 -0
- package/test/stabilization-smoke-root.test.mjs +23 -0
- package/test/stabilization-state-root.test.mjs +60 -0
- package/test/stabilization-update-child-root.test.mjs +20 -0
|
@@ -52,6 +52,7 @@ const client = new Client({ name: 'deadbyte-machine-fs-smoke', version: '0.8.0'
|
|
|
52
52
|
const transport = new StdioClientTransport({
|
|
53
53
|
command: process.execPath, args: [serverPath], cwd: repoRoot,
|
|
54
54
|
env: { ...getDefaultEnvironment(), DEADBYTE_CODING_RUNTIME: '1', DEADBYTE_MACHINE_FS_RUNTIME: '1',
|
|
55
|
+
DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(scratch,'state')),
|
|
55
56
|
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
56
57
|
DEADBYTE_CODING_POLICY: policyPath, DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
57
58
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath }, stderr: 'pipe'
|
|
@@ -34,6 +34,7 @@ const transport = new StdioClientTransport({
|
|
|
34
34
|
cwd: repoRoot,
|
|
35
35
|
env: {
|
|
36
36
|
...getDefaultEnvironment(),
|
|
37
|
+
DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(trust,'state')),
|
|
37
38
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
38
39
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
39
40
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath
|
|
@@ -66,6 +66,7 @@ const transport = new StdioClientTransport({
|
|
|
66
66
|
cwd: repoRoot,
|
|
67
67
|
env: {
|
|
68
68
|
...getDefaultEnvironment(),
|
|
69
|
+
DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(temp,'state')),
|
|
69
70
|
DEADBYTE_WORKSPACE_ROOT: path.join(repoRoot, 'workspaces'),
|
|
70
71
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
71
72
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
package/src/mcp-smoke-client.mjs
CHANGED
|
@@ -37,6 +37,7 @@ const transport = new StdioClientTransport({
|
|
|
37
37
|
cwd: repoRoot,
|
|
38
38
|
env: {
|
|
39
39
|
...getDefaultEnvironment(),
|
|
40
|
+
DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(smokeTrust,'state')),
|
|
40
41
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
41
42
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
42
43
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
|
@@ -39,6 +39,7 @@ const client = new Client({ name: 'deadbyte-process-smoke', version: '0.8.0' },
|
|
|
39
39
|
const transport = new StdioClientTransport({
|
|
40
40
|
command: process.execPath, args: [serverPath], cwd: repoRoot,
|
|
41
41
|
env: { ...getDefaultEnvironment(), DEADBYTE_PRIVILEGED_POLICY: '', DEADBYTE_MACHINE_RUNTIME:'0', DEADBYTE_AGENT_RUNTIME:'0',
|
|
42
|
+
DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(smokeTrust,'state')),
|
|
42
43
|
DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY: smokePolicyPath, DEADBYTE_CODING_RUNTIME:'0', DEADBYTE_MACHINE_FS_RUNTIME:'0',
|
|
43
44
|
DEADBYTE_OBSERVATION_RUNTIME:'0', DEADBYTE_DESKTOP_RUNTIME:'0', DEADBYTE_AUTONOMOUS_RUNTIME:'0', DEADBYTE_AUDIT_RUNTIME:'0',
|
|
44
45
|
DEADBYTE_CAPABILITY_PROFILE:'full', DEADBYTE_SIGNING_KEY: keys.privateKeyPath, DEADBYTE_VERIFY_KEY: keys.publicKeyPath },
|
package/src/release-slot.mjs
CHANGED
|
@@ -195,7 +195,8 @@ export function renderStableLauncherCmd(){
|
|
|
195
195
|
return [
|
|
196
196
|
'@echo off',
|
|
197
197
|
'setlocal EnableExtensions DisableDelayedExpansion',
|
|
198
|
-
'
|
|
198
|
+
'if not defined DEADBYTE_STATE_ROOT set "DEADBYTE_STATE_ROOT=%USERPROFILE%\\.deadbyte-mcp"',
|
|
199
|
+
'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
|
|
199
200
|
'exit /b %ERRORLEVEL%',
|
|
200
201
|
''
|
|
201
202
|
].join('\r\n');
|
|
@@ -208,7 +209,9 @@ export function renderStableLauncherPs1(){
|
|
|
208
209
|
'param([Parameter(ValueFromRemainingArguments=$true)][string[]]$ControllerArgs)',
|
|
209
210
|
'Set-StrictMode -Version 3.0',
|
|
210
211
|
"$ErrorActionPreference = 'Stop'",
|
|
211
|
-
"$StateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'",
|
|
212
|
+
"$StateRoot = if ([string]::IsNullOrEmpty($env:DEADBYTE_STATE_ROOT)) { Join-Path $env:USERPROFILE '.deadbyte-mcp' } else { $env:DEADBYTE_STATE_ROOT }",
|
|
213
|
+
'$StateRoot = [IO.Path]::GetFullPath($StateRoot)',
|
|
214
|
+
'$env:DEADBYTE_STATE_ROOT = $StateRoot',
|
|
212
215
|
"$PointerPath = Join-Path $StateRoot 'active-release.json'",
|
|
213
216
|
"function Fail([string]$m) { throw ('DEADBYTE launcher: ' + $m) }",
|
|
214
217
|
"if (-not (Test-Path -LiteralPath $PointerPath -PathType Leaf)) { Fail 'active release pointer missing' }",
|
|
@@ -4,6 +4,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
7
|
+
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
8
|
+
import { resolveStateRoot } from './state-root.mjs';
|
|
7
9
|
|
|
8
10
|
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
|
|
9
11
|
|
|
@@ -36,9 +38,9 @@ function markerMatches(marker,{ownerPid,nonce}){
|
|
|
36
38
|
marker?.owner_pid===ownerPid&&
|
|
37
39
|
marker?.session_nonce===nonce;
|
|
38
40
|
}
|
|
39
|
-
function invokeCleanup(powershell,launcher,command){
|
|
41
|
+
function invokeCleanup(powershell,launcher,command,stateRoot){
|
|
40
42
|
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
|
|
41
|
-
stdio:'ignore',windowsHide:true,shell:false
|
|
43
|
+
stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
|
|
42
44
|
});
|
|
43
45
|
return !result.error&&result.status===0;
|
|
44
46
|
}
|
|
@@ -46,7 +48,7 @@ function invokeCleanup(powershell,launcher,command){
|
|
|
46
48
|
async function main(){
|
|
47
49
|
if(process.platform!=='win32') return;
|
|
48
50
|
const args=parseArgs(process.argv.slice(2));
|
|
49
|
-
const root=
|
|
51
|
+
const root=resolveStateRoot(args['state-root']);
|
|
50
52
|
const ownerPid=Number(args['owner-pid']);
|
|
51
53
|
const nonce=String(args['session-nonce']||'');
|
|
52
54
|
if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
|
|
@@ -66,7 +68,13 @@ async function main(){
|
|
|
66
68
|
continue;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
const failed=[];
|
|
72
|
+
for(const command of CLEANUP){
|
|
73
|
+
const latest=await readMarker(markerPath);
|
|
74
|
+
if(!latest||!markerMatches(latest,{ownerPid,nonce})) return;
|
|
75
|
+
if(!invokeCleanup(powershell,launcher,command,root)) failed.push(command);
|
|
76
|
+
}
|
|
77
|
+
if(failed.length) throw new Error('guardian cleanup failed; ownership marker retained: '+failed.join(','));
|
|
70
78
|
const current=await readMarker(markerPath);
|
|
71
79
|
if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
|
|
72
80
|
return;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const SEMVER=/^(
|
|
1
|
+
const SEMVER=/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2
2
|
const HEX64=/^[0-9a-f]{64}$/;
|
|
3
3
|
|
|
4
4
|
function parseSemver(value){
|
|
5
5
|
const match=SEMVER.exec(String(value??''));
|
|
6
|
-
if(!match) throw new Error(`runtime version invalid: ${String(value)}`);
|
|
7
|
-
return {major:
|
|
6
|
+
if(!match||match[0]!==String(value??'')||(match[4]??'').split('.').some(id=>/^0[0-9]+$/.test(id))) throw new Error(`runtime version invalid: ${String(value)}`);
|
|
7
|
+
return {major:BigInt(match[1]),minor:BigInt(match[2]),patch:BigInt(match[3]),prerelease:match[4]??null};
|
|
8
8
|
}
|
|
9
9
|
export function compareRuntimeVersions(a,b){
|
|
10
10
|
const left=parseSemver(a),right=parseSemver(b);
|
|
@@ -12,7 +12,16 @@ export function compareRuntimeVersions(a,b){
|
|
|
12
12
|
if(left.prerelease===right.prerelease) return 0;
|
|
13
13
|
if(left.prerelease===null) return 1;
|
|
14
14
|
if(right.prerelease===null) return -1;
|
|
15
|
-
|
|
15
|
+
const aIds=left.prerelease.split('.'),bIds=right.prerelease.split('.');
|
|
16
|
+
for(let i=0;i<Math.min(aIds.length,bIds.length);i++){
|
|
17
|
+
const x=aIds[i],y=bIds[i];
|
|
18
|
+
if(x===y) continue;
|
|
19
|
+
const xn=/^[0-9]+$/.test(x),yn=/^[0-9]+$/.test(y);
|
|
20
|
+
if(xn&&yn) return BigInt(x)<BigInt(y)?-1:1;
|
|
21
|
+
if(xn!==yn) return xn?-1:1;
|
|
22
|
+
return x<y?-1:1;
|
|
23
|
+
}
|
|
24
|
+
return aIds.length<bIds.length?-1:aIds.length>bIds.length?1:0;
|
|
16
25
|
}
|
|
17
26
|
function assertManifest(value,label){
|
|
18
27
|
if(!HEX64.test(String(value??''))) throw new Error(`${label} must be lowercase SHA-256`);
|
package/src/runtime-update.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
verifyManifestTree
|
|
18
18
|
} from './release-slot.mjs';
|
|
19
19
|
import { createGenerationBundle, verifyGenerationBundle } from './release-generation.mjs';
|
|
20
|
+
import { resolveStateRoot } from './state-root.mjs';
|
|
20
21
|
|
|
21
22
|
const HEX64=/^[0-9a-f]{64}$/;
|
|
22
23
|
const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -25,7 +26,7 @@ const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
|
|
|
25
26
|
const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
|
|
26
27
|
const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
|
|
27
28
|
const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
|
|
28
|
-
const DEFAULT_STATE_ROOT=
|
|
29
|
+
const DEFAULT_STATE_ROOT=undefined;
|
|
29
30
|
const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
|
|
30
31
|
const RUNTIME_STABILITY_MS=12000;
|
|
31
32
|
const RUNTIME_STABILITY_POLL_MS=500;
|
|
@@ -195,11 +196,21 @@ export function windowsPowerShellEnvironment(base=process.env){
|
|
|
195
196
|
const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
|
|
196
197
|
const userProfile=base.USERPROFILE||os.homedir();
|
|
197
198
|
const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
|
|
198
|
-
|
|
199
|
+
const environment={...base};
|
|
200
|
+
const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
|
|
201
|
+
for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
|
|
202
|
+
const modules=[
|
|
203
|
+
path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
|
|
199
204
|
path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
|
|
200
205
|
path.join(programFiles,'WindowsPowerShell','Modules'),
|
|
201
|
-
|
|
202
|
-
]
|
|
206
|
+
...inherited
|
|
207
|
+
];
|
|
208
|
+
const seen=new Set();
|
|
209
|
+
return {...environment,PSModulePath:modules.filter(modulePath=>{
|
|
210
|
+
const key=modulePath.toLowerCase();
|
|
211
|
+
if(seen.has(key)) return false;
|
|
212
|
+
seen.add(key);return true;
|
|
213
|
+
}).join(';')};
|
|
203
214
|
}
|
|
204
215
|
function commandEnvironment(exe,base=process.env){
|
|
205
216
|
return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
|
|
@@ -582,7 +593,7 @@ function controllerInvocation(ctx,command){
|
|
|
582
593
|
exe:powershellPath(),
|
|
583
594
|
args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],
|
|
584
595
|
cwd:ctx.cwd,
|
|
585
|
-
env:{...process.env,DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
|
|
596
|
+
env:{...process.env,DEADBYTE_STATE_ROOT:resolveStateRoot(ctx.state_root),DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
|
|
586
597
|
};
|
|
587
598
|
return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],cwd:ctx.cwd};
|
|
588
599
|
}
|
|
@@ -704,7 +715,7 @@ async function captureRuntime(ctx){
|
|
|
704
715
|
const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
|
|
705
716
|
return {status,status_text:statusText.trim(),bridge,cloud,core};
|
|
706
717
|
}
|
|
707
|
-
async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
718
|
+
export async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
708
719
|
let statusText,status;
|
|
709
720
|
try{
|
|
710
721
|
statusText=controllerCaptured(ctx,'status',15000);
|
|
@@ -738,7 +749,13 @@ async function capturePredecessor(ctx,recoverOfflinePredecessor){
|
|
|
738
749
|
}
|
|
739
750
|
if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
|
|
740
751
|
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)
|
|
752
|
+
if(status.host_armed){
|
|
753
|
+
controllerCaptured(ctx,'disarmhost',15000);
|
|
754
|
+
statusText=controllerCaptured(ctx,'status',15000);
|
|
755
|
+
status=parseControllerStatus(statusText);
|
|
756
|
+
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}`);
|
|
757
|
+
if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
|
|
758
|
+
}
|
|
742
759
|
if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
|
|
743
760
|
return {mode:'offline-recovery',status,status_text:statusText.trim(),runtime:null};
|
|
744
761
|
}
|
|
@@ -1405,6 +1422,7 @@ async function resumeSlotUpdate({handoffPath}){
|
|
|
1405
1422
|
}
|
|
1406
1423
|
}
|
|
1407
1424
|
export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROOT}={}){
|
|
1425
|
+
stateRoot=resolveStateRoot(stateRoot);
|
|
1408
1426
|
const evidenceRoot=path.join(path.resolve(stateRoot),'evidence','updates');
|
|
1409
1427
|
if(!existsSync(evidenceRoot)) return {ok:true,launched:0,updates:[]};
|
|
1410
1428
|
const entries=await readdir(evidenceRoot,{withFileTypes:true});
|
|
@@ -1437,6 +1455,7 @@ export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROO
|
|
|
1437
1455
|
}
|
|
1438
1456
|
|
|
1439
1457
|
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,codingPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
1458
|
+
stateRoot=resolveStateRoot(stateRoot);
|
|
1440
1459
|
if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
|
|
1441
1460
|
const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
|
|
1442
1461
|
const key=await loadUpdateKey(state),updateId='r27-'+expectedManifest.slice(0,16)+'-'+Date.now().toString(36)+'-'+randomBytes(4).toString('hex');
|
|
@@ -1521,6 +1540,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1521
1540
|
}
|
|
1522
1541
|
|
|
1523
1542
|
export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_STATE_ROOT}){
|
|
1543
|
+
stateRoot=resolveStateRoot(stateRoot);
|
|
1524
1544
|
if(process.platform!=='win32') throw new Error('R31 slot rollback is Windows-only');
|
|
1525
1545
|
const state=path.resolve(stateRoot),receiptFile=path.resolve(activationReceipt);
|
|
1526
1546
|
const key=await loadUpdateKey(state);
|
|
@@ -1572,6 +1592,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
|
|
|
1572
1592
|
}
|
|
1573
1593
|
|
|
1574
1594
|
export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEFAULT_STATE_ROOT}={}){
|
|
1595
|
+
stateRoot=resolveStateRoot(stateRoot);
|
|
1575
1596
|
if(argv.includes('--rollback-receipt')){
|
|
1576
1597
|
const args=parseRollbackArgs(argv);
|
|
1577
1598
|
const result=await performSlotRollback({activationReceipt:args.activationReceipt,stateRoot});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function resolveStateRoot(explicit,environment=process.env){
|
|
5
|
+
const value=explicit===undefined?(environment.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')):explicit;
|
|
6
|
+
if(typeof value!=='string'||value.length===0||value.includes('\0')) throw new Error('state root invalid');
|
|
7
|
+
return path.resolve(value);
|
|
8
|
+
}
|
package/src/trust.mjs
CHANGED
|
@@ -9,11 +9,12 @@ import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { sha256Bytes } from './hash.mjs';
|
|
12
|
+
import { resolveStateRoot } from './state-root.mjs';
|
|
12
13
|
|
|
13
14
|
const DOMAIN = Buffer.from('DEADBYTE-RECEIPT-V1\0', 'utf8');
|
|
14
15
|
|
|
15
16
|
export function defaultTrustDir() {
|
|
16
|
-
return path.join(
|
|
17
|
+
return path.join(resolveStateRoot(), 'trust');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function defaultPrivateKeyPath() {
|
|
@@ -101,6 +101,7 @@ async function modernPassThrough() {
|
|
|
101
101
|
cwd: repoRoot,
|
|
102
102
|
env: {
|
|
103
103
|
...getDefaultEnvironment(),
|
|
104
|
+
...(process.env.DEADBYTE_STATE_ROOT?{DEADBYTE_STATE_ROOT:path.resolve(process.env.DEADBYTE_STATE_ROOT)}:{}),
|
|
104
105
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
105
106
|
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
106
107
|
DEADBYTE_TUNNEL_STDIO_PROBE_COMPAT: '1'
|
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, keys };
|
|
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();
|
|
@@ -6,7 +6,7 @@ import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
|
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
9
|
-
const oracleRoot='C:\\Users\\DEADBYTE\\.deadbyte-mcp\\evidence\\commander-biff-oracle';
|
|
9
|
+
const oracleRoot=process.env.DEADBYTE_BIFF_TEST_ORACLE||'C:\\Users\\DEADBYTE\\.deadbyte-mcp\\evidence\\commander-biff-oracle';
|
|
10
10
|
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
11
|
|
|
12
12
|
test('pinned reference rejects a real BIFF8 workbook and writes OOXML bytes under .xls',async()=>{
|
|
@@ -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
|
+
});
|
|
@@ -34,15 +34,16 @@ test('R44 detached finalizer accepts only exact signed-R43-parent feature gate e
|
|
|
34
34
|
assert.throws(()=>validateWindowsGateEvidence({...valid(),features:{...features(),skill_integrity:false}},manifest,parent),/skill_integrity/);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
test('R44 detached finalizer
|
|
37
|
+
test('R44 detached finalizer prioritizes native Windows modules and preserves custom paths',()=>{
|
|
38
38
|
const env=windowsPowerShellEnvironment({
|
|
39
39
|
SystemRoot:'C:\\Windows',USERPROFILE:'C:\\Users\\operator',ProgramFiles:'C:\\Program Files',
|
|
40
40
|
PSModulePath:'C:\\poisoned-runtime-modules'
|
|
41
41
|
});
|
|
42
42
|
assert.equal(env.PSModulePath,[
|
|
43
|
+
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules',
|
|
43
44
|
'C:\\Users\\operator\\Documents\\WindowsPowerShell\\Modules',
|
|
44
45
|
'C:\\Program Files\\WindowsPowerShell\\Modules',
|
|
45
|
-
'C:\\
|
|
46
|
+
'C:\\poisoned-runtime-modules'
|
|
46
47
|
].join(';'));
|
|
47
|
-
assert.
|
|
48
|
+
assert.equal(env.PSModulePath.split(';')[0],'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules');
|
|
48
49
|
});
|
|
@@ -4,9 +4,13 @@ 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, historicalR50, historicalR49, deferred] = await Promise.all([
|
|
7
|
+
test('R53 canonical Windows gate is successor-bound and preserves R52/R51/R50/R49 historical gates', async () => {
|
|
8
|
+
const [pkgText, gate, evidence, historicalR52, historicalEvidenceR52, historicalR51, historicalEvidenceR51, historicalR50, historicalR49, deferred] = await Promise.all([
|
|
9
9
|
read('package.json'),
|
|
10
|
+
read('scripts/gate-windows-r53.ps1'),
|
|
11
|
+
read('scripts/windows-gate-evidence-r53.mjs'),
|
|
12
|
+
read('scripts/gate-windows-r52.ps1'),
|
|
13
|
+
read('scripts/windows-gate-evidence-r52.mjs'),
|
|
10
14
|
read('scripts/gate-windows-r51.ps1'),
|
|
11
15
|
read('scripts/windows-gate-evidence-r51.mjs'),
|
|
12
16
|
read('scripts/gate-windows-r50.ps1'),
|
|
@@ -14,16 +18,22 @@ test('R51 canonical Windows gate is successor-bound and preserves R50/R49 histor
|
|
|
14
18
|
read('scripts/deferred-slot-operation-r46.mjs')
|
|
15
19
|
]);
|
|
16
20
|
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, /
|
|
20
|
-
assert.match(gate, /
|
|
21
|
+
assert.match(pkg.scripts['gate:windows'], /gate-windows-r53\.ps1$/);
|
|
22
|
+
assert.match(gate, /DEADBYTE 0\.15\.30 \/ R53 WINDOWS GATE: PASS/);
|
|
23
|
+
assert.match(gate, /r53-package-boundary/);
|
|
24
|
+
assert.match(gate, /r53-focused-contracts/);
|
|
21
25
|
assert.match(gate, /r49-remote-control-donors\.test\.mjs/);
|
|
22
26
|
assert.match(gate, /desktop-runtime-r40\.test\.mjs/);
|
|
23
27
|
assert.match(gate, /manifest-final-verify/);
|
|
24
|
-
assert.match(evidence, /deadbyte\.
|
|
25
|
-
assert.match(evidence, /pkg\.version === '0\.15\.
|
|
26
|
-
assert.match(evidence, /release\.train === '
|
|
28
|
+
assert.match(evidence, /deadbyte\.r53-windows-gate\.v1/);
|
|
29
|
+
assert.match(evidence, /pkg\.version === '0\.15\.30'/);
|
|
30
|
+
assert.match(evidence, /release\.train === 'R53'/);
|
|
31
|
+
assert.match(evidence, /release\.parent\.version === '0\.15\.29'/);
|
|
32
|
+
assert.match(evidence, /four_hour_soak_completed: false/);
|
|
33
|
+
assert.match(historicalR52, /DEADBYTE 0\.15\.29 \/ R52 WINDOWS GATE: PASS/);
|
|
34
|
+
assert.match(historicalEvidenceR52, /deadbyte\.r52-windows-gate\.v1/);
|
|
35
|
+
assert.match(historicalR51, /DEADBYTE 0\.15\.28 \/ R51 WINDOWS GATE: PASS/);
|
|
36
|
+
assert.match(historicalEvidenceR51, /deadbyte\.r51-windows-gate\.v1/);
|
|
27
37
|
assert.match(historicalR50, /DEADBYTE 0\.15\.27 \/ R50 WINDOWS GATE: PASS/);
|
|
28
38
|
assert.match(historicalR49, /DEADBYTE 0\.15\.26 \/ R49 WINDOWS GATE: PASS/);
|
|
29
39
|
assert.match(deferred, /const expectedVersion=packageIdentity\.version/);
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { readFile } from 'node:fs/promises';
|
|
4
5
|
|
|
5
6
|
const root=new URL('../',import.meta.url);
|
|
6
|
-
const read=rel=>readFile(new URL(rel,root)
|
|
7
|
-
const json=rel=>read(rel).then(JSON.parse);
|
|
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');
|
|
8
10
|
|
|
9
|
-
test('R51 release
|
|
10
|
-
const [
|
|
11
|
-
|
|
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')
|
|
12
16
|
]);
|
|
13
|
-
assert.equal(
|
|
14
|
-
assert.equal(
|
|
15
|
-
assert.equal(
|
|
16
|
-
|
|
17
|
-
|
|
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');
|
|
18
24
|
assert.equal(release.train,'R51');
|
|
19
|
-
assert.equal(provenance.version,
|
|
20
|
-
assert.equal(matrix.version,
|
|
25
|
+
assert.equal(provenance.version,'0.15.28');
|
|
26
|
+
assert.equal(matrix.version,'0.15.28');
|
|
21
27
|
assert.equal(matrix.train,'R51');
|
|
22
|
-
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
23
|
-
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r51:0.15.28');
|
|
24
|
-
assert.match(pkg.scripts['gate:windows'],/gate-windows-r51\.ps1/);
|
|
25
28
|
});
|
|
26
29
|
|
|
27
|
-
test('R51 lineage binds exact
|
|
28
|
-
const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
|
|
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')]);
|
|
29
32
|
const expected={
|
|
30
33
|
release_id:'v0.15.27-1b1dbe24b45ad06a',
|
|
31
34
|
manifest_sha256:'1b1dbe24b45ad06a62771d15f510662952f3d7c3e5aa08053c6c61511c523e47',
|
|
@@ -38,11 +41,10 @@ test('R51 lineage binds exact immutable published 0.15.27 parent',async()=>{
|
|
|
38
41
|
assert.equal(provenance.parent_release_id,expected.release_id);
|
|
39
42
|
assert.equal(provenance.parent_manifest_sha256,expected.manifest_sha256);
|
|
40
43
|
assert.equal(provenance.parent_generation_manifest_sha256,expected.generation_manifest_sha256);
|
|
41
|
-
assert.equal(provenance.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
test('R51
|
|
45
|
-
const release=await json('RELEASE.json');
|
|
46
|
+
test('R51 historical capability claims remain frozen',async()=>{
|
|
47
|
+
const release=await json('docs/R51-RELEASE.json');
|
|
46
48
|
for(const key of [
|
|
47
49
|
'runtime_package_auto_coherence',
|
|
48
50
|
'coding_command_singleflight',
|
|
@@ -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
|
+
});
|