deadbyte-mcp 0.9.0 → 0.10.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 +119 -91
- package/README.txt +16 -11
- 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-verify.mjs +88 -39
- package/scripts/final-closure.mjs +63 -56
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-tests.ps1 +9 -0
- package/scripts/release-parity.mjs +149 -139
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-mcp-tools.mjs +5 -1
- package/src/autonomous-output-contracts.mjs +4 -4
- package/src/autonomous-planner-contracts.mjs +36 -1
- package/src/autonomous-planner.mjs +25 -3
- package/src/autonomous-runtime.mjs +191 -21
- 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 +51 -28
- 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-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 +76 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +7 -3
- 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/test/autonomous-capability-r36.test.mjs +151 -0
- 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 +3 -3
- 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 +52 -0
- package/test/release-version.test.mjs +18 -15
- package/test/remote-live-log-r36.test.mjs +37 -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 +40 -30
- 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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { validateParityEvidence, R35_PREDECESSOR_RELEASE_ID, R35_PREDECESSOR_MANIFEST } from '../scripts/release-parity.mjs';
|
|
5
|
+
import { verifyFinalClosureObject } from '../scripts/final-closure-verify.mjs';
|
|
6
|
+
|
|
7
|
+
const root=new URL('../',import.meta.url);
|
|
8
|
+
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
9
|
+
|
|
10
|
+
function parityFixture(){
|
|
11
|
+
const manifest='a'.repeat(64);
|
|
12
|
+
return {
|
|
13
|
+
schema:'deadbyte.r36-parity.v1',status:'passed',execution_provider:'deadbyte_mcp',rdc_operation_count:0,
|
|
14
|
+
candidate:{release_id:`v0.10.0-${manifest.slice(0,16)}`,version:'0.10.0',manifest_sha256:manifest},
|
|
15
|
+
immutable_slot:{protected:true,release_root:'C:/immutable/r36',development_root:'D:/Projects/deadbyte-mcp-r36-dev'},
|
|
16
|
+
local_full:{status:'passed',tool_count:76,strict_output_schemas:true},
|
|
17
|
+
public_compact:{status:'passed',tool_count:41,capability_closure:true,gateway_count:5},
|
|
18
|
+
autonomous:{status:'passed',signed_release_verified:true,observed_failure_before_replan:true},
|
|
19
|
+
before_rollback_disarmed:{status:'passed',capability_closure:true,coding_armed:false,autonomous_armed:false},
|
|
20
|
+
rollback:{state:'completed'},stage_restore:{status:'restored',manifest_sha256:manifest},restore:{state:'completed'},
|
|
21
|
+
predecessor:{release_id:R35_PREDECESSOR_RELEASE_ID,version:'0.9.0',manifest_sha256:R35_PREDECESSOR_MANIFEST},
|
|
22
|
+
predecessor_disarmed:{status:'passed',tool_count:73},
|
|
23
|
+
successor:{release_id:`v0.10.0-${manifest.slice(0,16)}`,version:'0.10.0',manifest_sha256:manifest},
|
|
24
|
+
successor_disarmed:{status:'passed',capability_closure:true,coding_armed:false,autonomous_armed:false},
|
|
25
|
+
requirements:Object.fromEntries(['tree_read','precondition_mutation','search','owned_process','observation','coding_loop','autonomy','interrupted_recovery','evidence_verification','compact_closure','full_surface'].map(key=>[key,true]))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test('R36 parity validator requires full-76, compact closure, exact R35 predecessor and immutable-slot separation',()=>{
|
|
30
|
+
const good=parityFixture();
|
|
31
|
+
assert.equal(validateParityEvidence(good),true);
|
|
32
|
+
assert.throws(()=>validateParityEvidence({...good,local_full:{...good.local_full,tool_count:75}}),/full 76/i);
|
|
33
|
+
assert.throws(()=>validateParityEvidence({...good,public_compact:{...good.public_compact,capability_closure:false}}),/compact closure/i);
|
|
34
|
+
assert.throws(()=>validateParityEvidence({...good,predecessor:{...good.predecessor,manifest_sha256:'b'.repeat(64)}}),/predecessor/i);
|
|
35
|
+
assert.throws(()=>validateParityEvidence({...good,immutable_slot:{...good.immutable_slot,development_root:good.immutable_slot.release_root}}),/immutable-slot/i);
|
|
36
|
+
assert.throws(()=>validateParityEvidence({...good,stage_restore:{status:'missing',manifest_sha256:good.candidate.manifest_sha256}}),/stage restore/i);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('R36 compact disarmed probe carries signed machine status/ping evidence into closure',async()=>{
|
|
40
|
+
const probe=await text('src/r36-compact-disarmed-probe.mjs');
|
|
41
|
+
assert.match(probe,/op:'machine_ping'/);
|
|
42
|
+
assert.match(probe,/machine_status/);
|
|
43
|
+
assert.match(probe,/machine_ping/);
|
|
44
|
+
assert.match(probe,/nonce/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('R36 parity captures exact successor bridge before rollback and reuses it for restore',async()=>{
|
|
48
|
+
const parity=await text('scripts/release-parity.mjs');
|
|
49
|
+
assert.match(parity,/loadPowerShellDataFile/);
|
|
50
|
+
assert.match(parity,/successorBridge/);
|
|
51
|
+
assert.match(parity,/Bridge\?\.WorkingDirectory/);
|
|
52
|
+
assert.match(parity,/Bridge\?\.ScriptSha256/);
|
|
53
|
+
assert.match(parity,/const stageRestore=await restoreFrozenStage\(\{developmentRoot,releaseRoot:active\.release_root,expectedManifestSha256:candidateManifest\}\)/);
|
|
54
|
+
assert.match(parity,/scheduleDeferred\('activate',candidateManifest,\{bridgeRoot:successorBridge\.root,bridgeScriptSha256:successorBridge\.sha256\}\)/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('R36 compact parity uses bounded release-parity tests instead of the full npm suite',async()=>{
|
|
58
|
+
const parity=await text('src/remote-mcp-r36-parity-client.mjs');
|
|
59
|
+
assert.match(parity,/const testScript='scripts\/release-parity-tests\.ps1'/);
|
|
60
|
+
assert.doesNotMatch(parity,/const testScript='scripts\/autonomous-tests\.ps1'/);
|
|
61
|
+
const script=await text('scripts/release-parity-tests.ps1');
|
|
62
|
+
assert.match(script,/r36-finalization\.test\.mjs/);
|
|
63
|
+
assert.match(script,/r36-release-identity\.test\.mjs/);
|
|
64
|
+
assert.match(script,/deferred-slot-operation\.test\.mjs/);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('R36 compact parity treats stale machine-fs precondition denial as expected evidence',async()=>{
|
|
68
|
+
const parity=await text('src/remote-mcp-r36-parity-client.mjs');
|
|
69
|
+
assert.match(parity,/const staleValue=stale\.structuredContent/);
|
|
70
|
+
assert.match(parity,/stale\.isError!==true\|\|staleValue\.status!=='denied'/);
|
|
71
|
+
assert.doesNotMatch(parity,/structured\(stale,'machine_fs stale'\)/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('R36 final closure writer binds compact public evidence and R36 release evidence domains',async()=>{
|
|
75
|
+
const writer=await text('scripts/final-closure.mjs');
|
|
76
|
+
assert.match(writer,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
|
|
77
|
+
assert.match(writer,/deadbyte\.r36-final-closure\.v1/);
|
|
78
|
+
assert.match(writer,/pointer\.version==='0\.10\.0'/);
|
|
79
|
+
assert.match(writer,/release_label:'R36 FINAL MACHINE BASELINE'/);
|
|
80
|
+
assert.match(writer,/deadbyte\.r36-parity\.v1/);
|
|
81
|
+
assert.match(writer,/deadbyte\.r36-windows-gate\.v1/);
|
|
82
|
+
assert.match(writer,/tool_count.*76|local_full[\s\S]*76/);
|
|
83
|
+
assert.match(writer,/capability_closure/);
|
|
84
|
+
assert.match(writer,/R35_PREDECESSOR_RELEASE_ID|v0\.9\.0-b95e1ec1d9a7e26a/);
|
|
85
|
+
assert.match(writer,/development_root/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('R36 independent closure verifier binds full, compact, predecessor, restore, Windows gate and immutable slot',async()=>{
|
|
89
|
+
const verifier=await text('scripts/final-closure-verify.mjs');
|
|
90
|
+
assert.equal(typeof verifyFinalClosureObject,'function');
|
|
91
|
+
assert.doesNotMatch(verifier,/from ['"]\.\.\/src\//);
|
|
92
|
+
assert.match(verifier,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
|
|
93
|
+
assert.match(verifier,/deadbyte\.r36-final-closure\.v1/);
|
|
94
|
+
assert.match(verifier,/envelope\.body\.version==='0\.10\.0'/);
|
|
95
|
+
assert.match(verifier,/R36 FINAL MACHINE BASELINE/);
|
|
96
|
+
assert.match(verifier,/deadbyte\.r36-parity\.v1/);
|
|
97
|
+
assert.match(verifier,/deadbyte\.r36-windows-gate\.v1/);
|
|
98
|
+
assert.match(verifier,/76/);
|
|
99
|
+
assert.match(verifier,/42/);
|
|
100
|
+
assert.match(verifier,/b95e1ec1d9a7e26a96ef492306c1be4df18c883ff03ab3b6015f677d7429ec8d/);
|
|
101
|
+
assert.match(verifier,/development_root/);
|
|
102
|
+
assert.match(verifier,/release_root/);
|
|
103
|
+
assert.match(verifier,/successor_disarmed/);
|
|
104
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
const root=new URL('../',import.meta.url);
|
|
6
|
+
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
7
|
+
const R35_RELEASE_ID='v0.9.0-b95e1ec1d9a7e26a';
|
|
8
|
+
const R35_MANIFEST='b95e1ec1d9a7e26a96ef492306c1be4df18c883ff03ab3b6015f677d7429ec8d';
|
|
9
|
+
|
|
10
|
+
test('R36 current release authority is consistently 0.10.0 / R36 and preserves exact R35 predecessor',async()=>{
|
|
11
|
+
const pkg=JSON.parse(await text('package.json'));
|
|
12
|
+
const [version,controller,gate,readme,architecture,controllerReadme,deferred,parity,closure,closureVerify,windowsGate]=await Promise.all([
|
|
13
|
+
text('src/version.mjs'),text('controller/deadbyte-controller.ps1'),text('scripts/gate-windows.ps1'),
|
|
14
|
+
text('README.txt'),text('docs/ARCHITECTURE.md'),text('controller/README.TXT'),
|
|
15
|
+
text('scripts/deferred-slot-operation.mjs'),text('scripts/release-parity.mjs'),text('scripts/final-closure.mjs'),
|
|
16
|
+
text('scripts/final-closure-verify.mjs'),text('scripts/windows-gate-evidence.mjs')
|
|
17
|
+
]);
|
|
18
|
+
assert.equal(pkg.version,'0.10.0');
|
|
19
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.10\.0'/);
|
|
20
|
+
assert.match(controller,/CONTROLLER V0\.10\.0 \/ R36/);
|
|
21
|
+
assert.match(gate,/DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
|
|
22
|
+
assert.match(readme,/^DEADBYTE MCP V0\.10\.0 \/ R36 PRODUCTION BASELINE$/m);
|
|
23
|
+
assert.match(architecture,/^# DEADBYTE MCP V0\.10\.0 \/ R36 ARCHITECTURE$/m);
|
|
24
|
+
assert.match(controllerReadme,/^DEADBYTE MCP V0\.10\.0 \/ R36 CONTROLLER$/m);
|
|
25
|
+
assert.match(deferred,/--expected-version','0\.10\.0'/);
|
|
26
|
+
assert.match(parity,/deadbyte\.r36-parity\.v1/);
|
|
27
|
+
assert.match(parity,new RegExp(R35_RELEASE_ID.replaceAll('.','\\.')));
|
|
28
|
+
assert.match(parity,new RegExp(R35_MANIFEST));
|
|
29
|
+
assert.match(closure,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
|
|
30
|
+
assert.match(closure,/version:'0\.10\.0'/);
|
|
31
|
+
assert.match(closure,/release_label:'R36 FINAL MACHINE BASELINE'/);
|
|
32
|
+
assert.match(closureVerify,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
|
|
33
|
+
assert.match(closureVerify,/version==='0\.10\.0'/);
|
|
34
|
+
assert.match(windowsGate,/schema:'deadbyte\.r36-windows-gate\.v1'/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('R36 controller binds production remote to compact profile and restores parent environment',async()=>{
|
|
38
|
+
const controller=await text('controller/deadbyte-controller.ps1');
|
|
39
|
+
assert.match(controller,/Test-Path Env:DEADBYTE_CAPABILITY_PROFILE/);
|
|
40
|
+
assert.match(controller,/\$previousCapabilityProfileEnv = \$env:DEADBYTE_CAPABILITY_PROFILE/);
|
|
41
|
+
assert.match(controller,/\$env:DEADBYTE_CAPABILITY_PROFILE = 'compact'/);
|
|
42
|
+
assert.match(controller,/Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE/);
|
|
43
|
+
assert.match(controller,/\$env:DEADBYTE_CAPABILITY_PROFILE = \$previousCapabilityProfileEnv/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('R36 parity source defines exact full 76-tool authority plus compact gateway closure',async()=>{
|
|
47
|
+
const parity=await text('src/remote-mcp-r36-parity-client.mjs');
|
|
48
|
+
assert.match(parity,/R36_EXPECTED_TOOLS/);
|
|
49
|
+
assert.match(parity,/R36_EXPECTED_TOOLS\.length !== 76|tool_count !== 76|length === 76/);
|
|
50
|
+
for(const name of ['capability_manifest','coding_inspect','machine_fs','process_session','machine_observe']) assert.match(parity,new RegExp(name));
|
|
51
|
+
assert.match(parity,/42/);
|
|
52
|
+
});
|
|
@@ -6,7 +6,7 @@ import { createHash } from 'node:crypto';
|
|
|
6
6
|
const root = new URL('../', import.meta.url);
|
|
7
7
|
const text = rel => readFile(new URL(rel, root), 'utf8');
|
|
8
8
|
|
|
9
|
-
test('
|
|
9
|
+
test('R36 successor release authority agrees on V0.10.0 / R36 full+compact identity', async () => {
|
|
10
10
|
const pkg = JSON.parse(await text('package.json'));
|
|
11
11
|
const [version, controller, gate, readme, architecture, controllerReadme, deferred, parity, closure, closureVerify, windowsGate, semanticConfig] = await Promise.all([
|
|
12
12
|
text('src/version.mjs'),
|
|
@@ -16,25 +16,28 @@ test('R35 successor release authority agrees on V0.9.0 / R35 identity', async ()
|
|
|
16
16
|
text('docs/ARCHITECTURE.md'),
|
|
17
17
|
text('controller/README.TXT'),
|
|
18
18
|
text('scripts/deferred-slot-operation.mjs'),
|
|
19
|
-
text('src/remote-mcp-
|
|
19
|
+
text('src/remote-mcp-r36-parity-client.mjs'),
|
|
20
20
|
text('scripts/final-closure.mjs'),
|
|
21
21
|
text('scripts/final-closure-verify.mjs'),
|
|
22
22
|
text('scripts/windows-gate-evidence.mjs'),
|
|
23
23
|
text('controller/deadbyte-semantic-lsp-config.json')
|
|
24
24
|
]);
|
|
25
|
-
assert.equal(pkg.version, '0.
|
|
26
|
-
assert.match(version, /DEADBYTE_VERSION = '0\.
|
|
27
|
-
assert.match(controller, /CONTROLLER V0\.
|
|
28
|
-
assert.match(gate, /DEADBYTE V0\.
|
|
29
|
-
assert.match(readme, /^DEADBYTE MCP V0\.
|
|
30
|
-
assert.match(architecture, /^# DEADBYTE MCP V0\.
|
|
31
|
-
assert.match(controllerReadme, /^DEADBYTE MCP V0\.
|
|
32
|
-
assert.match(controllerReadme, /
|
|
33
|
-
assert.match(
|
|
34
|
-
assert.match(
|
|
35
|
-
assert.match(
|
|
36
|
-
assert.match(
|
|
37
|
-
assert.match(
|
|
25
|
+
assert.equal(pkg.version, '0.10.0');
|
|
26
|
+
assert.match(version, /DEADBYTE_VERSION = '0\.10\.0'/);
|
|
27
|
+
assert.match(controller, /CONTROLLER V0\.10\.0 \/ R36/);
|
|
28
|
+
assert.match(gate, /DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
|
|
29
|
+
assert.match(readme, /^DEADBYTE MCP V0\.10\.0 \/ R36 PRODUCTION BASELINE$/m);
|
|
30
|
+
assert.match(architecture, /^# DEADBYTE MCP V0\.10\.0 \/ R36 ARCHITECTURE$/m);
|
|
31
|
+
assert.match(controllerReadme, /^DEADBYTE MCP V0\.10\.0 \/ R36 CONTROLLER$/m);
|
|
32
|
+
assert.match(controllerReadme, /full 76-tool (?:profile|catalog)/);
|
|
33
|
+
assert.match(controllerReadme, /compact capability profile[^\n]*<=42 tools/);
|
|
34
|
+
assert.match(deferred, /--expected-version','0\.10\.0'/);
|
|
35
|
+
assert.match(parity, /version:'0\.10\.0'/);
|
|
36
|
+
assert.match(parity, /R36_EXPECTED_TOOLS\.length !== 76/);
|
|
37
|
+
assert.match(parity, /R36_COMPACT_TOOL_LIMIT=42/);
|
|
38
|
+
assert.match(closure, /version:'0\.10\.0'/);
|
|
39
|
+
assert.match(closureVerify, /DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
|
|
40
|
+
assert.match(windowsGate, /schema:'deadbyte\.r36-windows-gate\.v1'/);
|
|
38
41
|
assert.equal(createHash('sha256').update(Buffer.from(semanticConfig,'utf8')).digest('hex'), 'd274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
|
|
39
42
|
assert.match(deferred, /--semantic-lsp-config/);
|
|
40
43
|
assert.match(deferred, /--semantic-lsp-config-sha256/);
|
|
@@ -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,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
|
}
|
|
@@ -71,17 +71,23 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
|
|
|
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\] Live activity\. 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,17 +110,19 @@ 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&&!/❌ ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
|
|
114
|
+
assert.match(output.stdout,/▶ RUN coding_tree root=projects path=src/);
|
|
115
|
+
assert.match(output.stdout,/✅ OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:1\]/);
|
|
116
|
+
assert.match(output.stdout,/▶ RUN coding_file_read root=projects path=src\/deadbyte-cli\.mjs/);
|
|
117
|
+
assert.match(output.stdout,/❌ ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:2\]/);
|
|
118
|
+
assert.equal((output.stdout.match(/✅ 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
124
|
assert.doesNotMatch(output.stdout,/abcdef12|fedcba98/);
|
|
117
|
-
assert.doesNotMatch(output.stdout,/
|
|
125
|
+
assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN/);
|
|
118
126
|
assert.doesNotMatch(output.stdout,/BRIDGE\.STDOUT/);
|
|
119
127
|
assert.doesNotMatch(output.stdout,/\[TOOL IN \]|\[TOOL OUT\]/);
|
|
120
128
|
assert.doesNotMatch(output.stdout,/payload bytes=/);
|
|
@@ -124,9 +132,10 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
|
|
|
124
132
|
const commands=await traceCommands(fx.trace);
|
|
125
133
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
126
134
|
assert.match(output.stdout,/^DEADBYTE MCP$/m);
|
|
127
|
-
assert.match(output.stdout,/Live
|
|
135
|
+
assert.match(output.stdout,/Live activity/i);
|
|
136
|
+
assert.match(output.stdout,/🔐 \[AUTH\] Revoking session authority/);
|
|
128
137
|
assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmhost OK|DEADBYTE MCP = PARKED/);
|
|
129
|
-
const withoutApprovedEmoji=output.stdout.replace(/[
|
|
138
|
+
const withoutApprovedEmoji=output.stdout.replace(/[📜📥📤✅❌⚠♻🛡▶🔐🚀🔌]/gu,'');
|
|
130
139
|
assert.doesNotMatch(withoutApprovedEmoji,/\p{Extended_Pictographic}/u,'human live stream may use only the approved semantic emoji set');
|
|
131
140
|
}finally{
|
|
132
141
|
if(child?.exitCode===null) child.kill();
|
|
@@ -142,7 +151,7 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
142
151
|
const firstOutput=await collect(first);
|
|
143
152
|
await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
144
153
|
const liveDeadline=Date.now()+3000;
|
|
145
|
-
while(Date.now()<liveDeadline&&!/\[LOG\] Live
|
|
154
|
+
while(Date.now()<liveDeadline&&!/\[LOG\] Live activity/.test(firstOutput.stdout)) await sleep(50);
|
|
146
155
|
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
147
156
|
const auditFile=path.join(auditDir,'segment-00000001.jsonl');
|
|
148
157
|
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');
|
|
@@ -161,11 +170,12 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
161
170
|
const secondOutput=await collect(second);
|
|
162
171
|
deadline=Date.now()+5000;
|
|
163
172
|
while(Date.now()<deadline&&(!/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/.test(secondOutput.stdout)||
|
|
164
|
-
!/\[LOG\] Live
|
|
173
|
+
!/\[LOG\] Live activity/.test(secondOutput.stdout))) await sleep(50);
|
|
165
174
|
assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/);
|
|
175
|
+
assert.match(secondOutput.stdout,/♻️ \[SESSION\] Previous result stream replayed/);
|
|
166
176
|
assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_tree/);
|
|
167
177
|
const replayIndex=secondOutput.stdout.indexOf('[REPLAY]');
|
|
168
|
-
const liveIndex=secondOutput.stdout.indexOf('[LOG] Live
|
|
178
|
+
const liveIndex=secondOutput.stdout.indexOf('[LOG] Live activity');
|
|
169
179
|
assert.ok(replayIndex>=0&&liveIndex>replayIndex,'resume replay must happen before new live tail');
|
|
170
180
|
second.stdin.end();
|
|
171
181
|
const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
@@ -183,8 +193,8 @@ winTest('remote --verbose preserves detailed audit and bridge log streaming for
|
|
|
183
193
|
try{
|
|
184
194
|
child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
185
195
|
const output=await collect(child);
|
|
186
|
-
const ready=await waitFor(fx.trace,commands=>commands.slice(0,
|
|
187
|
-
assert.equal(ready.length>=
|
|
196
|
+
const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
197
|
+
assert.equal(ready.length>=8,true);
|
|
188
198
|
const liveDeadline=Date.now()+3000;
|
|
189
199
|
while(Date.now()<liveDeadline&&!/Live logs active/.test(output.stdout)) await sleep(50);
|
|
190
200
|
assert.match(output.stdout,/Live logs active/);
|
|
@@ -201,7 +211,7 @@ winTest('remote --verbose preserves detailed audit and bridge log streaming for
|
|
|
201
211
|
'[TOOL OUT] 2026-09-16T00:00:00.001Z #000001 coding_tree {"is_error":false}'
|
|
202
212
|
].join('\n')+'\n','utf8');
|
|
203
213
|
const deadline=Date.now()+3000;
|
|
204
|
-
while(Date.now()<deadline
|
|
214
|
+
while(Date.now()<deadline&&(!/\[AUDIT #2\] tool\.result coding_tree abcdef12/.test(output.stdout)||!/\[BRIDGE\.STDOUT\] \[TOOL OUT\].*coding_tree/.test(output.stdout))) await sleep(50);
|
|
205
215
|
assert.match(output.stdout,/\[AUDIT #1\] tool\.request coding_tree abcdef12/);
|
|
206
216
|
assert.match(output.stdout,/\[AUDIT #2\] tool\.result coding_tree abcdef12/);
|
|
207
217
|
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] \[TOOL IN \].*coding_tree/);
|
|
@@ -222,9 +232,9 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
222
232
|
try{
|
|
223
233
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
224
234
|
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',
|
|
235
|
+
const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
236
|
+
assert.deepEqual(ready.slice(0,8),[
|
|
237
|
+
'start','armprocess','armcoding','armautonomous',
|
|
228
238
|
'processstatus','codingstatus','autonomousstatus','hoststatus'
|
|
229
239
|
]);
|
|
230
240
|
const markerPath=path.join(fx.state,'runtime','remote-session.json');
|
|
@@ -259,7 +269,7 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
259
269
|
});
|
|
260
270
|
|
|
261
271
|
winTest('bare remote rolls back authority and parks if one plane fails to arm',async()=>{
|
|
262
|
-
const fx=await fixture({failCommand:'
|
|
272
|
+
const fx=await fixture({failCommand:'armcoding'});
|
|
263
273
|
try{
|
|
264
274
|
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
265
275
|
const output=await collect(child);
|
|
@@ -267,7 +277,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
|
|
|
267
277
|
assert.equal(ended.timeout,undefined,'failed startup did not terminate');
|
|
268
278
|
assert.notEqual(ended.code,0);
|
|
269
279
|
const commands=await traceCommands(fx.trace);
|
|
270
|
-
assert.deepEqual(commands.slice(0,
|
|
280
|
+
assert.deepEqual(commands.slice(0,3),['start','armprocess','armcoding']);
|
|
271
281
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
272
282
|
assert.doesNotMatch(output.stdout,/Live logs/i);
|
|
273
283
|
}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');
|