deadbyte-mcp 0.15.35 → 0.15.37
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 +91 -46
- package/PROVENANCE.json +76 -30
- package/README.txt +62 -8
- package/RELEASE.json +78 -23
- package/SBOM.cdx.json +10 -25
- package/TEST-MATRIX.json +109 -44
- package/bridge/package.json +4 -2
- package/bridge/src/bridge-health-r59.mjs +53 -0
- package/bridge/src/bridge.mjs +158 -26
- package/bridge/src/canonical-json-r60.mjs +13 -0
- package/bridge/src/r40-governor.mjs +56 -10
- package/bridge/src/reconnect-delay-r60.mjs +31 -0
- package/bridge/src/remote-call-claims-r60.mjs +157 -0
- package/bridge/src/remote-dispatch-r60.mjs +75 -0
- package/controller/deadbyte-controller.ps1 +76 -13
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R59-PROVENANCE.json +87 -0
- package/docs/R59-RELEASE.json +187 -0
- package/docs/R59-SBOM.cdx.json +68 -0
- package/docs/R59-TASK-CONTRACT.md +26 -0
- package/docs/R59-TEST-MATRIX.json +258 -0
- package/docs/R59-bridge-package.json +13 -0
- package/docs/R59-package-lock.json +184 -0
- package/docs/R59-package.json +106 -0
- package/docs/R59-version.mjs +6 -0
- package/docs/R60-TASK-CONTRACT.md +22 -0
- package/package.json +7 -3
- package/proof/r59-full-regression-current.json +13 -0
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +1268 -0
- package/scripts/full-regression-r60.mjs +32 -0
- package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
- package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
- package/scripts/r59-full-regression-capture.mjs +35 -0
- package/src/capability-resolver.mjs +25 -7
- package/src/capability-snapshot.mjs +56 -0
- package/src/compact-gateway-mcp-tools.mjs +10 -11
- package/src/deadbyte-cli.mjs +80 -34
- package/src/mcp-server.mjs +179 -118
- package/src/r40-compact-disarmed-probe.mjs +1 -1
- package/src/r44-compact-disarmed-probe.mjs +1 -1
- package/src/r45-compact-disarmed-probe.mjs +1 -1
- package/src/remote-authority.mjs +56 -0
- package/src/remote-enforcement-probe-r60.mjs +17 -0
- package/src/remote-live-log.mjs +19 -2
- package/src/remote-log-decode-r60.mjs +32 -0
- package/src/remote-mcp-machine-smoke-client.mjs +19 -1
- package/src/remote-mcp-r40-parity-client.mjs +1 -1
- package/src/remote-mcp-r44-parity-client.mjs +1 -1
- package/src/remote-mcp-r45-parity-client.mjs +1 -1
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/remote-session-guardian.mjs +20 -3
- package/src/runtime-plane-registry.mjs +74 -0
- package/src/tool-audit.mjs +5 -0
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +60 -0
- package/test/capability-resolver.test.mjs +16 -0
- package/test/capability-snapshot-r59.test.mjs +39 -0
- package/test/compact-gateway-r36.test.mjs +22 -2
- package/test/compact-max-catalog-r44.test.mjs +1 -1
- package/test/compact-max-catalog-r45.test.mjs +1 -1
- package/test/contained.test.mjs +2 -1
- package/test/controller-policy-preflight-r59.test.mjs +39 -0
- package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
- package/test/observation-integration.test.mjs +2 -3
- package/test/optional-plane-degraded-r59.test.mjs +44 -0
- package/test/r48-release-contract.test.mjs +1 -1
- package/test/r49-windows-gate.test.mjs +1 -1
- package/test/r51-runtime-coherence.test.mjs +2 -2
- package/test/r58-bundled-bridge.test.mjs +3 -1
- package/test/r58-cleanup-retention.test.mjs +9 -3
- package/test/r58-concurrent-recovery.test.mjs +9 -3
- package/test/r58-controller-live-local.test.mjs +36 -4
- package/test/r58-controller-state-coherence.test.mjs +1 -1
- package/test/r58-release-contract.test.mjs +3 -2
- package/test/r58-stale-owner.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +2 -1
- package/test/r59-release-contract.test.mjs +61 -0
- package/test/r59-tool-surface-compat.test.mjs +90 -0
- package/test/r60-authority-coherence.test.mjs +36 -0
- package/test/r60-authority-enforcement.test.mjs +28 -0
- package/test/r60-bridge-dispatch.test.mjs +119 -0
- package/test/r60-clock-discipline.test.mjs +15 -0
- package/test/r60-live-timeline.test.mjs +29 -0
- package/test/r60-reconnect-errors.test.mjs +21 -0
- package/test/r60-reconnect-jitter.test.mjs +18 -0
- package/test/r60-release-contract.test.mjs +81 -0
- package/test/r60-remote-call-recovery.test.mjs +170 -0
- package/test/r60-windows-log-decode.test.mjs +25 -0
- package/test/remote-live-log-r37.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +200 -43
- package/test/runtime-plane-registry-r59.test.mjs +40 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root=path.resolve(here,'..');
|
|
8
|
+
const testDir=path.join(root,'test');
|
|
9
|
+
const excluded=new Set(['full-max-catalog-r44.test.mjs','full-max-catalog-r45.test.mjs','full-max-catalog-r46.test.mjs']);
|
|
10
|
+
const files=(await readdir(testDir,{withFileTypes:true}))
|
|
11
|
+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs')&&!excluded.has(entry.name));
|
|
12
|
+
const weighted=[];
|
|
13
|
+
for(const entry of files) weighted.push({path:path.join('test',entry.name),size:(await stat(path.join(testDir,entry.name))).size});
|
|
14
|
+
weighted.sort((a,b)=>b.size-a.size||a.path.localeCompare(b.path,'en'));
|
|
15
|
+
const batchCount=16;
|
|
16
|
+
const batches=Array.from({length:batchCount},()=>[]);
|
|
17
|
+
weighted.forEach((entry,index)=>batches[index%batchCount].push(entry.path));
|
|
18
|
+
const match=process.argv.slice(2).map(String).find(arg=>/^--batch=\d+$/.test(arg));
|
|
19
|
+
const selected=match?Number(match.slice('--batch='.length)):null;
|
|
20
|
+
if(selected!==null&&(!Number.isInteger(selected)||selected<1||selected>batchCount))throw new Error(`R60 regression batch must be 1..${batchCount}`);
|
|
21
|
+
const indexes=selected===null?[...Array(batchCount).keys()]:[selected-1];
|
|
22
|
+
let total=0;
|
|
23
|
+
for(const index of indexes){
|
|
24
|
+
const batch=batches[index].sort((a,b)=>a.localeCompare(b,'en'));
|
|
25
|
+
console.log(`R60-BOUNDED-REGRESSION batch=${index+1}/${batchCount} files=${batch.length}`);
|
|
26
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...batch],{cwd:root,stdio:'inherit',windowsHide:true,shell:false});
|
|
27
|
+
if(result.error)throw result.error;
|
|
28
|
+
if(result.signal)throw new Error(`R60 regression batch ${index+1} terminated by ${result.signal}`);
|
|
29
|
+
if(result.status!==0)throw new Error(`R60 regression batch ${index+1} failed with exit code ${result.status}`);
|
|
30
|
+
total+=batch.length;
|
|
31
|
+
}
|
|
32
|
+
console.log(`R60-BOUNDED-REGRESSION PASS files=${total} batches=${indexes.length}`);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
Set-StrictMode -Version 3.0
|
|
3
|
+
$root=Split-Path $PSScriptRoot -Parent
|
|
4
|
+
Set-Location -LiteralPath $root
|
|
5
|
+
|
|
6
|
+
function Invoke-NodeTest([string]$Name,[string[]]$Files){
|
|
7
|
+
Write-Host "==== $Name ===="
|
|
8
|
+
& node.exe --test --test-concurrency=1 @Files
|
|
9
|
+
if($LASTEXITCODE -ne 0){ throw "R59 prepublish gate failed at $Name exit=$LASTEXITCODE" }
|
|
10
|
+
}
|
|
11
|
+
function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
|
|
12
|
+
Write-Host "==== $Name ===="
|
|
13
|
+
& npm.cmd @NpmArgs
|
|
14
|
+
if($LASTEXITCODE -ne 0){ throw "R59 prepublish gate failed at $Name exit=$LASTEXITCODE" }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$active=Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.deadbyte-mcp\active-release.json') -Raw | ConvertFrom-Json
|
|
18
|
+
if($active.release_id -ne 'v0.15.35-1b5715b276facbc0'){ throw "R59 predecessor release mismatch: $($active.release_id)" }
|
|
19
|
+
if($active.manifest_sha256 -ne '1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4'){ throw 'R59 predecessor manifest mismatch' }
|
|
20
|
+
if($active.generation_manifest_sha256 -ne '047e73b62d7b7819b84201a221a09b7e647cacf21f82101ebed4fbd7e23fc430'){ throw 'R59 predecessor generation mismatch' }
|
|
21
|
+
|
|
22
|
+
Invoke-NodeTest 'r59-contracts' @(
|
|
23
|
+
'test/capability-snapshot-r59.test.mjs',
|
|
24
|
+
'test/capability-resolver.test.mjs',
|
|
25
|
+
'test/runtime-plane-registry-r59.test.mjs',
|
|
26
|
+
'test/optional-plane-degraded-r59.test.mjs',
|
|
27
|
+
'test/bridge-runtime-r59.test.mjs',
|
|
28
|
+
'test/controller-policy-preflight-r59.test.mjs',
|
|
29
|
+
'test/r59-tool-surface-compat.test.mjs',
|
|
30
|
+
'test/r58-controller-state-coherence.test.mjs'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
$proof=Get-Content -LiteralPath (Join-Path $root 'proof\r59-full-regression-current.json') -Raw | ConvertFrom-Json
|
|
34
|
+
if($proof.schema -ne 'deadbyte.r59-full-regression-capture.v1' -or -not $proof.pass -or $proof.exit_code -ne 0){ throw 'R59 full regression proof is not green' }
|
|
35
|
+
if($proof.stderr_bytes -ne 0){ throw 'R59 full regression stderr is non-empty' }
|
|
36
|
+
|
|
37
|
+
Invoke-Npm 'manifest-write' @('run','manifest:write')
|
|
38
|
+
Invoke-Npm 'manifest-verify' @('run','manifest:verify')
|
|
39
|
+
Invoke-Npm 'doctor' @('run','doctor')
|
|
40
|
+
|
|
41
|
+
Write-Host '==== R59 PREPUBLISH WINDOWS GATE: PASS ===='
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# R60 staged gate revision 3: final pre-package metadata seal identity
|
|
2
|
+
param(
|
|
3
|
+
[ValidateSet('all','preflight','focused','full-batch','manifest','doctor')]
|
|
4
|
+
[string]$Stage='all',
|
|
5
|
+
[ValidateRange(1,16)]
|
|
6
|
+
[int]$Batch=1
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference='Stop'
|
|
10
|
+
Set-StrictMode -Version 3.0
|
|
11
|
+
$root=Split-Path $PSScriptRoot -Parent
|
|
12
|
+
Set-Location -LiteralPath $root
|
|
13
|
+
|
|
14
|
+
function Invoke-NodeTest([string]$Name,[string[]]$Files){
|
|
15
|
+
Write-Host "==== $Name ===="
|
|
16
|
+
& node.exe --test --test-concurrency=1 @Files
|
|
17
|
+
if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at $Name exit=$LASTEXITCODE" }
|
|
18
|
+
}
|
|
19
|
+
function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
|
|
20
|
+
Write-Host "==== $Name ===="
|
|
21
|
+
& npm.cmd @NpmArgs
|
|
22
|
+
if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at $Name exit=$LASTEXITCODE" }
|
|
23
|
+
}
|
|
24
|
+
function Assert-R60Predecessor {
|
|
25
|
+
$active=Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.deadbyte-mcp\active-release.json') -Raw | ConvertFrom-Json
|
|
26
|
+
if($active.release_id -ne 'v0.15.36-090d9db3e0679c0d'){ throw "R60 predecessor release mismatch: $($active.release_id)" }
|
|
27
|
+
if($active.manifest_sha256 -ne '090d9db3e0679c0d4943f3229ecab7b9fcccbe4f767348a568c9f3bb60d1b431'){ throw 'R60 predecessor manifest mismatch' }
|
|
28
|
+
if($active.generation_manifest_sha256 -ne '6134f9f26b680e13ed545da4ab5f3020f411ea2569062fb9c154c8a7e036c9dc'){ throw 'R60 predecessor generation mismatch' }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Assert-R60Predecessor
|
|
32
|
+
if($Stage -eq 'preflight'){
|
|
33
|
+
Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS preflight ===='
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if($Stage -eq 'all' -or $Stage -eq 'focused'){
|
|
38
|
+
Invoke-Npm 'r60-focused' @('run','r60:focused:test')
|
|
39
|
+
if($Stage -eq 'focused'){
|
|
40
|
+
Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS focused ===='
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if($Stage -eq 'full-batch'){
|
|
46
|
+
Write-Host "==== r60-full-regression-batch-$Batch ===="
|
|
47
|
+
& node.exe scripts/full-regression-r60.mjs "--batch=$Batch"
|
|
48
|
+
if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at full-batch-$Batch exit=$LASTEXITCODE" }
|
|
49
|
+
Write-Host "==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS full-batch-$Batch ===="
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if($Stage -eq 'all'){
|
|
54
|
+
Invoke-Npm 'r60-full-regression' @('run','r60:full:test')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if($Stage -eq 'all' -or $Stage -eq 'manifest'){
|
|
58
|
+
Invoke-Npm 'manifest-write' @('run','manifest:write')
|
|
59
|
+
Invoke-Npm 'manifest-verify' @('run','manifest:verify')
|
|
60
|
+
if($Stage -eq 'manifest'){
|
|
61
|
+
Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS manifest ===='
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if($Stage -eq 'all' -or $Stage -eq 'doctor'){
|
|
67
|
+
Invoke-Npm 'doctor' @('run','doctor')
|
|
68
|
+
if($Stage -eq 'doctor'){
|
|
69
|
+
Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS doctor ===='
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Write-Host '==== R60 PREPUBLISH WINDOWS GATE: PASS ===='
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root=path.resolve(here,'..');
|
|
9
|
+
const proofDir=path.join(root,'proof');
|
|
10
|
+
await mkdir(proofDir,{recursive:true});
|
|
11
|
+
const started=new Date().toISOString();
|
|
12
|
+
const child=spawnSync(process.execPath,[path.join(root,'scripts','full-regression-r48.mjs')],{
|
|
13
|
+
cwd:root,encoding:'utf8',windowsHide:true,shell:false,maxBuffer:64*1024*1024
|
|
14
|
+
});
|
|
15
|
+
const stdout=child.stdout??'';
|
|
16
|
+
const stderr=child.stderr??'';
|
|
17
|
+
const sha=s=>createHash('sha256').update(s,'utf8').digest('hex');
|
|
18
|
+
const result={
|
|
19
|
+
schema:'deadbyte.r59-full-regression-capture.v1',
|
|
20
|
+
started_at_utc:started,
|
|
21
|
+
finished_at_utc:new Date().toISOString(),
|
|
22
|
+
exit_code:child.status,
|
|
23
|
+
signal:child.signal??null,
|
|
24
|
+
error:child.error?String(child.error.message??child.error):null,
|
|
25
|
+
stdout_sha256:sha(stdout),
|
|
26
|
+
stderr_sha256:sha(stderr),
|
|
27
|
+
stdout_bytes:Buffer.byteLength(stdout),
|
|
28
|
+
stderr_bytes:Buffer.byteLength(stderr),
|
|
29
|
+
pass:child.status===0&&!child.signal&&!child.error
|
|
30
|
+
};
|
|
31
|
+
await writeFile(path.join(proofDir,'r59-full-regression-current.stdout.log'),stdout,'utf8');
|
|
32
|
+
await writeFile(path.join(proofDir,'r59-full-regression-current.stderr.log'),stderr,'utf8');
|
|
33
|
+
await writeFile(path.join(proofDir,'r59-full-regression-current.json'),JSON.stringify(result,null,2)+'\n','utf8');
|
|
34
|
+
console.log(JSON.stringify(result));
|
|
35
|
+
if(!result.pass) process.exitCode=1;
|
|
@@ -146,7 +146,8 @@ export async function resolveCapabilityStates({
|
|
|
146
146
|
desktopPolicy = null,
|
|
147
147
|
autonomousRuntime = null,
|
|
148
148
|
skillRuntime = null,
|
|
149
|
-
hostPolicy = null
|
|
149
|
+
hostPolicy = null,
|
|
150
|
+
runtimePlanes = null
|
|
150
151
|
} = {}) {
|
|
151
152
|
const capabilities = ledger?.entries ?? [];
|
|
152
153
|
const resolved = [];
|
|
@@ -201,9 +202,26 @@ export async function resolveCapabilityStates({
|
|
|
201
202
|
let state = CAPABILITY_STATES.AVAILABLE;
|
|
202
203
|
let ready = true;
|
|
203
204
|
let diagnostic = null;
|
|
205
|
+
const livePlane = cap.runtime_flag && runtimePlanes && typeof runtimePlanes === 'object'
|
|
206
|
+
? runtimePlanes[cap.runtime_flag] ?? null
|
|
207
|
+
: null;
|
|
204
208
|
|
|
205
|
-
// 1.
|
|
206
|
-
|
|
209
|
+
// 1. A live plane overlay may only reduce availability declared by the
|
|
210
|
+
// signed ledger. It cannot create a capability or bypass later authority
|
|
211
|
+
// checks.
|
|
212
|
+
if (livePlane && livePlane.ready !== true) {
|
|
213
|
+
const observed=String(livePlane.state??'UNAVAILABLE');
|
|
214
|
+
state=observed==='DEGRADED'?CAPABILITY_STATES.DEGRADED:
|
|
215
|
+
observed==='DISABLED'?CAPABILITY_STATES.DISABLED:CAPABILITY_STATES.UNAVAILABLE;
|
|
216
|
+
ready=false;
|
|
217
|
+
diagnostic={
|
|
218
|
+
code:String(livePlane.diagnostic?.code??`RUNTIME_PLANE_${observed}`).slice(0,128),
|
|
219
|
+
reason:String(livePlane.diagnostic?.reason??`Runtime plane ${cap.runtime_flag} is ${observed}`).slice(0,1024),
|
|
220
|
+
recoverable:livePlane.diagnostic?.recoverable===true
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// 2. Runtime Flag Disabled check
|
|
224
|
+
else if (cap.runtime_flag && env[cap.runtime_flag] === '0') {
|
|
207
225
|
state = CAPABILITY_STATES.DISABLED;
|
|
208
226
|
ready = false;
|
|
209
227
|
diagnostic = {
|
|
@@ -212,7 +230,7 @@ export async function resolveCapabilityStates({
|
|
|
212
230
|
recoverable: false
|
|
213
231
|
};
|
|
214
232
|
}
|
|
215
|
-
//
|
|
233
|
+
// 3. Tool presence check
|
|
216
234
|
else if (!catalogSet.has(activeTool)) {
|
|
217
235
|
state = CAPABILITY_STATES.UNAVAILABLE;
|
|
218
236
|
ready = false;
|
|
@@ -222,7 +240,7 @@ export async function resolveCapabilityStates({
|
|
|
222
240
|
recoverable: false
|
|
223
241
|
};
|
|
224
242
|
}
|
|
225
|
-
//
|
|
243
|
+
// 4. Subsystem runtime instantiation check
|
|
226
244
|
else if (cap.id.startsWith('machine_fs.') && !machineFsRuntime) {
|
|
227
245
|
state = CAPABILITY_STATES.UNAVAILABLE;
|
|
228
246
|
ready = false;
|
|
@@ -256,7 +274,7 @@ export async function resolveCapabilityStates({
|
|
|
256
274
|
recoverable: true
|
|
257
275
|
};
|
|
258
276
|
}
|
|
259
|
-
//
|
|
277
|
+
// 5. Session Authority Checks
|
|
260
278
|
else if (requires.includes('coding_session.armed') && !codingStatus?.armed) {
|
|
261
279
|
state = CAPABILITY_STATES.DENIED;
|
|
262
280
|
ready = false;
|
|
@@ -298,7 +316,7 @@ export async function resolveCapabilityStates({
|
|
|
298
316
|
recoverable: true
|
|
299
317
|
};
|
|
300
318
|
}
|
|
301
|
-
//
|
|
319
|
+
// 6. Subsystem Health Probes (e.g. Skill Plane envelope verification)
|
|
302
320
|
else if (cap.id === 'skill.catalog') {
|
|
303
321
|
if (skillListResult?.invalid_skills && skillListResult.invalid_skills.length > 0) {
|
|
304
322
|
state = CAPABILITY_STATES.DEGRADED;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
4
|
+
function canonicalJson(value){
|
|
5
|
+
if(value===null||typeof value!=='object') return JSON.stringify(value);
|
|
6
|
+
if(Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
7
|
+
return `{${Object.keys(value).sort((a,b)=>a.localeCompare(b,'en')).map(key=>`${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
8
|
+
}
|
|
9
|
+
const sha=value=>createHash('sha256').update(canonicalJson(value),'utf8').digest('hex');
|
|
10
|
+
function iso(ms){return new Date(ms).toISOString();}
|
|
11
|
+
function boundedPlaneState(raw){
|
|
12
|
+
const diagnostic=raw?.diagnostic&&typeof raw.diagnostic==='object'?{
|
|
13
|
+
code:String(raw.diagnostic.code??'UNKNOWN').slice(0,128),
|
|
14
|
+
reason:String(raw.diagnostic.reason??'').replace(/[\r\n\t]+/g,' ').slice(0,512),
|
|
15
|
+
recoverable:raw.diagnostic.recoverable===true
|
|
16
|
+
}:null;
|
|
17
|
+
return Object.freeze({state:String(raw?.state??'UNAVAILABLE'),ready:raw?.ready===true,
|
|
18
|
+
policy_sha256:HEX64.test(String(raw?.policy_sha256??''))?raw.policy_sha256:null,diagnostic});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCapabilitySnapshotTracker({clock=Date.now,ttlMs=5000}={}){
|
|
22
|
+
if(typeof clock!=='function'||!Number.isInteger(ttlMs)||ttlMs<1000||ttlMs>15000) throw new Error('capability snapshot tracker options invalid');
|
|
23
|
+
let lastFingerprint=null,stateEpoch=0,lastSnapshotId=null;
|
|
24
|
+
function snapshot({profile,ledger,toolCatalog,catalogSha256,resolution,bindings={},planes={}}){
|
|
25
|
+
if(!ledger||!HEX64.test(String(ledger.ledger_sha256??''))||!Array.isArray(ledger.entries)) throw new Error('capability snapshot ledger invalid');
|
|
26
|
+
if(!Array.isArray(toolCatalog)||toolCatalog.some(name=>typeof name!=='string')||new Set(toolCatalog).size!==toolCatalog.length) throw new Error('capability snapshot catalog invalid');
|
|
27
|
+
if(!resolution||!Array.isArray(resolution.capabilities)) throw new Error('capability snapshot resolution invalid');
|
|
28
|
+
const signedIds=new Set(ledger.entries.map(entry=>entry.id));
|
|
29
|
+
for(const capability of resolution.capabilities) if(!signedIds.has(capability.id)) throw new Error(`capability '${capability.id}' is outside signed ledger`);
|
|
30
|
+
const safePlanes=Object.freeze(Object.fromEntries(Object.entries(planes).sort(([a],[b])=>a.localeCompare(b,'en')).map(([id,value])=>[id,boundedPlaneState(value)])));
|
|
31
|
+
const capabilities=resolution.capabilities.map(capability=>Object.freeze({...capability,
|
|
32
|
+
selected_route:Object.freeze({tool:capability.tool,operations:[...(capability.operations??[])]})
|
|
33
|
+
}));
|
|
34
|
+
const safeBindings={
|
|
35
|
+
release_id:typeof bindings.release_id==='string'?bindings.release_id:null,
|
|
36
|
+
release_manifest_sha256:HEX64.test(String(bindings.release_manifest_sha256??''))?bindings.release_manifest_sha256:null,
|
|
37
|
+
generation_manifest_sha256:HEX64.test(String(bindings.generation_manifest_sha256??''))?bindings.generation_manifest_sha256:null
|
|
38
|
+
};
|
|
39
|
+
const body={profile,ledger_sha256:ledger.ledger_sha256,tool_catalog:[...toolCatalog],tool_count:toolCatalog.length,
|
|
40
|
+
catalog_sha256:catalogSha256,capabilities,effective_capabilities:[...(resolution.effective_capabilities??[])],
|
|
41
|
+
unavailable_capabilities:[...(resolution.unavailable_capabilities??[])],degraded_capabilities:[...(resolution.degraded_capabilities??[])],
|
|
42
|
+
bindings:safeBindings,planes:safePlanes,
|
|
43
|
+
// Informational transport contract, not an authority-bearing ledger entry.
|
|
44
|
+
remote_dispatch_contract:Object.freeze({scope:'bridge_ingress_only',schema:'deadbyte.remote-dispatch.v1',
|
|
45
|
+
keyed_mutation:'same stable key and canonical payload: no duplicate dispatch; unknown post-dispatch outcome is not retried',
|
|
46
|
+
read_only_recovery:'a proven read-only interrupted call may re-execute under its original deadline',
|
|
47
|
+
keyless_mutation:'cross-reconnect logical retry safety unavailable; ambiguous outcome is not auto-replayed'})};
|
|
48
|
+
const fingerprint=sha(body);
|
|
49
|
+
if(fingerprint!==lastFingerprint){stateEpoch++;lastFingerprint=fingerprint;lastSnapshotId=sha({fingerprint,state_epoch:stateEpoch});}
|
|
50
|
+
const observed=Number(clock());
|
|
51
|
+
if(!Number.isFinite(observed)) throw new Error('capability snapshot clock invalid');
|
|
52
|
+
return Object.freeze({schema:'deadbyte.capability-manifest.v3',...body,snapshot_id:lastSnapshotId,state_epoch:stateEpoch,
|
|
53
|
+
observed_at_utc:iso(observed),valid_until_utc:iso(observed+ttlMs)});
|
|
54
|
+
}
|
|
55
|
+
return Object.freeze({snapshot});
|
|
56
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as z from 'zod/v4';
|
|
|
3
3
|
import { compactGatewayOutputSchema, compactGatewayResult } from './compact-gateway-output-contracts.mjs';
|
|
4
4
|
import { desktopControlInputSchema,invokeDesktopOperation } from './desktop-mcp-tools-r40.mjs';
|
|
5
5
|
import { resolveCapabilityStates } from './capability-resolver.mjs';
|
|
6
|
+
import { createCapabilitySnapshotTracker } from './capability-snapshot.mjs';
|
|
6
7
|
|
|
7
8
|
const id=z.string().regex(/^[A-Za-z0-9._-]{1,64}$/);
|
|
8
9
|
const rel=z.string().min(1).max(2048);
|
|
@@ -71,7 +72,7 @@ function unavailable(op,name){return compactGatewayResult({status:'unavailable',
|
|
|
71
72
|
async function invoke(op,runtime,name,call){
|
|
72
73
|
if(!runtime) return unavailable(op,name);
|
|
73
74
|
try{return compactGatewayResult({status:'ok',op,result:await call(runtime)},false);}
|
|
74
|
-
catch(error){return compactGatewayResult({status:'denied',op,error:String(error)},true);}
|
|
75
|
+
catch(error){return compactGatewayResult({status:error?.code==='DEADBYTE_RUNTIME_UNAVAILABLE'?'unavailable':'denied',op,error:String(error)},true);}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
async function codingInspect(args,codingRuntime,semanticRuntime){
|
|
@@ -138,27 +139,25 @@ async function machineObserve(args,machineRuntime,observationRuntime){
|
|
|
138
139
|
return invoke(args.op,observationRuntime,'observation',map[args.op]);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime,semanticRuntime,machineFsRuntime,processRuntime,observationRuntime,machineRuntime,desktopRuntime,skillRuntime,autonomousRuntime,hostPolicy,env=process.env,catalogProvider}){
|
|
142
|
+
export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime,semanticRuntime,machineFsRuntime,processRuntime,observationRuntime,machineRuntime,desktopRuntime,skillRuntime,autonomousRuntime,hostPolicy,env=process.env,catalogProvider,runtimePlanes=null,releaseBindingProvider=null}){
|
|
142
143
|
if(!server) throw new Error('compact gateway registration requires server');
|
|
143
144
|
if(profile!=='compact') return;
|
|
144
145
|
if(typeof catalogProvider!=='function') throw new Error('compact gateway requires catalogProvider');
|
|
146
|
+
const capabilitySnapshots=createCapabilitySnapshotTracker({ttlMs:5000});
|
|
145
147
|
server.registerTool('capability_manifest',{description:'Read the canonical capability ledger and exact active compact tool catalog with runtime-aware resolution.',inputSchema:capabilityManifestInputSchema,outputSchema:compactGatewayOutputSchema},async()=>{
|
|
146
148
|
const toolCatalog=[...catalogProvider()].map(String).sort();
|
|
147
149
|
if(toolCatalog.length<1||new Set(toolCatalog).size!==toolCatalog.length) throw new Error('compact catalog provider returned invalid catalog');
|
|
148
150
|
const catalogSha256=createHash('sha256').update(JSON.stringify(toolCatalog),'utf8').digest('hex');
|
|
151
|
+
const livePlanes=typeof runtimePlanes==='function'?await runtimePlanes():runtimePlanes;
|
|
149
152
|
const dynamicResolution = await resolveCapabilityStates({
|
|
150
153
|
ledger, toolCatalog, profile, env,
|
|
151
154
|
codingRuntime, processRuntime, machineFsRuntime, observationRuntime, desktopRuntime,
|
|
152
|
-
autonomousRuntime, skillRuntime, hostPolicy
|
|
155
|
+
autonomousRuntime, skillRuntime, hostPolicy, runtimePlanes:livePlanes
|
|
153
156
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
effective_capabilities:dynamicResolution.effective_capabilities,
|
|
159
|
-
unavailable_capabilities:dynamicResolution.unavailable_capabilities,
|
|
160
|
-
degraded_capabilities:dynamicResolution.degraded_capabilities
|
|
161
|
-
}},false);
|
|
157
|
+
const bindings=typeof releaseBindingProvider==='function'?await releaseBindingProvider():{};
|
|
158
|
+
return compactGatewayResult({status:'ok',op:'manifest',result:capabilitySnapshots.snapshot({
|
|
159
|
+
profile,ledger,toolCatalog,catalogSha256,resolution:dynamicResolution,bindings,planes:livePlanes??{}
|
|
160
|
+
})},false);
|
|
162
161
|
});
|
|
163
162
|
server.registerTool('coding_inspect',{description:'Typed compact coding inspection and semantic navigation gateway. No arbitrary tool dispatch.',inputSchema:codingInspectInputSchema,outputSchema:compactGatewayOutputSchema},args=>codingInspect(args,codingRuntime,semanticRuntime));
|
|
164
163
|
server.registerTool('machine_fs',{description:'Typed compact machine filesystem gateway preserving root policy and mutation preconditions.',inputSchema:machineFsGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineFs(args,machineFsRuntime));
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -20,6 +20,8 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
|
20
20
|
import { resolveStateRoot } from './state-root.mjs';
|
|
21
21
|
import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
|
|
22
22
|
import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
|
|
23
|
+
import { AUTHORITY_PLANES, authorityVector, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
|
|
24
|
+
import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
|
|
23
25
|
|
|
24
26
|
const REMOTE_COMMANDS=new Set([
|
|
25
27
|
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
@@ -27,17 +29,9 @@ const REMOTE_COMMANDS=new Set([
|
|
|
27
29
|
'armdesktop','disarmdesktop','desktopstatus',
|
|
28
30
|
'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
|
|
29
31
|
]);
|
|
30
|
-
const SESSION_ARM=['armprocess','armcoding','armautonomous'];
|
|
31
32
|
const REMOTE_STATUS_TIMEOUT_MS=30000;
|
|
32
33
|
const REMOTE_START_TIMEOUT_MS=150000;
|
|
33
|
-
const
|
|
34
|
-
['processstatus',/^Process plane\s*:\s*ARMED\b/m],
|
|
35
|
-
['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
|
|
36
|
-
['autonomousstatus',/^Autonomous\s*:\s*ARMED\b/m],
|
|
37
|
-
['desktopstatus',/^Desktop plane\s*:\s*DISARMED\b/m],
|
|
38
|
-
['hoststatus',/^Host plane\s*:\s*DISARMED\b/m]
|
|
39
|
-
];
|
|
40
|
-
const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
|
|
34
|
+
const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
|
|
41
35
|
const SUPERVISOR_COMMANDS=Object.freeze({
|
|
42
36
|
start:'supervisorstart',stop:'supervisorstop',status:'supervisorstatus',recover:'supervisorrecover'
|
|
43
37
|
});
|
|
@@ -55,6 +49,9 @@ function printHelp(){
|
|
|
55
49
|
` npx -y ${exact} doctor # verify current package/release truth`,
|
|
56
50
|
` npx -y ${exact} mcp # stdio MCP server for local clients`,
|
|
57
51
|
` npx -y ${exact} remote # unified foreground device session + live timeline`,
|
|
52
|
+
` npx -y ${exact} remote --desktop # session with Desktop authority; Host remains disarmed`,
|
|
53
|
+
` npx -y ${exact} remote --host # session with Host authority; Desktop remains disarmed`,
|
|
54
|
+
` npx -y ${exact} remote --full # explicit five-plane foreground session`,
|
|
58
55
|
` npx -y ${exact} remote --resume <cursor> # replay missed results, then reconnect`,
|
|
59
56
|
` npx -y ${exact} remote --trace # show trace/span identity without payload detail`,
|
|
60
57
|
` npx -y ${exact} remote --filter <expr> # presentation filter: tool/category/status/trace`,
|
|
@@ -75,8 +72,7 @@ async function remoteContext(){
|
|
|
75
72
|
return {root,launcher,powershell};
|
|
76
73
|
}
|
|
77
74
|
function boundedSingleLine(value,maxChars=2048){
|
|
78
|
-
|
|
79
|
-
return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
|
|
75
|
+
return boundedRemoteDiagnostic(value,maxChars);
|
|
80
76
|
}
|
|
81
77
|
async function readActiveRuntimeBinding(root){
|
|
82
78
|
const file=path.join(root,'active-release.json');
|
|
@@ -158,11 +154,16 @@ async function readTailBounded(file,maxBytes=4096){
|
|
|
158
154
|
if(length===0) return '';
|
|
159
155
|
const buffer=Buffer.alloc(length);
|
|
160
156
|
const handle=await open(file,'r');
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
let encoding=null;
|
|
158
|
+
try{
|
|
159
|
+
const header=Buffer.alloc(2);await handle.read(header,0,2,0);
|
|
160
|
+
if(header[0]===0xff&&header[1]===0xfe)encoding='utf16le';
|
|
161
|
+
await handle.read(buffer,0,length,info.size-length);
|
|
162
|
+
}finally{await handle.close();}
|
|
163
|
+
return boundedSingleLine(decodeRemoteLogBytes(file,buffer,{encoding}),maxBytes);
|
|
163
164
|
}catch(error){if(error?.code==='ENOENT') return '';throw error;}
|
|
164
165
|
}
|
|
165
|
-
function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null}={}){
|
|
166
|
+
function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null,json=false,probeMode=null}={}){
|
|
166
167
|
if(capture&&discardOutput) throw new Error('invokeController cannot capture and discard output together');
|
|
167
168
|
if(capture&&redirectOutput) throw new Error('invokeController cannot capture and redirect output together');
|
|
168
169
|
if(discardOutput&&redirectOutput) throw new Error('invokeController cannot discard and redirect output together');
|
|
@@ -175,9 +176,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
|
|
|
175
176
|
stderrFd=openSync(redirectOutput.stderr_path,'w');
|
|
176
177
|
stdio=['ignore',stdoutFd,stderrFd];
|
|
177
178
|
}else stdio=capture?['ignore','pipe','pipe']:'inherit';
|
|
178
|
-
const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
|
|
179
|
+
const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
|
|
180
|
+
env:windowsPowerShellEnvironment(probeMode?{...process.env,DEADBYTE_REMOTE_PROBE_MODE:probeMode}:process.env)};
|
|
179
181
|
if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
|
|
180
|
-
result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
|
|
182
|
+
result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command,...(json?['-Json']:[])],options);
|
|
181
183
|
}finally{
|
|
182
184
|
if(stdoutFd!==null) closeSync(stdoutFd);
|
|
183
185
|
if(stderrFd!==null) closeSync(stderrFd);
|
|
@@ -193,6 +195,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
|
|
|
193
195
|
if(status!==0&&!allowFailure) throw new Error(`remote controller '${command}' failed exit=${status}`);
|
|
194
196
|
return {status,stdout,stderr};
|
|
195
197
|
}
|
|
198
|
+
function observedAuthority(ctx){
|
|
199
|
+
const result=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS,json:true});
|
|
200
|
+
return parseAuthorityObservation(result.stdout);
|
|
201
|
+
}
|
|
196
202
|
function controllerRuntimeReady(text){
|
|
197
203
|
const value=String(text||'');
|
|
198
204
|
return /^DEADBYTE MCP\s*=\s*ONLINE\s*$/m.test(value)&&
|
|
@@ -278,13 +284,23 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
|
|
|
278
284
|
async function readAppend(file,offset){
|
|
279
285
|
let info;try{info=await stat(file);}catch(error){if(error?.code==='ENOENT') return {offset:0,text:''};throw error;}
|
|
280
286
|
let start=offset;
|
|
281
|
-
|
|
287
|
+
const reset=info.size<start;
|
|
288
|
+
if(reset) start=0;
|
|
282
289
|
if(info.size===start) return {offset:start,text:''};
|
|
283
|
-
const length=info.size-start;
|
|
284
|
-
const buffer=Buffer.alloc(length);
|
|
285
290
|
const handle=await open(file,'r');
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
let encoding=null,buffer,bytesRead=0;
|
|
292
|
+
try{
|
|
293
|
+
const header=Buffer.alloc(2);await handle.read(header,0,2,0);
|
|
294
|
+
if(header[0]===0xff&&header[1]===0xfe&&/\.log$/i.test(file)){
|
|
295
|
+
encoding='utf16le';if(start%2!==0)start=Math.max(0,start-1);
|
|
296
|
+
}
|
|
297
|
+
const length=Math.min(1024*1024,info.size-start);
|
|
298
|
+
if(length===0)return {offset:start,text:''};
|
|
299
|
+
buffer=Buffer.alloc(length);
|
|
300
|
+
({bytesRead}=await handle.read(buffer,0,length,start));
|
|
301
|
+
}finally{await handle.close();}
|
|
302
|
+
const complete=safeRemoteLogPrefixLength(buffer.subarray(0,bytesRead),encoding??'utf8');
|
|
303
|
+
return {offset:start+complete,text:decodeRemoteLogBytes(file,buffer.subarray(0,complete),{encoding}),reset};
|
|
288
304
|
}
|
|
289
305
|
async function snapshotRemoteLogOffsets(root){
|
|
290
306
|
const offsets=new Map();
|
|
@@ -314,6 +330,7 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
314
330
|
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
|
|
315
331
|
: '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
|
|
316
332
|
const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
|
|
333
|
+
const partialLines=new Map();
|
|
317
334
|
let liveFiles=[...activeOffsets.keys()];
|
|
318
335
|
let nextDiscoveryAt=0;
|
|
319
336
|
while(!signal.aborted){
|
|
@@ -326,7 +343,13 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
326
343
|
const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
|
|
327
344
|
const next=await readAppend(file,prior);
|
|
328
345
|
activeOffsets.set(file,next.offset);
|
|
329
|
-
|
|
346
|
+
if(next.reset)partialLines.delete(file);
|
|
347
|
+
if(!next.text)continue;
|
|
348
|
+
const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
|
|
349
|
+
partialLines.set(file,lines.pop()??'');
|
|
350
|
+
if(Buffer.byteLength(partialLines.get(file),'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
|
|
351
|
+
for(const line of lines){
|
|
352
|
+
if(Buffer.byteLength(line,'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
|
|
330
353
|
const auditEvent=parseAuditLogLine(file,line);
|
|
331
354
|
const timeline=auditTimelineRecord(file,line,auditEvent);
|
|
332
355
|
let recorded=null;
|
|
@@ -363,6 +386,15 @@ async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
363
386
|
catch(error){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
|
|
364
387
|
}
|
|
365
388
|
if(failed.length) throw new Error(`remote session cleanup failed; ownership marker retained: ${failed.join(',')}`);
|
|
389
|
+
let snapshot;
|
|
390
|
+
try{snapshot=assertAuthorityVector(observedAuthority(ctx),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));}
|
|
391
|
+
catch(error){throw new Error(`remote session cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
|
|
392
|
+
if(snapshot.state==='ONLINE'){
|
|
393
|
+
try{invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:'revoked'});}
|
|
394
|
+
catch(error){throw new Error(`remote session enforcement cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
|
|
395
|
+
}
|
|
396
|
+
const parked=invokeController(ctx,'park',{allowFailure:true,capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
397
|
+
if(parked.status!==0) throw new Error(`remote session park failed exit=${parked.status}; ownership marker retained`);
|
|
366
398
|
}
|
|
367
399
|
const GUARDIAN_SCRIPT=fileURLToPath(new URL('./remote-session-guardian.mjs',import.meta.url));
|
|
368
400
|
function remoteSessionMarker(root){return path.join(root,'runtime','remote-session.json');}
|
|
@@ -454,7 +486,7 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
|
|
|
454
486
|
const same=current?.schema==='deadbyte.remote-session.v2'&¤t.owner_pid===marker.owner_pid&¤t.session_nonce===marker.session_nonce;
|
|
455
487
|
if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
|
|
456
488
|
}
|
|
457
|
-
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression=''}={}){
|
|
489
|
+
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default'}={}){
|
|
458
490
|
const display=prepareRemoteConsole();
|
|
459
491
|
const displayMode=display.mode;
|
|
460
492
|
const ctx=await remoteContext();
|
|
@@ -524,21 +556,31 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
524
556
|
}
|
|
525
557
|
process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
|
|
526
558
|
process.stdout.write(`${formatRemoteLifecycle('cloud_connected',{displayMode})}\n`);
|
|
527
|
-
|
|
559
|
+
const desired=authorityVector(mode);
|
|
560
|
+
const priorAuthority=observedAuthority(ctx);
|
|
561
|
+
for(const plane of AUTHORITY_PLANES.filter(name=>!desired[name]&&priorAuthority.authority[name].armed)){
|
|
562
|
+
const command=`disarm${plane}`;
|
|
563
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Revoking unwanted ${plane} authority...\n`,displayMode);
|
|
564
|
+
invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
565
|
+
}
|
|
566
|
+
for(const plane of AUTHORITY_PLANES.filter(name=>desired[name])){
|
|
567
|
+
const command=`arm${plane}`;
|
|
528
568
|
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Arming ${command.slice(3)}...\n`,displayMode);
|
|
529
569
|
invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
530
570
|
}
|
|
531
571
|
const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
|
|
572
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying exact ${mode} authority vector...\n`,displayMode);
|
|
573
|
+
const current=await readActiveRuntimeBinding(ctx.root);
|
|
574
|
+
const authority=assertAuthorityVector(assertRuntimeReadyObservation(observedAuthority(ctx),{
|
|
575
|
+
coreRoot:current?.release_root??null
|
|
576
|
+
}),desired);
|
|
577
|
+
process.stdout.write('[BOOT] Verifying public signed MCP execution path...\n');
|
|
578
|
+
invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:mode});
|
|
579
|
+
ready=true;
|
|
580
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ${authorityBanner(authority)}\n`,displayMode);
|
|
532
581
|
process.stdout.write('[OK] ONLINE\n');
|
|
533
582
|
process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
|
|
534
583
|
if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
|
|
535
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; desktop + host must stay DISARMED...\n`,displayMode);
|
|
536
|
-
for(const [command,pattern] of SESSION_STATUS){
|
|
537
|
-
const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
|
|
538
|
-
if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
|
|
539
|
-
}
|
|
540
|
-
ready=true;
|
|
541
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | desktop, host DISARMED\n`,displayMode);
|
|
542
584
|
await logTask;
|
|
543
585
|
}finally{
|
|
544
586
|
const wasAborted=aborter.signal.aborted;
|
|
@@ -556,12 +598,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
556
598
|
}
|
|
557
599
|
}
|
|
558
600
|
async function runRemote(args){
|
|
559
|
-
let verbose=false,trace=false,filterExpression='',resumeCursor=null;
|
|
601
|
+
let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default';
|
|
560
602
|
const positional=[];
|
|
561
603
|
for(let index=0;index<args.length;index++){
|
|
562
604
|
const arg=args[index];
|
|
563
605
|
if(arg==='--verbose'||arg==='-v'){verbose=true;continue;}
|
|
564
606
|
if(arg==='--trace'){trace=true;continue;}
|
|
607
|
+
if(['--desktop','--host','--full'].includes(arg)){
|
|
608
|
+
if(mode!=='default') throw new Error('remote authority modes are mutually exclusive');
|
|
609
|
+
mode=arg.slice(2);continue;
|
|
610
|
+
}
|
|
565
611
|
if(arg==='--filter'){
|
|
566
612
|
if(filterExpression) throw new Error('remote --filter may be specified once');
|
|
567
613
|
if(index+1>=args.length) throw new Error('remote --filter requires an expression');
|
|
@@ -574,8 +620,8 @@ async function runRemote(args){
|
|
|
574
620
|
}
|
|
575
621
|
positional.push(arg);
|
|
576
622
|
}
|
|
577
|
-
const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null;
|
|
578
|
-
if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression});return;}
|
|
623
|
+
const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null||mode!=='default';
|
|
624
|
+
if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression,mode});return;}
|
|
579
625
|
if(presentation) throw new Error('remote presentation flags are valid only for foreground session');
|
|
580
626
|
if(positional.length>1) throw new Error('remote accepts one controller command');
|
|
581
627
|
const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
|