deadbyte-mcp 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +134 -92
- package/README.txt +19 -15
- package/bench/fixtures/corpus.json +32 -13
- package/bench/fixtures/r36/case-map.json +8 -0
- package/bin/WORKER-REGISTRY.txt +3 -3
- package/bin/appcontainer-stage.exe +0 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.exe +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.exe +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.exe +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.exe +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.exe +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.exe +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.exe +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.exe +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.exe +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.exe +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.exe +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +8 -6
- package/controller/deadbyte-controller.ps1 +48 -2
- package/controller/deadbyte-process-policy.json +5 -5
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
- package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
- package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
- package/package.json +1 -1
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/deferred-slot-operation.mjs +53 -15
- package/scripts/final-closure-r36.mjs +113 -0
- package/scripts/final-closure-verify-r36.mjs +208 -0
- package/scripts/final-closure-verify.mjs +88 -39
- package/scripts/final-closure.mjs +63 -56
- package/scripts/gate-windows-r36.ps1 +42 -0
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-r36.mjs +200 -0
- package/scripts/release-parity-tests.ps1 +9 -0
- package/scripts/release-parity.mjs +149 -139
- package/scripts/windows-gate-evidence-r36.mjs +52 -0
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/agent-memory-plane.mjs +98 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-mcp-tools.mjs +5 -1
- package/src/autonomous-output-contracts.mjs +9 -6
- package/src/autonomous-planner-contracts.mjs +36 -1
- package/src/autonomous-planner.mjs +25 -3
- package/src/autonomous-runtime.mjs +337 -43
- package/src/autonomous-timeout-recovery.mjs +27 -0
- package/src/capability-benchmark-runner.mjs +86 -1
- package/src/capability-benchmark.mjs +45 -0
- package/src/capability-ledger.mjs +158 -0
- package/src/coding-mcp-tools.mjs +31 -17
- package/src/coding-output-contracts.mjs +48 -10
- package/src/coding-plane.mjs +45 -9
- package/src/coding-search.mjs +130 -47
- package/src/compact-gateway-mcp-tools.mjs +128 -0
- package/src/compact-gateway-output-contracts.mjs +16 -0
- package/src/deadbyte-cli.mjs +62 -78
- package/src/effect-ledger.mjs +47 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +51 -8
- package/src/mcp-server.mjs +32 -9
- package/src/native-diff-v2.mjs +337 -0
- package/src/native-diff.mjs +50 -2
- package/src/observation-lease.mjs +32 -0
- package/src/observation-smoke-client.mjs +1 -1
- package/src/operator-surface.mjs +7 -1
- package/src/process-runtime.mjs +98 -11
- package/src/process-smoke-client.mjs +11 -3
- package/src/r36-compact-disarmed-probe.mjs +50 -0
- package/src/r36-full-surface-probe.mjs +56 -0
- package/src/remote-live-log.mjs +135 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +22 -12
- package/src/remote-mcp-coding-smoke-client.mjs +1 -1
- package/src/remote-mcp-r36-parity-client.mjs +175 -0
- package/src/remote-mcp-smoke-client.mjs +112 -56
- package/src/runtime-update.mjs +51 -1
- package/src/search-cursor.mjs +45 -0
- package/src/semantic-code.mjs +72 -5
- package/src/semantic-lsp-backend.mjs +39 -3
- package/src/version.mjs +1 -1
- package/src/worker-scheduler.mjs +53 -0
- package/test/autonomous-capability-r36.test.mjs +151 -0
- package/test/autonomous-output-contract.test.mjs +1 -1
- package/test/autonomous-runtime.test.mjs +2 -2
- package/test/capability-benchmark-r36.test.mjs +107 -0
- package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
- package/test/capability-claims-r36.test.mjs +23 -0
- package/test/capability-ledger-r36.test.mjs +102 -0
- package/test/coding-catalog-consistency.test.mjs +2 -2
- package/test/coding-plane.test.mjs +2 -2
- package/test/coding-runtime.test.mjs +16 -0
- package/test/coding-search-r36.test.mjs +90 -0
- package/test/compact-catalog-r36.test.mjs +37 -0
- package/test/compact-gateway-r36.test.mjs +66 -0
- package/test/contained.test.mjs +1 -1
- package/test/core.test.mjs +1 -1
- package/test/deferred-slot-operation.test.mjs +9 -0
- package/test/fixtures/lsp-framed-server.mjs +2 -0
- package/test/helpers/autonomous-r34-fixture.mjs +5 -3
- package/test/multi-file-read.test.mjs +3 -0
- package/test/native-diff-topology-r36.test.mjs +105 -0
- package/test/package-boundary.test.mjs +6 -0
- package/test/process-longrun-r36.test.mjs +82 -0
- package/test/process-release-gate.test.mjs +9 -0
- package/test/production-docs.test.mjs +5 -5
- package/test/prompts-resources.test.mjs +7 -1
- package/test/r33-closeout-regression.test.mjs +4 -3
- package/test/r33-finalization.test.mjs +14 -9
- package/test/r36-finalization.test.mjs +104 -0
- package/test/r36-release-identity.test.mjs +45 -0
- package/test/r37-agent-fabric.test.mjs +92 -0
- package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
- package/test/r37-finalization.test.mjs +74 -0
- package/test/release-version.test.mjs +42 -33
- package/test/remote-live-log-r36.test.mjs +37 -0
- package/test/remote-live-log-r37.test.mjs +47 -0
- package/test/remote-live-log-recovery-r36.test.mjs +41 -0
- package/test/remote-r24-catalog.test.mjs +13 -0
- package/test/remote-session-cli.test.mjs +53 -66
- package/test/runtime-self-update.test.mjs +58 -1
- package/test/semantic-lsp-backend.test.mjs +9 -0
- package/test/semantic-r36-rename.test.mjs +82 -0
- package/deadbyte-mcp-0.8.9.tgz +0 -0
|
@@ -3,40 +3,49 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
|
|
6
|
-
const root
|
|
7
|
-
const text
|
|
6
|
+
const root=new URL('../',import.meta.url);
|
|
7
|
+
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
8
|
+
const R36_RELEASE_ID='v0.10.0-8d1e5f09745e7647';
|
|
9
|
+
const R36_MANIFEST='8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8';
|
|
8
10
|
|
|
9
|
-
test('
|
|
10
|
-
const pkg
|
|
11
|
-
const [version,
|
|
12
|
-
text('src/version.mjs'),
|
|
13
|
-
text('controller/
|
|
14
|
-
text('scripts/
|
|
15
|
-
text('
|
|
16
|
-
text('docs/ARCHITECTURE.md'),
|
|
17
|
-
text('controller/README.TXT'),
|
|
18
|
-
text('scripts/deferred-slot-operation.mjs'),
|
|
19
|
-
text('src/remote-mcp-r34-parity-client.mjs'),
|
|
20
|
-
text('scripts/final-closure.mjs'),
|
|
21
|
-
text('scripts/final-closure-verify.mjs'),
|
|
22
|
-
text('scripts/windows-gate-evidence.mjs'),
|
|
11
|
+
test('R37 successor release authority agrees on V0.11.0 / R37 and preserves exact public R36 predecessor',async()=>{
|
|
12
|
+
const pkg=JSON.parse(await text('package.json'));
|
|
13
|
+
const [version,controller,gate,readme,architecture,controllerReadme,deferred,parity,closure,closureVerify,windowsGate,semanticConfig]=await Promise.all([
|
|
14
|
+
text('src/version.mjs'),text('controller/deadbyte-controller.ps1'),text('scripts/gate-windows.ps1'),
|
|
15
|
+
text('README.txt'),text('docs/ARCHITECTURE.md'),text('controller/README.TXT'),
|
|
16
|
+
text('scripts/deferred-slot-operation.mjs'),text('scripts/release-parity.mjs'),text('scripts/final-closure.mjs'),
|
|
17
|
+
text('scripts/final-closure-verify.mjs'),text('scripts/windows-gate-evidence.mjs'),
|
|
23
18
|
text('controller/deadbyte-semantic-lsp-config.json')
|
|
24
19
|
]);
|
|
25
|
-
assert.equal(pkg.version,
|
|
26
|
-
assert.match(version
|
|
27
|
-
assert.match(controller
|
|
28
|
-
assert.match(gate
|
|
29
|
-
assert.match(readme
|
|
30
|
-
assert.match(architecture
|
|
31
|
-
assert.match(controllerReadme
|
|
32
|
-
assert.match(controllerReadme
|
|
33
|
-
assert.match(
|
|
34
|
-
assert.match(
|
|
35
|
-
assert.match(
|
|
36
|
-
assert.match(
|
|
37
|
-
assert.match(
|
|
38
|
-
assert.
|
|
39
|
-
assert.match(
|
|
40
|
-
assert.match(
|
|
41
|
-
assert.match(
|
|
20
|
+
assert.equal(pkg.version,'0.11.0');
|
|
21
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.11\.0'/);
|
|
22
|
+
assert.match(controller,/CONTROLLER V0\.11\.0 \/ R37/);
|
|
23
|
+
assert.match(gate,/DEADBYTE V0\.11\.0 \/ R37 WINDOWS BATCH GATE: PASS/);
|
|
24
|
+
assert.match(readme,/^DEADBYTE MCP V0\.11\.0 \/ R37 PRODUCTION BASELINE$/m);
|
|
25
|
+
assert.match(architecture,/^# DEADBYTE MCP V0\.11\.0 \/ R37 ARCHITECTURE$/m);
|
|
26
|
+
assert.match(controllerReadme,/^DEADBYTE MCP V0\.11\.0 \/ R37 CONTROLLER$/m);
|
|
27
|
+
assert.match(controllerReadme,/full 76-tool (?:profile|catalog)/);
|
|
28
|
+
assert.match(controllerReadme,/compact capability profile[^\n]*<=42 tools/);
|
|
29
|
+
assert.match(deferred,/--expected-version','0\.11\.0'/);
|
|
30
|
+
assert.match(parity,/deadbyte\.r37-parity\.v1/);
|
|
31
|
+
assert.match(parity,/version==='0\.11\.0'|version:'0\.11\.0'/);
|
|
32
|
+
assert.match(parity,new RegExp(R36_RELEASE_ID.replaceAll('.','\\.')));
|
|
33
|
+
assert.match(parity,new RegExp(R36_MANIFEST));
|
|
34
|
+
assert.match(closure,/DEADBYTE-R37-FINAL-CLOSURE-V1/);
|
|
35
|
+
assert.match(closure,/version:'0\.11\.0'/);
|
|
36
|
+
assert.match(closureVerify,/DEADBYTE V0\.11\.0 \/ R37 WINDOWS BATCH GATE: PASS/);
|
|
37
|
+
assert.match(windowsGate,/schema:'deadbyte\.r37-windows-gate\.v1'/);
|
|
38
|
+
assert.equal(createHash('sha256').update(Buffer.from(semanticConfig,'utf8')).digest('hex'),'d274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('R37 keeps the R36 capability surface contract while adding internal agent fabric',async()=>{
|
|
42
|
+
const [parity,fabric,live]=await Promise.all([
|
|
43
|
+
text('src/remote-mcp-r36-parity-client.mjs'),
|
|
44
|
+
text('src/autonomous-runtime.mjs'),
|
|
45
|
+
text('src/remote-live-log.mjs')
|
|
46
|
+
]);
|
|
47
|
+
assert.match(parity,/R36_EXPECTED_TOOLS\.length !== 76/);
|
|
48
|
+
assert.match(parity,/R36_COMPACT_TOOL_LIMIT=42/);
|
|
49
|
+
for(const name of ['deriveAgentMemory','effectIdentity','createWorkerScheduler','issueObservationLease']) assert.match(fabric,new RegExp(name));
|
|
50
|
+
assert.match(live,/createLiveTimelineTracker/);
|
|
42
51
|
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { formatCompactAuditEvent } from '../src/remote-live-log.mjs';
|
|
4
|
+
|
|
5
|
+
test('compact live activity shows request context without payload or secret values',()=>{
|
|
6
|
+
const line=formatCompactAuditEvent({
|
|
7
|
+
event_type:'tool.request',
|
|
8
|
+
tool:'coding_file_read',
|
|
9
|
+
request_id:'abcdef1234567890',
|
|
10
|
+
args:{
|
|
11
|
+
root_id:'projects',
|
|
12
|
+
path:'src/deadbyte-cli.mjs',
|
|
13
|
+
content:'DO_NOT_PRINT_PAYLOAD',
|
|
14
|
+
token:'DO_NOT_PRINT_TOKEN'
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
assert.equal(line,'▶ RUN coding_file_read root=projects path=src/deadbyte-cli.mjs');
|
|
18
|
+
assert.doesNotMatch(line,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN|abcdef1234567890/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('compact live activity shows successful result duration',()=>{
|
|
22
|
+
assert.equal(formatCompactAuditEvent({
|
|
23
|
+
event_type:'tool.result',tool:'coding_search',request_id:'r1',
|
|
24
|
+
outcome:{is_error:false,duration_ms:42}
|
|
25
|
+
}),'✅ OK coding_search 42ms');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('compact live activity shows failed result duration',()=>{
|
|
29
|
+
assert.equal(formatCompactAuditEvent({
|
|
30
|
+
event_type:'tool.result',tool:'coding_command_run',request_id:'r2',
|
|
31
|
+
outcome:{is_error:true,duration_ms:1734}
|
|
32
|
+
}),'❌ ERR coding_command_run 1.73s');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('compact live activity ignores unrelated audit events',()=>{
|
|
36
|
+
assert.equal(formatCompactAuditEvent({event_type:'checkpoint',tool:'coding_tree'}),null);
|
|
37
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createLiveTimelineTracker } from '../src/remote-live-log.mjs';
|
|
4
|
+
|
|
5
|
+
test('R37 live timeline correlates RUN and result with stable short span id',()=>{
|
|
6
|
+
let now=1000;
|
|
7
|
+
const tracker=createLiveTimelineTracker({clock:()=>now});
|
|
8
|
+
const run=tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'abcdef1234567890',
|
|
9
|
+
args:{root_id:'projects',path:'src/a.mjs',content:'SECRET',token:'SECRET'}});
|
|
10
|
+
assert.equal(run,'▶ [abcdef12] RUN coding_file_read root=projects path=src/a.mjs');
|
|
11
|
+
now=1250;
|
|
12
|
+
const done=tracker.observe({event_type:'tool.result',tool:'coding_file_read',request_id:'abcdef1234567890',
|
|
13
|
+
outcome:{is_error:false,duration_ms:250}});
|
|
14
|
+
assert.equal(done,'✓ [abcdef12] OK coding_file_read 250ms');
|
|
15
|
+
assert.deepEqual(tracker.pending(),[]);
|
|
16
|
+
assert.doesNotMatch(run+done,/SECRET/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('R37 live timeline distinguishes result whose request was outside live tail',()=>{
|
|
20
|
+
const tracker=createLiveTimelineTracker();
|
|
21
|
+
const line=tracker.observe({event_type:'tool.result',tool:'coding_tree',request_id:'old-request-12345678',
|
|
22
|
+
outcome:{is_error:false,duration_ms:15}});
|
|
23
|
+
assert.equal(line,'↩ [old-requ] OK coding_tree 15ms (request before live window)');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('R37 live timeline deduplicates terminal result and reports interrupted spans with ids',()=>{
|
|
27
|
+
const tracker=createLiveTimelineTracker();
|
|
28
|
+
tracker.observe({event_type:'tool.request',tool:'coding_command_run',request_id:'request-23456789',args:{command_id:'node_script'}});
|
|
29
|
+
const first=tracker.observe({event_type:'tool.result',tool:'coding_command_run',request_id:'request-23456789',outcome:{is_error:true,duration_ms:900}});
|
|
30
|
+
const duplicate=tracker.observe({event_type:'tool.result',tool:'coding_command_run',request_id:'request-23456789',outcome:{is_error:true,duration_ms:900}});
|
|
31
|
+
assert.equal(first,'✗ [request-] ERR coding_command_run 900ms');
|
|
32
|
+
assert.equal(duplicate,null);
|
|
33
|
+
|
|
34
|
+
tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'pending-12345678',args:{path:'src/a.mjs'}});
|
|
35
|
+
assert.deepEqual(tracker.reconcileInterrupted('recovery'),[
|
|
36
|
+
'⚠ [pending-] INTERRUPTED coding_file_read — recovery ended before terminal result'
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('R37 CLI uses timeline tracker for compact live mode while keeping legacy formatter API',async()=>{
|
|
41
|
+
const {readFile}=await import('node:fs/promises');
|
|
42
|
+
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
43
|
+
assert.match(src,/createLiveTimelineTracker/);
|
|
44
|
+
assert.match(src,/const activityTracker=createLiveTimelineTracker\(\)/);
|
|
45
|
+
assert.doesNotMatch(src,/remote --verbose/);
|
|
46
|
+
assert.doesNotMatch(src,/args\[0\]==='--verbose'/);
|
|
47
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createLiveActivityTracker, formatRemoteLifecycle } from '../src/remote-live-log.mjs';
|
|
4
|
+
|
|
5
|
+
test('R36 lifecycle formatter exposes device/connect/ready/cleanup states with bounded semantic emoji',()=>{
|
|
6
|
+
assert.equal(formatRemoteLifecycle('device_start'),'🚀 [DEVICE] Starting DEADBYTE MCP...');
|
|
7
|
+
assert.equal(formatRemoteLifecycle('bridge_connected'),'🔌 [BRIDGE] Local bridge connected');
|
|
8
|
+
assert.equal(formatRemoteLifecycle('cloud_connected'),'🔌 [CLOUD] Remote MCP connected');
|
|
9
|
+
assert.equal(formatRemoteLifecycle('session_new'),'[SESSION] New foreground session');
|
|
10
|
+
assert.equal(formatRemoteLifecycle('session_restored'),'♻️ [SESSION] Previous result stream replayed');
|
|
11
|
+
assert.equal(formatRemoteLifecycle('ready'),'✅ [READY] DEADBYTE MCP online');
|
|
12
|
+
assert.equal(formatRemoteLifecycle('cleanup'),'🔐 [AUTH] Revoking session authority...');
|
|
13
|
+
assert.throws(()=>formatRemoteLifecycle('invented'),/unknown remote lifecycle/i);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('R36 live activity tracker prints RUN before terminal result and reconciles interrupted requests',()=>{
|
|
17
|
+
const tracker=createLiveActivityTracker();
|
|
18
|
+
const run=tracker.observe({event_type:'tool.request',tool:'coding_search',request_id:'request-1',args:{root_id:'projects',path:'src',pattern:'needle',token:'DO_NOT_PRINT'}});
|
|
19
|
+
assert.equal(run,'▶ RUN coding_search root=projects path=src pattern=needle');
|
|
20
|
+
assert.deepEqual(tracker.pending(),[{request_id:'request-1',tool:'coding_search'}]);
|
|
21
|
+
const ok=tracker.observe({event_type:'tool.result',tool:'coding_search',request_id:'request-1',outcome:{is_error:false,duration_ms:84}});
|
|
22
|
+
assert.equal(ok,'✅ OK coding_search 84ms');
|
|
23
|
+
assert.deepEqual(tracker.pending(),[]);
|
|
24
|
+
|
|
25
|
+
tracker.observe({event_type:'tool.request',tool:'coding_command_run',request_id:'request-2',args:{command_id:'node_script',content:'DO_NOT_PRINT_PAYLOAD'}});
|
|
26
|
+
tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'request-3',args:{path:'src/a.mjs',private_key:'DO_NOT_PRINT_KEY'}});
|
|
27
|
+
const interrupted=tracker.reconcileInterrupted('session_end');
|
|
28
|
+
assert.deepEqual(interrupted,[
|
|
29
|
+
'⚠️ INTERRUPTED coding_command_run — session ended before result',
|
|
30
|
+
'⚠️ INTERRUPTED coding_file_read — session ended before result'
|
|
31
|
+
]);
|
|
32
|
+
assert.deepEqual(tracker.pending(),[]);
|
|
33
|
+
assert.doesNotMatch(interrupted.join('\n'),/DO_NOT_PRINT|request-2|request-3/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('R36 live activity tracker tolerates terminal result whose request was before the tail baseline',()=>{
|
|
37
|
+
const tracker=createLiveActivityTracker();
|
|
38
|
+
const line=tracker.observe({event_type:'tool.result',tool:'coding_tree',request_id:'old-request',outcome:{is_error:false,duration_ms:15}});
|
|
39
|
+
assert.equal(line,'✅ OK coding_tree 15ms');
|
|
40
|
+
assert.deepEqual(tracker.pending(),[]);
|
|
41
|
+
});
|
|
@@ -66,6 +66,19 @@ test('R32 remote gate accepts observation only with machine plus coding configur
|
|
|
66
66
|
assert.match(source, /observation_runtime: observationRuntime/);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test('R36 remote smoke accepts only the exact compact production catalog and uses read-only gateway proof', async () => {
|
|
70
|
+
const source = await readFile(sourcePath, 'utf8');
|
|
71
|
+
assert.match(source, /const r36CompactTools = \[/);
|
|
72
|
+
assert.match(source, /r36CompactTools\.length !== 41/);
|
|
73
|
+
assert.match(source, /const compactProfile = JSON\.stringify\(names\) === JSON\.stringify\(r36CompactTools\)/);
|
|
74
|
+
assert.match(source, /capability_manifest/);
|
|
75
|
+
assert.match(source, /machine_observe/);
|
|
76
|
+
assert.match(source, /op: 'machine_status'/);
|
|
77
|
+
assert.match(source, /op: 'machine_ping'/);
|
|
78
|
+
assert.match(source, /if \(compactProfile\) \{[\s\S]*?\} else \{[\s\S]*?workspace_open/);
|
|
79
|
+
assert.doesNotMatch(source, /r36CompactTools\.includes\(name\).*extras/s);
|
|
80
|
+
});
|
|
81
|
+
|
|
69
82
|
test('R34 disarmed public gate requires the exact canonical 73-tool catalog', async () => {
|
|
70
83
|
const disarmedPath = fileURLToPath(new URL('../src/remote-mcp-r24-disarmed-smoke-client.mjs', import.meta.url));
|
|
71
84
|
const source = await readFile(disarmedPath, 'utf8');
|
|
@@ -18,7 +18,7 @@ async function fixture({failCommand=''}={}){
|
|
|
18
18
|
const trace=path.join(dir,'trace.txt');
|
|
19
19
|
await mkdir(bin,{recursive:true});
|
|
20
20
|
const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
|
|
21
|
-
const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { Write-Host 'DEADBYTE MCP = ONLINE' }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'hoststatus' { Write-Host 'Host plane :
|
|
21
|
+
const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { Write-Host 'DEADBYTE MCP = ONLINE' }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
|
|
22
22
|
await writeFile(launcher,ps1,'utf8');
|
|
23
23
|
return {dir,state,trace,env:{...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_REMOTE_TEST_TRACE:trace,DEADBYTE_REMOTE_TEST_FAIL:failCommand}};
|
|
24
24
|
}
|
|
@@ -65,23 +65,29 @@ async function collect(child){
|
|
|
65
65
|
|
|
66
66
|
const winTest=isWindows?test:test.skip;
|
|
67
67
|
|
|
68
|
-
winTest('bare remote uses
|
|
68
|
+
winTest('bare remote uses one unified live timeline, keeps authority semantics, then disarms and parks when the foreground session ends',async()=>{
|
|
69
69
|
const fx=await fixture();
|
|
70
70
|
let child=null;
|
|
71
71
|
try{
|
|
72
72
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
73
73
|
const output=await collect(child);
|
|
74
|
-
const ready=await waitFor(fx.trace,commands=>commands.slice(0,
|
|
75
|
-
assert.deepEqual(ready.slice(0,
|
|
76
|
-
'start','armprocess','armcoding','armautonomous',
|
|
74
|
+
const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
75
|
+
assert.deepEqual(ready.slice(0,8),[
|
|
76
|
+
'start','armprocess','armcoding','armautonomous',
|
|
77
77
|
'processstatus','codingstatus','autonomousstatus','hoststatus'
|
|
78
78
|
]);
|
|
79
79
|
assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
|
|
80
80
|
const tailDeadline=Date.now()+3000;
|
|
81
|
-
while(Date.now()<tailDeadline
|
|
81
|
+
while(Date.now()<tailDeadline&&!/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
|
|
82
|
+
assert.match(output.stdout,/🚀 \[DEVICE\] Starting DEADBYTE MCP/);
|
|
83
|
+
assert.match(output.stdout,/🔌 \[BRIDGE\] Local bridge connected/);
|
|
84
|
+
assert.match(output.stdout,/🔌 \[CLOUD\] Remote MCP connected/);
|
|
85
|
+
assert.match(output.stdout,/\[SESSION\] New foreground session/);
|
|
86
|
+
assert.match(output.stdout,/\[BOOT\] Starting local \+ remote runtime/);
|
|
82
87
|
assert.match(output.stdout,/\[OK\] ONLINE/);
|
|
83
|
-
assert.match(output.stdout
|
|
84
|
-
assert.match(output.stdout
|
|
88
|
+
assert.match(output.stdout,/✅ \[READY\] DEADBYTE MCP online/);
|
|
89
|
+
assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
|
|
90
|
+
assert.match(output.stdout,/\[LOG\] Unified live timeline\. Ctrl\+C disconnects and revokes authority\./);
|
|
85
91
|
assert.doesNotMatch(output.stdout,/Process plane\s*:/);
|
|
86
92
|
assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
|
|
87
93
|
const auditDir=path.join(fx.state,'audit');
|
|
@@ -91,11 +97,11 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
|
|
|
91
97
|
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
92
98
|
assert.match(String(marker.result_session_id||''),/^[0-9a-f]{24}$/);
|
|
93
99
|
const auditEvents=[
|
|
94
|
-
{seq:1,event_type:'tool.request',tool:'coding_tree',request_id:'abcdef1234567890'},
|
|
95
|
-
{seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false}},
|
|
96
|
-
{seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false}},
|
|
97
|
-
{seq:3,event_type:'tool.request',tool:'coding_file_read',request_id:'fedcba9876543210'},
|
|
98
|
-
{seq:4,event_type:'tool.result',tool:'coding_file_read',request_id:'fedcba9876543210',outcome:{is_error:true}}
|
|
100
|
+
{seq:1,event_type:'tool.request',tool:'coding_tree',request_id:'abcdef1234567890',args:{root_id:'projects',path:'src',content:'DO_NOT_PRINT_PAYLOAD',token:'DO_NOT_PRINT_TOKEN'}},
|
|
101
|
+
{seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false,duration_ms:42}},
|
|
102
|
+
{seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false,duration_ms:42}},
|
|
103
|
+
{seq:3,event_type:'tool.request',tool:'coding_file_read',request_id:'fedcba9876543210',args:{root_id:'projects',path:'src/deadbyte-cli.mjs'}},
|
|
104
|
+
{seq:4,event_type:'tool.result',tool:'coding_file_read',request_id:'fedcba9876543210',outcome:{is_error:true,duration_ms:1734}}
|
|
99
105
|
];
|
|
100
106
|
await writeFile(path.join(auditDir,'segment-00000001.jsonl'),`${auditEvents.map(event=>JSON.stringify(event)).join('\n')}\n`,'utf8');
|
|
101
107
|
await writeFile(path.join(runtimeLogs,'bridge.stdout.log'),[
|
|
@@ -104,18 +110,23 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
|
|
|
104
110
|
'[TOOL OUT] 2026-09-16T00:00:00.002Z #000002 coding_file_read {"is_error":true}'
|
|
105
111
|
].join('\n')+'\n','utf8');
|
|
106
112
|
const logDeadline=Date.now()+3000;
|
|
107
|
-
while(Date.now()<logDeadline
|
|
108
|
-
assert.match(output.stdout
|
|
109
|
-
assert.match(output.stdout
|
|
110
|
-
assert.
|
|
113
|
+
while(Date.now()<logDeadline&&!/✗ \[fedcba98\] ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
|
|
114
|
+
assert.match(output.stdout,/▶ \[abcdef12\] RUN coding_tree root=projects path=src/);
|
|
115
|
+
assert.match(output.stdout,/✓ \[abcdef12\] OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:1\]/);
|
|
116
|
+
assert.match(output.stdout,/▶ \[fedcba98\] RUN coding_file_read root=projects path=src\/deadbyte-cli\.mjs/);
|
|
117
|
+
assert.match(output.stdout,/✗ \[fedcba98\] ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:2\]/);
|
|
118
|
+
assert.equal((output.stdout.match(/✓ \[abcdef12\] OK coding_tree 42ms/g)||[]).length,1,'duplicate terminal result must print once');
|
|
111
119
|
const resultJournal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`),'utf8'));
|
|
112
120
|
assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2]);
|
|
113
121
|
assert.equal(resultJournal.events[0].request_id,'abcdef1234567890');
|
|
114
122
|
assert.ok(resultJournal.events.every(event=>/^[0-9a-f]{64}$/.test(event.payload_sha256)));
|
|
115
123
|
assert.doesNotMatch(JSON.stringify(resultJournal),/TOOL IN|TOOL OUT|stdout_excerpt|stderr_excerpt/);
|
|
116
|
-
assert.
|
|
117
|
-
assert.
|
|
118
|
-
assert.doesNotMatch(output.stdout,/
|
|
124
|
+
assert.match(output.stdout,/abcdef12/);
|
|
125
|
+
assert.match(output.stdout,/fedcba98/);
|
|
126
|
+
assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN/);
|
|
127
|
+
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] IN coding_tree/);
|
|
128
|
+
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] OUT coding_tree/);
|
|
129
|
+
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] ERR coding_file_read/);
|
|
119
130
|
assert.doesNotMatch(output.stdout,/\[TOOL IN \]|\[TOOL OUT\]/);
|
|
120
131
|
assert.doesNotMatch(output.stdout,/payload bytes=/);
|
|
121
132
|
child.stdin.end();
|
|
@@ -124,9 +135,10 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
|
|
|
124
135
|
const commands=await traceCommands(fx.trace);
|
|
125
136
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
126
137
|
assert.match(output.stdout,/^DEADBYTE MCP$/m);
|
|
127
|
-
assert.match(output.stdout,/
|
|
138
|
+
assert.match(output.stdout,/Unified live timeline/i);
|
|
139
|
+
assert.match(output.stdout,/🔐 \[AUTH\] Revoking session authority/);
|
|
128
140
|
assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmhost OK|DEADBYTE MCP = PARKED/);
|
|
129
|
-
const withoutApprovedEmoji=output.stdout.replace(/[
|
|
141
|
+
const withoutApprovedEmoji=output.stdout.replace(/[📜📥📤✅❌⚠♻🛡▶🔐🚀🔌]/gu,'');
|
|
130
142
|
assert.doesNotMatch(withoutApprovedEmoji,/\p{Extended_Pictographic}/u,'human live stream may use only the approved semantic emoji set');
|
|
131
143
|
}finally{
|
|
132
144
|
if(child?.exitCode===null) child.kill();
|
|
@@ -142,17 +154,17 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
142
154
|
const firstOutput=await collect(first);
|
|
143
155
|
await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
144
156
|
const liveDeadline=Date.now()+3000;
|
|
145
|
-
while(Date.now()<liveDeadline&&!/\[LOG\]
|
|
157
|
+
while(Date.now()<liveDeadline&&!/\[LOG\] Unified live timeline/.test(firstOutput.stdout)) await sleep(50);
|
|
146
158
|
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
147
159
|
const auditFile=path.join(auditDir,'segment-00000001.jsonl');
|
|
148
160
|
await writeFile(auditFile,JSON.stringify({seq:1,event_type:'tool.result',tool:'coding_tree',request_id:'resume-request-1',outcome:{is_error:false}})+'\n','utf8');
|
|
149
161
|
let deadline=Date.now()+3000;
|
|
150
|
-
while(Date.now()<deadline&&!/coding_tree
|
|
151
|
-
const cursor1=firstOutput.stdout.match(/coding_tree
|
|
162
|
+
while(Date.now()<deadline&&!/coding_tree .*\[cursor r1:[0-9a-f]{24}:1\]/.test(firstOutput.stdout)) await sleep(50);
|
|
163
|
+
const cursor1=firstOutput.stdout.match(/coding_tree .*\[cursor (r1:[0-9a-f]{24}:1)\]/)?.[1];
|
|
152
164
|
assert.ok(cursor1,'first result cursor missing');
|
|
153
165
|
await writeFile(auditFile,JSON.stringify({seq:2,event_type:'tool.result',tool:'coding_file_read',request_id:'resume-request-2',outcome:{is_error:false}})+'\n',{encoding:'utf8',flag:'a'});
|
|
154
166
|
deadline=Date.now()+3000;
|
|
155
|
-
while(Date.now()<deadline&&!/coding_file_read
|
|
167
|
+
while(Date.now()<deadline&&!/coding_file_read .*\[cursor r1:[0-9a-f]{24}:2\]/.test(firstOutput.stdout)) await sleep(50);
|
|
156
168
|
first.stdin.end();
|
|
157
169
|
const firstEnded=await Promise.race([firstOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
158
170
|
assert.equal(firstEnded.timeout,undefined);
|
|
@@ -161,11 +173,12 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
161
173
|
const secondOutput=await collect(second);
|
|
162
174
|
deadline=Date.now()+5000;
|
|
163
175
|
while(Date.now()<deadline&&(!/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/.test(secondOutput.stdout)||
|
|
164
|
-
!/\[LOG\]
|
|
176
|
+
!/\[LOG\] Unified live timeline/.test(secondOutput.stdout))) await sleep(50);
|
|
165
177
|
assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/);
|
|
178
|
+
assert.match(secondOutput.stdout,/♻️ \[SESSION\] Previous result stream replayed/);
|
|
166
179
|
assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_tree/);
|
|
167
180
|
const replayIndex=secondOutput.stdout.indexOf('[REPLAY]');
|
|
168
|
-
const liveIndex=secondOutput.stdout.indexOf('[LOG]
|
|
181
|
+
const liveIndex=secondOutput.stdout.indexOf('[LOG] Unified live timeline');
|
|
169
182
|
assert.ok(replayIndex>=0&&liveIndex>replayIndex,'resume replay must happen before new live tail');
|
|
170
183
|
second.stdin.end();
|
|
171
184
|
const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
@@ -177,43 +190,17 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
177
190
|
}
|
|
178
191
|
});
|
|
179
192
|
|
|
180
|
-
winTest('remote --verbose
|
|
193
|
+
winTest('remote --verbose is rejected because R37 has one unified remote mode',async()=>{
|
|
181
194
|
const fx=await fixture();
|
|
182
|
-
let child=null;
|
|
183
195
|
try{
|
|
184
|
-
child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['
|
|
196
|
+
const child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
185
197
|
const output=await collect(child);
|
|
186
|
-
const ready=await waitFor(fx.trace,commands=>commands.slice(0,9).join(',')==='start,armprocess,armcoding,armautonomous,armhost,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
187
|
-
assert.equal(ready.length>=9,true);
|
|
188
|
-
const liveDeadline=Date.now()+3000;
|
|
189
|
-
while(Date.now()<liveDeadline&&!/Live logs active/.test(output.stdout)) await sleep(50);
|
|
190
|
-
assert.match(output.stdout,/Live logs active/);
|
|
191
|
-
const auditDir=path.join(fx.state,'audit');
|
|
192
|
-
const runtimeLogs=path.join(fx.state,'runtime','logs');
|
|
193
|
-
await mkdir(auditDir,{recursive:true});
|
|
194
|
-
await mkdir(runtimeLogs,{recursive:true});
|
|
195
|
-
await writeFile(path.join(auditDir,'segment-00000001.jsonl'),[
|
|
196
|
-
JSON.stringify({seq:1,event_type:'tool.request',tool:'coding_tree',request_id:'abcdef1234567890'}),
|
|
197
|
-
JSON.stringify({seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false}})
|
|
198
|
-
].join('\n')+'\n','utf8');
|
|
199
|
-
await writeFile(path.join(runtimeLogs,'bridge.stdout.log'),[
|
|
200
|
-
'[TOOL IN ] 2026-09-16T00:00:00.000Z #000001 coding_tree {}',
|
|
201
|
-
'[TOOL OUT] 2026-09-16T00:00:00.001Z #000001 coding_tree {"is_error":false}'
|
|
202
|
-
].join('\n')+'\n','utf8');
|
|
203
|
-
const deadline=Date.now()+3000;
|
|
204
|
-
while(Date.now()<deadline&&!/\[BRIDGE\.STDOUT\] \[TOOL OUT\].*coding_tree/.test(output.stdout)) await sleep(50);
|
|
205
|
-
assert.match(output.stdout,/\[AUDIT #1\] tool\.request coding_tree abcdef12/);
|
|
206
|
-
assert.match(output.stdout,/\[AUDIT #2\] tool\.result coding_tree abcdef12/);
|
|
207
|
-
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] \[TOOL IN \].*coding_tree/);
|
|
208
|
-
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] \[TOOL OUT\].*coding_tree/);
|
|
209
|
-
child.stdin.end();
|
|
210
198
|
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
211
|
-
assert.equal(ended.timeout,undefined
|
|
212
|
-
assert.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
199
|
+
assert.equal(ended.timeout,undefined);
|
|
200
|
+
assert.notEqual(ended.code,0);
|
|
201
|
+
assert.match(output.stderr,/unsupported remote command '--verbose'/i);
|
|
202
|
+
assert.deepEqual(await traceCommands(fx.trace),[],'rejected legacy flag must not touch runtime authority');
|
|
203
|
+
}finally{await rm(fx.dir,{recursive:true,force:true});}
|
|
217
204
|
});
|
|
218
205
|
|
|
219
206
|
winTest('detached guardian revokes authority, parks, and removes the session marker after the owner is hard-killed',async()=>{
|
|
@@ -222,9 +209,9 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
222
209
|
try{
|
|
223
210
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
224
211
|
const output=await collect(child);
|
|
225
|
-
const ready=await waitFor(fx.trace,commands=>commands.slice(0,
|
|
226
|
-
assert.deepEqual(ready.slice(0,
|
|
227
|
-
'start','armprocess','armcoding','armautonomous',
|
|
212
|
+
const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
213
|
+
assert.deepEqual(ready.slice(0,8),[
|
|
214
|
+
'start','armprocess','armcoding','armautonomous',
|
|
228
215
|
'processstatus','codingstatus','autonomousstatus','hoststatus'
|
|
229
216
|
]);
|
|
230
217
|
const markerPath=path.join(fx.state,'runtime','remote-session.json');
|
|
@@ -259,7 +246,7 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
259
246
|
});
|
|
260
247
|
|
|
261
248
|
winTest('bare remote rolls back authority and parks if one plane fails to arm',async()=>{
|
|
262
|
-
const fx=await fixture({failCommand:'
|
|
249
|
+
const fx=await fixture({failCommand:'armcoding'});
|
|
263
250
|
try{
|
|
264
251
|
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
265
252
|
const output=await collect(child);
|
|
@@ -267,7 +254,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
|
|
|
267
254
|
assert.equal(ended.timeout,undefined,'failed startup did not terminate');
|
|
268
255
|
assert.notEqual(ended.code,0);
|
|
269
256
|
const commands=await traceCommands(fx.trace);
|
|
270
|
-
assert.deepEqual(commands.slice(0,
|
|
257
|
+
assert.deepEqual(commands.slice(0,3),['start','armprocess','armcoding']);
|
|
271
258
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
272
259
|
assert.doesNotMatch(output.stdout,/Live logs/i);
|
|
273
260
|
}finally{await rm(fx.dir,{recursive:true,force:true});}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
loadPowerShellDataFile,
|
|
11
11
|
parseUpdateArgs,
|
|
12
12
|
prepareNamedTunnelCloudSpec,
|
|
13
|
+
readControllerStartFailure,
|
|
13
14
|
runControllerStartNoCapture
|
|
14
15
|
} from '../scripts/runtime-self-update.mjs';
|
|
15
16
|
import * as runtimeUpdate from '../scripts/runtime-self-update.mjs';
|
|
@@ -31,10 +32,12 @@ test('R32 updater loads controller data with Windows PowerShell modules isolated
|
|
|
31
32
|
}
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
test('
|
|
35
|
+
test('R36 updater keeps detached start semantics and binds controller sidecar diagnostics', async () => {
|
|
35
36
|
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
36
37
|
assert.match(source,/stdio:'ignore'/);
|
|
37
38
|
assert.match(source,/env:commandEnvironment\(exe,env\?\?process\.env\)/);
|
|
39
|
+
assert.match(source,/readControllerStartFailure\(ctx\)/);
|
|
40
|
+
assert.match(source,/evidence_sha256/);
|
|
38
41
|
assert.match(source,/for\(let attempt=1;attempt<=3;attempt\+=1\)/);
|
|
39
42
|
});
|
|
40
43
|
|
|
@@ -74,6 +77,60 @@ test('R27 controller start transport returns while its long-lived descendant is
|
|
|
74
77
|
assert.doesNotThrow(()=>process.kill(childPid,0),'controller start returned only after descendant exited');
|
|
75
78
|
});
|
|
76
79
|
|
|
80
|
+
test('R36 controller start sidecar is schema-bound and hash-bound', async () => {
|
|
81
|
+
const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r36-start-evidence-'));
|
|
82
|
+
const controllerDir=path.join(base,'controller');
|
|
83
|
+
const runtime=path.join(controllerDir,'runtime');
|
|
84
|
+
await mkdir(runtime,{recursive:true});
|
|
85
|
+
await writeFile(path.join(controllerDir,'deadbyte-controller.ps1'),"param([string]$Command)\nexit 0\n");
|
|
86
|
+
const body={
|
|
87
|
+
schema:'deadbyte.controller-start-failure.v1',
|
|
88
|
+
command:'start',
|
|
89
|
+
failed_at_utc:'2026-09-18T00:00:00.000Z',
|
|
90
|
+
message:'R36_CONTROLLER_START_SENTINEL',
|
|
91
|
+
bridge_stdout_tail:'',
|
|
92
|
+
bridge_stderr_tail:'compact bridge failed',
|
|
93
|
+
cloud_stdout_tail:'',
|
|
94
|
+
cloud_stderr_tail:''
|
|
95
|
+
};
|
|
96
|
+
const bytes=Buffer.from(JSON.stringify(body),'utf8');
|
|
97
|
+
await writeFile(path.join(runtime,'controller-start-failure.json'),bytes);
|
|
98
|
+
const evidence=await readControllerStartFailure(legacyContext(base));
|
|
99
|
+
assert.equal(evidence.message,'R36_CONTROLLER_START_SENTINEL');
|
|
100
|
+
assert.equal(evidence.command,'start');
|
|
101
|
+
assert.equal(evidence.sha256,sha(bytes));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('R36 updater preserves exact controller-start failure evidence before rollback', async () => {
|
|
105
|
+
assert.equal(typeof runtimeUpdate.preserveControllerStartFailure,'function');
|
|
106
|
+
const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r36-preserve-start-evidence-'));
|
|
107
|
+
const controllerDir=path.join(base,'controller');
|
|
108
|
+
const runtime=path.join(controllerDir,'runtime');
|
|
109
|
+
const evidenceDir=path.join(base,'evidence','updates','candidate');
|
|
110
|
+
await mkdir(runtime,{recursive:true});
|
|
111
|
+
await writeFile(path.join(controllerDir,'deadbyte-controller.ps1'),"param([string]$Command)\nexit 0\n");
|
|
112
|
+
const body={
|
|
113
|
+
schema:'deadbyte.controller-start-failure.v1',
|
|
114
|
+
command:'start',
|
|
115
|
+
failed_at_utc:'2026-09-18T00:00:00.000Z',
|
|
116
|
+
message:'R36_PRESERVE_SENTINEL',
|
|
117
|
+
bridge_stdout_tail:'bridge-out',
|
|
118
|
+
bridge_stderr_tail:'bridge-err',
|
|
119
|
+
cloud_stdout_tail:'',
|
|
120
|
+
cloud_stderr_tail:''
|
|
121
|
+
};
|
|
122
|
+
const bytes=Buffer.from(JSON.stringify(body),'utf8');
|
|
123
|
+
const source=path.join(runtime,'controller-start-failure.json');
|
|
124
|
+
await writeFile(source,bytes);
|
|
125
|
+
const preserved=await runtimeUpdate.preserveControllerStartFailure(legacyContext(base),evidenceDir);
|
|
126
|
+
assert.equal(preserved.sha256,sha(bytes));
|
|
127
|
+
assert.equal(preserved.message,'R36_PRESERVE_SENTINEL');
|
|
128
|
+
assert.equal(preserved.path,path.join(evidenceDir,'controller-start-failure.json'));
|
|
129
|
+
assert.deepEqual(await readFile(preserved.path),bytes);
|
|
130
|
+
await writeFile(source,Buffer.from('destroyed-after-snapshot','utf8'));
|
|
131
|
+
assert.deepEqual(await readFile(preserved.path),bytes,'rollback/runtime restart must not mutate preserved evidence');
|
|
132
|
+
});
|
|
133
|
+
|
|
77
134
|
test('R28 slot-update CLI binds direct cloudflared plus external token file as one pinned contract', () => {
|
|
78
135
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.8.4','--expected-manifest',hex('a')];
|
|
79
136
|
assert.throws(() => parseUpdateArgs([...base,'--cloud-cloudflared','C:/cloudflared.exe']), /named tunnel bootstrap requires all|cloud/i);
|
|
@@ -43,6 +43,15 @@ test('semantic LSP backend normalizes outline definition references diagnostics
|
|
|
43
43
|
assert.equal(refs.locations[0].path,'main.foo');
|
|
44
44
|
const diagnostics=await f.backend.diagnostics({rootId:'project',relativePath:'main.foo',maxResults:10});
|
|
45
45
|
assert.equal(diagnostics.diagnostics[0].message,'fixture diagnostic');
|
|
46
|
+
assert.match(diagnostics.file_sha256,/^[0-9a-f]{64}$/); assert.match(diagnostics.result_sha256,/^[0-9a-f]{64}$/);
|
|
47
|
+
const implementations=await f.backend.implementations({rootId:'project',relativePath:'main.foo',line:0,column:18,maxResults:10});
|
|
48
|
+
assert.equal(implementations.status,'ok'); assert.equal(implementations.locations[0].path,'main.foo'); assert.match(implementations.result_sha256,/^[0-9a-f]{64}$/);
|
|
49
|
+
const unsupportedImpl=await f.backend.implementations({rootId:'project',relativePath:'main.foo',line:0,column:999,maxResults:10});
|
|
50
|
+
assert.equal(unsupportedImpl.status,'unsupported'); assert.equal(unsupportedImpl.capability,'implementations');
|
|
51
|
+
const rename=await f.backend.renameEdits({rootId:'project',relativePath:'main.foo',line:0,column:18,newName:'gamma'});
|
|
52
|
+
assert.equal(rename.status,'ok'); assert.deepEqual(rename.edits,[{path:'main.foo',start:16,length:5,new_text:'gamma'}]);
|
|
53
|
+
const unsupportedRename=await f.backend.renameEdits({rootId:'project',relativePath:'main.foo',line:0,column:18,newName:'UNSUPPORTED'});
|
|
54
|
+
assert.equal(unsupportedRename.status,'unsupported'); assert.equal(unsupportedRename.capability,'rename_preview');
|
|
46
55
|
const before=await f.codingRuntime.fileRead({rootId:'project',relativePath:'main.foo'});
|
|
47
56
|
const edited=await f.backend.replaceSymbol({rootId:'project',relativePath:'main.foo',symbolId:symbol.symbol_id,newBody:'export function beta() {}',expectedSha256:before.sha256});
|
|
48
57
|
assert.equal(edited.status,'committed');assert.equal(edited.symbol_name,'alpha');assert.equal(edited.engine,'fixture-lsp');
|