deadbyte-mcp 0.15.1 → 0.15.3
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 +95 -64
- package/PROVENANCE.json +6 -5
- package/README.txt +36 -20
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +3 -3
- package/controller/README.TXT +2 -2
- package/controller/deadbyte-controller.ps1 +50 -2
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/package.json +88 -86
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/deferred-slot-operation-r46.mjs +126 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/final-closure-r46.mjs +98 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/full-regression-r46.mjs +28 -0
- package/scripts/gate-windows-r46.ps1 +58 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/release-parity-r46.mjs +187 -0
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +111 -0
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/capability-ledger.mjs +1 -0
- package/src/coding-mcp-tools.mjs +2 -2
- package/src/coding-output-contracts.mjs +18 -4
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +11 -2
- package/src/coding-task-runtime.mjs +41 -6
- package/src/compact-gateway-mcp-tools.mjs +7 -5
- package/src/deadbyte-cli.mjs +7 -3
- package/src/deadline-cancellation.mjs +83 -0
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +66 -33
- package/src/mcp-server.mjs +10 -0
- package/src/mcp-smoke-client.mjs +4 -1
- package/src/observation-runtime.mjs +25 -4
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +29 -8
- package/src/process-output-contracts.mjs +43 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +152 -23
- package/src/process-smoke-client.mjs +67 -22
- package/src/release-generation.mjs +22 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/stdio-console-guard.mjs +24 -0
- package/src/version.mjs +1 -1
- package/src/workload-substrate.mjs +87 -0
- package/test/cli-entrypoint.test.mjs +3 -3
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/compact-gateway-r36.test.mjs +28 -0
- package/test/core.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +92 -0
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/observation-runtime.test.mjs +29 -2
- package/test/process-mcp-tools.test.mjs +27 -6
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +129 -2
- package/test/process-supervision.test.mjs +4 -2
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-generation.test.mjs +25 -11
- package/test/release-version.test.mjs +99 -44
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-self-update.test.mjs +118 -1
- package/test/workload-substrate.test.mjs +22 -0
|
@@ -5,54 +5,113 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
|
|
6
6
|
const root=new URL('../',import.meta.url);
|
|
7
7
|
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
8
|
-
const PARENT_RELEASE='v0.15.
|
|
9
|
-
const PARENT_MANIFEST='
|
|
10
|
-
const PARENT_GENERATION='
|
|
11
|
-
const PARENT_CLOSURE='533b7e0ff9ded04086dd90ed34e3b87bc427acc3c55c25bb9f4ec66cb448e667';
|
|
8
|
+
const PARENT_RELEASE='v0.15.1-d55f00b5144dc31a';
|
|
9
|
+
const PARENT_MANIFEST='d55f00b5144dc31a7fe39e4dc60d640a94780da6aaeca0918e87636b816d7854';
|
|
10
|
+
const PARENT_GENERATION='00cde7bf540cc2804d679a39040d5b07193c5f8a0d49cac3b882402f8f4e6ad8';
|
|
12
11
|
|
|
13
|
-
test('
|
|
12
|
+
test('R46 current authority agrees on V0.15.2 and preserves exact R45 parent identity',async()=>{
|
|
14
13
|
const pkg=JSON.parse(await text('package.json'));
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
text('
|
|
18
|
-
text('
|
|
19
|
-
text('scripts/
|
|
20
|
-
text('
|
|
14
|
+
const release=JSON.parse(await text('RELEASE.json'));
|
|
15
|
+
const [version,controller,gate,readme,architecture,controllerReadme,deferred,parity,closure,closureVerify,windowsGate,processPolicy]=await Promise.all([
|
|
16
|
+
text('src/version.mjs'),
|
|
17
|
+
text('controller/deadbyte-controller.ps1'),
|
|
18
|
+
text('scripts/gate-windows-r46.ps1'),
|
|
19
|
+
text('README.txt'),
|
|
20
|
+
text('docs/ARCHITECTURE.md'),
|
|
21
|
+
text('controller/README.TXT'),
|
|
22
|
+
text('scripts/deferred-slot-operation-r46.mjs'),
|
|
23
|
+
text('scripts/release-parity-r46.mjs'),
|
|
24
|
+
text('scripts/final-closure-r46.mjs'),
|
|
25
|
+
text('scripts/final-closure-verify-r46.mjs'),
|
|
26
|
+
text('scripts/windows-gate-evidence-r46.mjs'),
|
|
27
|
+
text('controller/deadbyte-process-policy.json')
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
assert.equal(pkg.version,'0.15.2');
|
|
31
|
+
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r46.ps1');
|
|
32
|
+
assert.equal(pkg.scripts['self-update:deferred'],'node scripts/deferred-slot-operation-r46.mjs');
|
|
33
|
+
assert.equal(pkg.scripts['release:parity'],'node scripts/release-parity-r46.mjs');
|
|
34
|
+
assert.equal(pkg.scripts['release:final:write'],'node scripts/final-closure-r46.mjs');
|
|
35
|
+
assert.equal(pkg.scripts['release:final:verify'],'node scripts/final-closure-verify-r46.mjs');
|
|
36
|
+
assert.equal(pkg.scripts['r46:fileless:test'],"node --test --test-concurrency=1 test/process-policy.test.mjs test/process-policy-refresh.test.mjs test/release-generation.test.mjs test/process-runtime.test.mjs test/process-mcp-tools.test.mjs test/compact-gateway-r36.test.mjs test/capability-ledger-r36.test.mjs test/remote-device-r44.test.mjs test/observation-runtime.test.mjs test/observation-mcp-tools.test.mjs");
|
|
37
|
+
assert.equal(pkg.scripts['r46:full:test'],'node scripts/full-regression-r46.mjs');
|
|
38
|
+
|
|
39
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.2'/);
|
|
40
|
+
assert.match(controller,/CONTROLLER V0\.15\.2 \/ R46/);
|
|
41
|
+
assert.match(gate,/DEADBYTE V0\.15\.2 \/ R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS/);
|
|
42
|
+
assert.match(readme,/^DEADBYTE MCP V0\.15\.2 \/ R46 FILELESS POWERSHELL PRODUCT BASELINE$/m);
|
|
43
|
+
assert.match(readme,/powershell\.exe -Command <source>/);
|
|
44
|
+
assert.match(architecture,/^# DEADBYTE MCP V0\.15\.2 \/ R46 ARCHITECTURE$/m);
|
|
45
|
+
assert.match(controllerReadme,/^DEADBYTE MCP V0\.15\.2 \/ R46 CONTROLLER$/m);
|
|
46
|
+
assert.match(deferred,/--expected-version','0\.15\.2'/);
|
|
47
|
+
assert.match(parity,/deadbyte\.r46-parity\.v1/);
|
|
48
|
+
assert.match(parity,/DB_R46_FILELESS_PARITY/);
|
|
49
|
+
assert.match(parity,/target_repin_verified:true/);
|
|
50
|
+
assert.match(parity,/DB_R46_REPL_VALUE=42/);
|
|
51
|
+
assert.match(parity,/raw_input_not_journaled:true/);
|
|
52
|
+
assert.match(parity,/eviction_verified:true/);
|
|
53
|
+
assert.match(windowsGate,/full_tool_count:84/);
|
|
54
|
+
assert.match(windowsGate,/remote_reachability_ttl:true/);
|
|
55
|
+
assert.match(windowsGate,/observation_search_terminal_ttl:true/);
|
|
56
|
+
assert.match(windowsGate,/observation_search_hard_cap:128/);
|
|
57
|
+
assert.match(closure,/deadbyte\.r46-final-closure\.v1/);
|
|
58
|
+
assert.match(closure,/DEADBYTE-R46-FINAL-CLOSURE-V1/);
|
|
59
|
+
assert.match(closureVerify,/DEADBYTE-R46-FINAL-CLOSURE-V1/);
|
|
60
|
+
assert.match(windowsGate,/deadbyte\.r46-windows-gate\.v1/);
|
|
61
|
+
|
|
62
|
+
assert.equal(release.version,'0.15.2');
|
|
63
|
+
assert.equal(release.train,'R46');
|
|
64
|
+
assert.equal(release.label,'FILELESS POWERSHELL PRODUCT BASELINE');
|
|
65
|
+
assert.equal(release.parent.release_id,PARENT_RELEASE);
|
|
66
|
+
assert.equal(release.parent.manifest_sha256,PARENT_MANIFEST);
|
|
67
|
+
assert.equal(release.parent.generation_manifest_sha256,PARENT_GENERATION);
|
|
68
|
+
assert.deepEqual(release.capabilities.process_profiles,['ping_hold','powershell','powershell_repl']);
|
|
69
|
+
assert.equal(release.capabilities.full_tool_count,83);
|
|
70
|
+
assert.equal(release.capabilities.compact_tool_count,43);
|
|
71
|
+
assert.equal(release.capabilities.powershell_persistent_repl,true);
|
|
72
|
+
assert.equal(release.capabilities.process_raw_input_profile_opt_in,true);
|
|
73
|
+
assert.equal(release.capabilities.powershell_fileless,true);
|
|
74
|
+
assert.equal(release.capabilities.powershell_source_max_arg_bytes,8192);
|
|
75
|
+
|
|
76
|
+
const policy=JSON.parse(processPolicy);
|
|
77
|
+
assert.ok(policy.profiles.ping_hold);
|
|
78
|
+
assert.ok(policy.profiles.powershell);
|
|
79
|
+
assert.ok(policy.profiles.powershell_repl);
|
|
80
|
+
assert.equal(policy.profiles.powershell_repl.allow_raw_input,true);
|
|
81
|
+
assert.deepEqual(policy.profiles.powershell_repl.prefix_args,['-NoLogo','-NoProfile','-NonInteractive']);
|
|
82
|
+
assert.deepEqual(policy.profiles.powershell.prefix_args,['-NoLogo','-NoProfile','-NonInteractive','-Command']);
|
|
83
|
+
assert.equal(policy.profiles.powershell.max_args,1);
|
|
84
|
+
assert.equal(policy.profiles.powershell.max_arg_bytes,8192);
|
|
85
|
+
assert.equal(policy.profiles.powershell.transport,'pipe');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('R46 leaves sealed R45 release artifacts historical and unchanged in identity',async()=>{
|
|
89
|
+
const [gate,deferred,parity,closure,closureVerify,finalizer,parent]=await Promise.all([
|
|
90
|
+
text('scripts/gate-windows-r45.ps1'),
|
|
91
|
+
text('scripts/deferred-slot-operation-r45.mjs'),
|
|
92
|
+
text('scripts/release-parity-r45.mjs'),
|
|
93
|
+
text('scripts/final-closure-r45.mjs'),
|
|
94
|
+
text('scripts/final-closure-verify-r45.mjs'),
|
|
95
|
+
text('scripts/finalize-r45.mjs'),
|
|
96
|
+
text('R45-PARENT.json')
|
|
21
97
|
]);
|
|
22
|
-
assert.equal(pkg.version,'0.15.1');
|
|
23
|
-
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r45.ps1');
|
|
24
|
-
assert.equal(pkg.scripts['r45:parent:verify'],'node scripts/verify-r45-parent.mjs');
|
|
25
|
-
assert.equal(pkg.scripts['r45:full:test'],'node scripts/full-regression-r45.mjs');
|
|
26
|
-
assert.equal(pkg.scripts['self-update:deferred'],'node scripts/deferred-slot-operation-r45.mjs');
|
|
27
|
-
assert.equal(pkg.scripts['release:parity'],'node scripts/release-parity-r45.mjs');
|
|
28
|
-
assert.equal(pkg.scripts['release:final:write'],'node scripts/final-closure-r45.mjs');
|
|
29
|
-
assert.equal(pkg.scripts['release:final:verify'],'node scripts/final-closure-verify-r45.mjs');
|
|
30
|
-
assert.equal(pkg.scripts['finalize:r45'],'node scripts/finalize-r45.mjs');
|
|
31
|
-
assert.match(version,/DEADBYTE_VERSION = '0\.15\.1'/);
|
|
32
|
-
assert.match(controller,/CONTROLLER V0\.15\.1 \/ R45/);
|
|
33
98
|
assert.match(gate,/DEADBYTE V0\.15\.1 \/ R45 MACHINE FREEDOM WINDOWS BATCH GATE: PASS/);
|
|
34
|
-
assert.match(readme,/^DEADBYTE MCP V0\.15\.1 \/ R45 MACHINE FREEDOM FINAL BASELINE$/m);
|
|
35
|
-
assert.match(architecture,/^# DEADBYTE MCP V0\.15\.1 \/ R45 ARCHITECTURE$/m);
|
|
36
|
-
assert.match(controllerReadme,/^DEADBYTE MCP V0\.15\.1 \/ R45 CONTROLLER$/m);
|
|
37
99
|
assert.match(deferred,/--expected-version','0\.15\.1'/);
|
|
38
|
-
assert.match(parity,/
|
|
39
|
-
assert.match(
|
|
40
|
-
assert.match(
|
|
41
|
-
assert.match(
|
|
42
|
-
assert.match(
|
|
43
|
-
assert.match(windowsGate,/deadbyte\.r45-windows-gate\.v1/);
|
|
100
|
+
assert.match(parity,/deadbyte\.r45-parity\.v1/);
|
|
101
|
+
assert.match(closure,/version:'0\.15\.1'/);
|
|
102
|
+
assert.match(closure,/DEADBYTE-R45-FINAL-CLOSURE-V1/);
|
|
103
|
+
assert.match(closureVerify,/R45_FULL_TOOL_COUNT=82/);
|
|
104
|
+
assert.match(finalizer,/EXPECTED_VERSION='0\.15\.1'/);
|
|
44
105
|
const parentRecord=JSON.parse(parent);
|
|
45
|
-
assert.equal(parentRecord.parent_release_id,
|
|
46
|
-
assert.equal(parentRecord.parent_manifest_sha256,PARENT_MANIFEST);
|
|
47
|
-
assert.equal(parentRecord.parent_generation_manifest_sha256,PARENT_GENERATION);
|
|
48
|
-
assert.equal(parentRecord.parent_final_closure_sha256,PARENT_CLOSURE);
|
|
49
|
-
assert.match(r44Gate,/DEADBYTE V0\.15\.0 \/ R44 REMOTE AGENT SKILL WINDOWS BATCH GATE: PASS/);
|
|
50
|
-
assert.match(r44Parity,/deadbyte\.r44-parity\.v1/);
|
|
51
|
-
assert.match(r44Closure,/version:'0\.15\.0'/);
|
|
106
|
+
assert.equal(parentRecord.parent_release_id,'v0.15.0-c15fa41719be61b3');
|
|
52
107
|
});
|
|
53
108
|
|
|
54
|
-
test('
|
|
55
|
-
const [cli,controller]=await Promise.all([
|
|
109
|
+
test('R46 keeps ONLINE reuse, explicit Desktop authority, and semantic config pin',async()=>{
|
|
110
|
+
const [cli,controller,semantic]=await Promise.all([
|
|
111
|
+
text('src/deadbyte-cli.mjs'),
|
|
112
|
+
text('controller/deadbyte-controller.ps1'),
|
|
113
|
+
text('controller/deadbyte-semantic-lsp-config.json')
|
|
114
|
+
]);
|
|
56
115
|
assert.match(cli,/controllerRuntimeReady/);
|
|
57
116
|
assert.match(cli,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
|
|
58
117
|
const start=controller.indexOf('function Recover-AutonomousSupervisor'),end=controller.indexOf('function Show-State',start);
|
|
@@ -62,9 +121,5 @@ test('R45 keeps ONLINE reuse and explicit Desktop authority',async()=>{
|
|
|
62
121
|
assert.match(block,/\$hostStatus\s*=\s*Get-HostArmStatus/);
|
|
63
122
|
assert.match(controller,/armdesktop/);
|
|
64
123
|
assert.match(controller,/disarmdesktop/);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('semantic config remains byte-pinned across R45 machine-freedom work',async()=>{
|
|
68
|
-
const semantic=await text('controller/deadbyte-semantic-lsp-config.json');
|
|
69
124
|
assert.equal(createHash('sha256').update(Buffer.from(semantic,'utf8')).digest('hex'),'d274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
|
|
70
125
|
});
|
|
@@ -102,3 +102,78 @@ test('R44 remote state corruption fails closed',async()=>{
|
|
|
102
102
|
await assert.rejects(createRemoteDevicePlaneR44({stateDir:root,relay:new FakeRelay(),authorizeCapability:async()=>true}),/state JSON invalid/);
|
|
103
103
|
}finally{await rm(root,{recursive:true,force:true});}
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
test('R46 remote readiness probe retries only the idempotent probe and remains bounded by timeout',async()=>{
|
|
108
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r46-remote-probe-timeout-'));
|
|
109
|
+
const key=deviceKey();
|
|
110
|
+
let attempts=0;
|
|
111
|
+
const relay={
|
|
112
|
+
requests:[],
|
|
113
|
+
async probe(value){
|
|
114
|
+
attempts+=1;
|
|
115
|
+
if(attempts===1) return await new Promise(()=>{});
|
|
116
|
+
return {device_id:value.device_id,epoch:value.epoch,nonce:value.nonce,ready:true};
|
|
117
|
+
},
|
|
118
|
+
async request(value){this.requests.push(value);return {ack:'accepted',request_id:value.request_id,terminal:{status:'completed',result_sha256:'a'.repeat(64)}};}
|
|
119
|
+
};
|
|
120
|
+
try{
|
|
121
|
+
const plane=await createRemoteDevicePlaneR44({
|
|
122
|
+
stateDir:root,relay,authorizeCapability:async()=>true,
|
|
123
|
+
relayTimeoutMs:100,probeRetries:1,reachableTtlMs:5000
|
|
124
|
+
});
|
|
125
|
+
await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
|
|
126
|
+
await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'retry-c'});
|
|
127
|
+
const ready=await plane.probe({deviceId:key.deviceId,epoch:1});
|
|
128
|
+
assert.equal(attempts,2);
|
|
129
|
+
assert.equal(ready.reachable,true);
|
|
130
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('R46 remote reachability expires after the bounded probe freshness window',async()=>{
|
|
134
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r46-remote-stale-'));
|
|
135
|
+
const relay=new FakeRelay(),key=deviceKey();
|
|
136
|
+
try{
|
|
137
|
+
const plane=await createRemoteDevicePlaneR44({
|
|
138
|
+
stateDir:root,relay,authorizeCapability:async()=>true,
|
|
139
|
+
relayTimeoutMs:1000,probeRetries:0,reachableTtlMs:1000
|
|
140
|
+
});
|
|
141
|
+
await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
|
|
142
|
+
await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'stale-c'});
|
|
143
|
+
await plane.probe({deviceId:key.deviceId,epoch:1});
|
|
144
|
+
assert.equal((await plane.status({deviceId:key.deviceId})).reachable,true);
|
|
145
|
+
await new Promise(resolve=>setTimeout(resolve,1100));
|
|
146
|
+
assert.equal((await plane.status({deviceId:key.deviceId})).reachable,false);
|
|
147
|
+
await assert.rejects(
|
|
148
|
+
plane.dispatch({deviceId:key.deviceId,epoch:1,capabilityId:'machine.status',payload:{},requestId:'8'.repeat(64)}),
|
|
149
|
+
/not reachable|stale/i
|
|
150
|
+
);
|
|
151
|
+
assert.equal(relay.requests.length,0);
|
|
152
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('R46 remote dispatch timeout is not retried and downgrades reachability',async()=>{
|
|
156
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r46-remote-dispatch-timeout-'));
|
|
157
|
+
const key=deviceKey();
|
|
158
|
+
let requestCount=0;
|
|
159
|
+
const relay={
|
|
160
|
+
async probe(value){return {device_id:value.device_id,epoch:value.epoch,nonce:value.nonce,ready:true};},
|
|
161
|
+
async request(){requestCount+=1;return await new Promise(()=>{});}
|
|
162
|
+
};
|
|
163
|
+
try{
|
|
164
|
+
const plane=await createRemoteDevicePlaneR44({
|
|
165
|
+
stateDir:root,relay,authorizeCapability:async()=>true,
|
|
166
|
+
relayTimeoutMs:100,probeRetries:0,reachableTtlMs:5000
|
|
167
|
+
});
|
|
168
|
+
await plane.pair({publicKeySpkiBase64:key.spki,displayName:'lab'});
|
|
169
|
+
await plane.reportOnline({deviceId:key.deviceId,epoch:1,connectionId:'dispatch-c'});
|
|
170
|
+
await plane.probe({deviceId:key.deviceId,epoch:1});
|
|
171
|
+
const result=await plane.dispatch({
|
|
172
|
+
deviceId:key.deviceId,epoch:1,capabilityId:'coding.file_read',payload:{path:'README.md'},requestId:'9'.repeat(64)
|
|
173
|
+
});
|
|
174
|
+
assert.equal(result.state,'failed');
|
|
175
|
+
assert.equal(result.error_code,'transport_error');
|
|
176
|
+
assert.equal(requestCount,1,'side-effecting dispatch must never auto-retry');
|
|
177
|
+
assert.equal((await plane.status({deviceId:key.deviceId})).reachable,false);
|
|
178
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
179
|
+
});
|
|
@@ -32,10 +32,10 @@ test('R29 remote gate accepts machine plane as an exact optional catalog dimensi
|
|
|
32
32
|
assert.match(source, /if \(machine\) items\.push\(\.\.\.machineTools\)/);
|
|
33
33
|
assert.match(source, /machine_runtime: machineRuntime/);
|
|
34
34
|
});
|
|
35
|
-
test('
|
|
35
|
+
test('R46 remote gate accepts seven-tool process supervision as one exact optional catalog dimension', async () => {
|
|
36
36
|
const source = await readFile(sourcePath, 'utf8');
|
|
37
37
|
assert.match(source, /const processTools = \[/);
|
|
38
|
-
for (const name of ['process_session_start','process_session_read','process_session_status','process_session_list','process_session_terminate','process_session_action']) {
|
|
38
|
+
for (const name of ['process_session_start','process_session_read','process_session_status','process_session_list','process_session_terminate','process_session_action','process_session_write']) {
|
|
39
39
|
assert.match(source, new RegExp(`'${name}'`));
|
|
40
40
|
}
|
|
41
41
|
assert.match(source, /for \(const processPlane of \[false, true\]\)/);
|
|
@@ -4,6 +4,7 @@ import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
7
8
|
import {
|
|
8
9
|
assertDevelopmentRootSafe,
|
|
9
10
|
legacyContext,
|
|
@@ -213,11 +214,105 @@ test('R28 updater requires a stabilization window and public MCP smoke before co
|
|
|
213
214
|
assert.match(source,/McpUrlFileSha256\s*=\s*stableMcpUrl\.sha256/);
|
|
214
215
|
assert.match(source,/mcp_url_restored/);
|
|
215
216
|
const stableIndex=source.indexOf('stabilizeRuntime');
|
|
216
|
-
const receiptIndex=source.indexOf('const
|
|
217
|
+
const receiptIndex=source.indexOf('const bodyReceipt={');
|
|
217
218
|
assert.ok(stableIndex>=0&&receiptIndex>stableIndex,'stability/public gate must happen before update receipt');
|
|
218
219
|
});
|
|
219
220
|
|
|
220
221
|
|
|
222
|
+
|
|
223
|
+
test('R47 runtime updater arms a detached supervisor before any destructive stop', async () => {
|
|
224
|
+
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
225
|
+
const start=source.indexOf('export async function performSlotUpdate');
|
|
226
|
+
const end=source.indexOf('export async function performSlotRollback',start);
|
|
227
|
+
assert.ok(start>=0&&end>start);
|
|
228
|
+
const perform=source.slice(start,end);
|
|
229
|
+
assert.match(source,/const HANDOFF_SCHEMA='deadbyte\.r27-slot-update-handoff\.v1'/);
|
|
230
|
+
assert.match(source,/shutdown_handoff_armed/);
|
|
231
|
+
assert.match(source,/detached:true/);
|
|
232
|
+
assert.match(source,/proc\.unref\(\)/);
|
|
233
|
+
const handoffIndex=perform.indexOf('const handoff=await writeHandoff');
|
|
234
|
+
const spawnIndex=perform.indexOf('await spawnDetachedSupervisor',handoffIndex);
|
|
235
|
+
const returnIndex=perform.indexOf('return {ok:true,handed_off:true',spawnIndex);
|
|
236
|
+
assert.ok(handoffIndex>=0&&spawnIndex>handoffIndex&&returnIndex>spawnIndex);
|
|
237
|
+
assert.equal(perform.indexOf("controllerCaptured(control.ctx,'stop'"),-1);
|
|
238
|
+
assert.equal(perform.indexOf('await revokeAuthority(control.ctx'),-1);
|
|
239
|
+
assert.equal(perform.indexOf("writeState(statePath,journal,'supervisor_spawned'"),-1);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('R47 supervisor resume parser is exact and recovery scans only durable update state', () => {
|
|
243
|
+
assert.deepEqual(runtimeUpdate.parseResumeHandoffArgs(['--resume-handoff','C:/state/evidence/updates/u/handoff.json']),{
|
|
244
|
+
handoff:path.resolve('C:/state/evidence/updates/u/handoff.json')
|
|
245
|
+
});
|
|
246
|
+
assert.throws(()=>runtimeUpdate.parseResumeHandoffArgs([]),/resume-handoff is required/i);
|
|
247
|
+
assert.throws(()=>runtimeUpdate.parseResumeHandoffArgs(['--resume-handoff','x','--bad']),/unknown supervisor argument/i);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('R47 interrupted transition has explicit durable recovery phases', async () => {
|
|
251
|
+
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
252
|
+
for(const phase of ['shutdown_handoff_armed','supervisor_spawned','authority_revoked','old_runtime_stopped','workspace_ready','launcher_installed','pointer_committed','fresh_runtime_verified','authority_verified','completed']){
|
|
253
|
+
assert.match(source,new RegExp(phase));
|
|
254
|
+
}
|
|
255
|
+
assert.match(source,/recoverInterruptedSlotUpdates/);
|
|
256
|
+
assert.match(source,/phaseRank\(phase\)<phaseRank\('authority_revoked'\)/);
|
|
257
|
+
assert.match(source,/recovery observed predecessor already OFFLINE/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
test('R47 MCP core startup invokes durable interrupted-update recovery before serving', async () => {
|
|
262
|
+
const source=await readFile(new URL('../src/mcp-server.mjs',import.meta.url),'utf8');
|
|
263
|
+
assert.match(source,/import \{ recoverInterruptedSlotUpdates \} from '\.\/runtime-update\.mjs';/);
|
|
264
|
+
assert.match(source,/await recoverInterruptedSlotUpdates\(\{ stateRoot: runtimeStateRoot \}\)/);
|
|
265
|
+
const recoveryIndex=source.indexOf('await recoverInterruptedSlotUpdates');
|
|
266
|
+
const serveIndex=source.indexOf('serveStdio(');
|
|
267
|
+
assert.ok(recoveryIndex>=0&&serveIndex>recoveryIndex,'startup recovery must run before MCP serving starts');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
test('R47 Windows detached supervisor survives termination of its parent process', { skip: process.platform !== 'win32' }, async () => {
|
|
272
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r47-detached-survival-'));
|
|
273
|
+
const parentPath=path.join(root,'parent.mjs');
|
|
274
|
+
const survivorPath=path.join(root,'survivor.mjs');
|
|
275
|
+
const pidPath=path.join(root,'survivor.pid');
|
|
276
|
+
await writeFile(survivorPath,[
|
|
277
|
+
"import { writeFile } from 'node:fs/promises';",
|
|
278
|
+
"const pidPath=process.argv[2];",
|
|
279
|
+
"await writeFile(pidPath,String(process.pid),'ascii');",
|
|
280
|
+
"setInterval(()=>{},1000);"
|
|
281
|
+
].join('\n'));
|
|
282
|
+
await writeFile(parentPath,[
|
|
283
|
+
"import { spawn } from 'node:child_process';",
|
|
284
|
+
"const survivor=spawn(process.execPath,[process.argv[2],process.argv[3]],{detached:true,windowsHide:true,stdio:'ignore'});",
|
|
285
|
+
"survivor.unref();",
|
|
286
|
+
"setInterval(()=>{},1000);"
|
|
287
|
+
].join('\n'));
|
|
288
|
+
const parent=spawn(process.execPath,[parentPath,survivorPath,pidPath],{windowsHide:true,stdio:'ignore'});
|
|
289
|
+
try{
|
|
290
|
+
const deadline=Date.now()+5000;
|
|
291
|
+
let survivorPid=0;
|
|
292
|
+
while(Date.now()<deadline){
|
|
293
|
+
try{
|
|
294
|
+
survivorPid=Number((await readFile(pidPath,'ascii')).trim());
|
|
295
|
+
if(Number.isInteger(survivorPid)&&survivorPid>0) break;
|
|
296
|
+
}catch{}
|
|
297
|
+
await new Promise(resolve=>setTimeout(resolve,50));
|
|
298
|
+
}
|
|
299
|
+
assert.ok(survivorPid>0,'detached survivor did not report PID');
|
|
300
|
+
parent.kill('SIGKILL');
|
|
301
|
+
await new Promise((resolve,reject)=>{
|
|
302
|
+
const timer=setTimeout(()=>reject(new Error('parent did not terminate after SIGKILL')),5000);
|
|
303
|
+
parent.once('exit',()=>{clearTimeout(timer);resolve();});
|
|
304
|
+
parent.once('error',error=>{clearTimeout(timer);reject(error);});
|
|
305
|
+
});
|
|
306
|
+
await new Promise(resolve=>setTimeout(resolve,350));
|
|
307
|
+
assert.doesNotThrow(()=>process.kill(survivorPid,0),'detached supervisor was terminated with its parent');
|
|
308
|
+
} finally {
|
|
309
|
+
const textPid=(await readFile(pidPath,'ascii').catch(()=>'' )).trim();
|
|
310
|
+
const cleanupPid=Number(textPid);
|
|
311
|
+
if(Number.isInteger(cleanupPid)&&cleanupPid>0){try{process.kill(cleanupPid,'SIGKILL');}catch{}}
|
|
312
|
+
try{process.kill(parent.pid,'SIGKILL');}catch{}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
221
316
|
test('R29 slot-update CLI requires explicit hash-bound host repins and offline recovery opt-in', () => {
|
|
222
317
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.8.5','--expected-manifest',hex('a')];
|
|
223
318
|
const parsed=parseUpdateArgs([...base,'--host-command-pin',`powershell_script=${hex('b')}`,'--host-command-pin',`cmd_script=${hex('c')}`,'--recover-offline-predecessor']);
|
|
@@ -286,6 +381,15 @@ test('R31 slot-update CLI binds process-policy bootstrap and machine-FS enable e
|
|
|
286
381
|
assert.equal(parsed.enableMachineFs,true);
|
|
287
382
|
});
|
|
288
383
|
|
|
384
|
+
test('R46 slot-update CLI binds coding policy bootstrap as one exact pair', () => {
|
|
385
|
+
const base=['--stage','stage','--development-root','dev','--expected-version','0.15.2','--expected-manifest',hex('a')];
|
|
386
|
+
assert.throws(()=>parseUpdateArgs([...base,'--coding-policy','C:/candidate/coding.json']),/coding policy.*pair/i);
|
|
387
|
+
assert.throws(()=>parseUpdateArgs([...base,'--coding-policy-sha256',hex('b')]),/coding policy.*pair/i);
|
|
388
|
+
const parsed=parseUpdateArgs([...base,'--coding-policy','C:/candidate/coding.json','--coding-policy-sha256',hex('b')]);
|
|
389
|
+
assert.equal(parsed.codingPolicyBootstrap.path,path.resolve('C:/candidate/coding.json'));
|
|
390
|
+
assert.equal(parsed.codingPolicyBootstrap.sha256,hex('b'));
|
|
391
|
+
});
|
|
392
|
+
|
|
289
393
|
test('R40 slot-update CLI binds Desktop policy as one exact pair and enables runtime only explicitly', () => {
|
|
290
394
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.12.0','--expected-manifest',hex('a')];
|
|
291
395
|
assert.throws(()=>parseUpdateArgs([...base,'--desktop-policy','C:/candidate/desktop.json']),/desktop policy.*pair/i);
|
|
@@ -299,6 +403,19 @@ test('R40 slot-update CLI binds Desktop policy as one exact pair and enables run
|
|
|
299
403
|
assert.throws(()=>parseUpdateArgs([...base,'--enable-desktop','--enable-desktop']),/duplicate self-update argument/i);
|
|
300
404
|
});
|
|
301
405
|
|
|
406
|
+
test('R46 coding policy bootstrap accepts only exact pinned enabled policy bytes',async()=>{
|
|
407
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r46-coding-bootstrap-'));
|
|
408
|
+
const file=path.join(root,'coding.json');
|
|
409
|
+
const policy={schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:'C:/old/grant.json',evidence_dir:'C:/old/evidence',roots:{core:{path:'C:/core',read:true,write:true,exec:true}},commands:{powershell_script:{executable:'C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',executable_sha256:hex('1')}}};
|
|
410
|
+
const bytes=Buffer.from(JSON.stringify(policy)+'\n','utf8');await writeFile(file,bytes);
|
|
411
|
+
assert.equal(typeof runtimeUpdate.prepareCodingPolicyBootstrap,'function');
|
|
412
|
+
const accepted=await runtimeUpdate.prepareCodingPolicyBootstrap({path:file,sha256:sha(bytes)});
|
|
413
|
+
assert.equal(accepted.sha256,sha(bytes));assert.deepEqual(accepted.policy,policy);
|
|
414
|
+
await assert.rejects(runtimeUpdate.prepareCodingPolicyBootstrap({path:file,sha256:hex('f')}),/coding policy.*SHA-256 mismatch/i);
|
|
415
|
+
const bad=path.join(root,'bad.json');await writeFile(bad,JSON.stringify({...policy,enabled:false}));
|
|
416
|
+
assert.rejects(runtimeUpdate.prepareCodingPolicyBootstrap({path:bad,sha256:sha(await readFile(bad))}),/coding policy schema/i);
|
|
417
|
+
});
|
|
418
|
+
|
|
302
419
|
test('R40 Desktop policy bootstrap accepts only exact pinned enabled policy bytes',async()=>{
|
|
303
420
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-bootstrap-'));
|
|
304
421
|
const file=path.join(root,'desktop.json');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createSequenceStream, assertSequenceStreamSnapshot } from '../src/workload-substrate.mjs';
|
|
3
|
+
|
|
4
|
+
const stream=createSequenceStream({streamId:'test',maxItems:2,maxBytes:1_000_000});
|
|
5
|
+
const a=stream.append({text:'a'},{producer:'p',channel:'stdout'});
|
|
6
|
+
const snap=stream.snapshot();
|
|
7
|
+
const b=stream.append({text:'b'},{producer:'p',channel:'stdout'});
|
|
8
|
+
assert.equal(stream.delta(snap).items[0].seq,b.seq);
|
|
9
|
+
stream.append({text:'c'},{producer:'p',channel:'stderr'});
|
|
10
|
+
const coverage=stream.coverage();
|
|
11
|
+
assert.equal(stream.snapshot().next_seq,4);
|
|
12
|
+
assert.equal(coverage.logical_item_count,3);
|
|
13
|
+
assert.equal(coverage.retained_item_count,2);
|
|
14
|
+
assert.equal(coverage.truncated,true);
|
|
15
|
+
assert.equal(coverage.complete,false);
|
|
16
|
+
assert.equal(stream.read(1,10).missing,true);
|
|
17
|
+
assert.equal(stream.read(2,10).items.length,2);
|
|
18
|
+
assert.equal(coverage.missing_ranges[0].from,1);
|
|
19
|
+
assert.equal(assertSequenceStreamSnapshot(snap).stream_id,'test');
|
|
20
|
+
stream.close();
|
|
21
|
+
assert.throws(()=>stream.append({text:'d'}),/not open/);
|
|
22
|
+
console.log(JSON.stringify({status:'pass',stream_id:stream.streamId,logical_last_seq:coverage.logical_last_seq,retained_first_seq:coverage.retained_first_seq,truncated:coverage.truncated}));
|