deadbyte-mcp 0.15.68 → 0.15.70
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 +85 -38
- package/PROVENANCE.json +52 -24
- package/README.txt +44 -31
- package/RELEASE.json +47 -17
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +44 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +65 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +97 -0
- package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
- package/package.json +1 -1
- package/scripts/install-r186-owner-trust.ps1 +59 -0
- package/scripts/r175-focused-runner.mjs +38 -0
- package/scripts/r176-log-preview.mjs +18 -0
- package/scripts/r177-owner-trust-inventory.ps1 +36 -0
- package/scripts/r178-full-regression.mjs +21 -0
- package/scripts/r178-npm-ci.mjs +10 -0
- package/scripts/r178-npm-preflight.mjs +16 -0
- package/scripts/r178-release-gates-readonly.mjs +9 -0
- package/scripts/r178-shard-regression.mjs +25 -0
- package/scripts/r179-predecessor-registry.mjs +24 -0
- package/scripts/r179-protected-inventory.ps1 +21 -0
- package/scripts/r186-focused-runner.mjs +22 -0
- package/scripts/r186-owner-anchor-inspect.mjs +6 -0
- package/scripts/r186-owner-consent-check.mjs +86 -0
- package/scripts/r186-owner-grant-cli.mjs +45 -0
- package/scripts/r186-owner-key-local.mjs +22 -0
- package/scripts/r186-owner-review-export.mjs +34 -0
- package/src/deadbyte-cli.mjs +130 -56
- package/src/foreground-owner-recovery-r190.mjs +117 -0
- package/src/r174-owner-webauthn.mjs +176 -0
- package/src/r175-foreground-boundary.mjs +28 -0
- package/src/r186-owner-anchor.mjs +12 -0
- package/src/r186-owner-consent-preflight.mjs +63 -0
- package/src/r186-owner-grant-broker.mjs +96 -0
- package/src/r186-owner-local-signing.mjs +25 -0
- package/src/r186-owner-request.mjs +40 -0
- package/src/r186-owner-review-export.mjs +49 -0
- package/src/remote-live-log.mjs +81 -5
- package/src/remote-session-guardian.mjs +23 -0
- package/src/version.mjs +1 -1
- package/test/helpers/r175-active-pointer.mjs +28 -0
- package/test/helpers/release-lock.mjs +12 -0
- package/test/r129-sdk-security-baseline.test.mjs +2 -1
- package/test/r130-release-security-contract.test.mjs +11 -10
- package/test/r132-release-identity.test.mjs +12 -11
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r155-default-live-log.test.mjs +17 -16
- package/test/r160-package-decision-manifest.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +4 -5
- package/test/r163-isolated-slot-rollback-restore.test.mjs +8 -8
- package/test/r165-live-cli-skew.test.mjs +6 -6
- package/test/r167-release-contract.test.mjs +15 -13
- package/test/r168-protected-entrypoint.test.mjs +5 -2
- package/test/r168-release-contract.test.mjs +4 -3
- package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
- package/test/r172-release-contract.test.mjs +7 -7
- package/test/r173-release-contract.test.mjs +18 -17
- package/test/r174-owner-webauthn.test.mjs +144 -0
- package/test/r175-cli-flags.test.mjs +23 -0
- package/test/r175-foreground-boundary.test.mjs +87 -0
- package/test/r175-guardian-owner-boundary.test.mjs +56 -0
- package/test/r176-pretty-live-log.test.mjs +54 -0
- package/test/r177-windows-absolute-live-path.test.mjs +37 -0
- package/test/r179-absolute-path-live-log.test.mjs +40 -0
- package/test/r179-release-contract.test.mjs +62 -0
- package/test/r186-controller-owner-grant.test.mjs +20 -0
- package/test/r186-owner-consent-preflight.test.mjs +98 -0
- package/test/r186-owner-enrollment.test.mjs +20 -0
- package/test/r186-owner-grant-broker.test.mjs +85 -0
- package/test/r186-owner-local-signing.test.mjs +30 -0
- package/test/r186-owner-request.test.mjs +28 -0
- package/test/r186-owner-review-export.test.mjs +52 -0
- package/test/r190-foreground-cli.test.mjs +41 -0
- package/test/r190-foreground-owner-recovery.test.mjs +61 -0
- package/test/r190-release-contract.test.mjs +33 -0
- package/test/r48-release-contract.test.mjs +2 -1
- package/test/r51-runtime-coherence.test.mjs +4 -2
- package/test/r58-cleanup-retention.test.mjs +5 -12
- package/test/r58-concurrent-recovery.test.mjs +5 -1
- package/test/r58-stale-owner.test.mjs +9 -3
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/remote-live-log-r37.test.mjs +2 -2
- package/test/remote-live-log-recovery-r36.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +92 -126
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { DEADBYTE_VERSION } from '../src/version.mjs';
|
|
10
|
+
import {installExactActivePointer} from './helpers/r175-active-pointer.mjs';
|
|
10
11
|
|
|
11
12
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
12
13
|
const cliPath=path.join(root,'src','deadbyte-cli.mjs');
|
|
@@ -35,12 +36,13 @@ async function fixture({failCommand='',statusOnline=false}={}){
|
|
|
35
36
|
const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
|
|
36
37
|
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 -in ($env:DEADBYTE_REMOTE_TEST_FAIL -split ',')) { $runtime=Join-Path $env:DEADBYTE_STATE_ROOT 'runtime'; New-Item -ItemType Directory -Path $runtime -Force | Out-Null; $failure=@{ schema='deadbyte.controller-start-failure.v1'; command=$Command; failed_at_utc='2026-09-26T00:00:00.000Z'; message='fixture controller evidence detail' } | ConvertTo-Json -Compress; [IO.File]::WriteAllText((Join-Path $runtime 'controller-start-failure.json'),$failure,[Text.Encoding]::UTF8); Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { if ($env:DEADBYTE_REMOTE_TEST_SPAWN_CHILD -eq '1') { $node=(Get-Command node.exe).Source; $spawned=Start-Process -FilePath $node -ArgumentList @('-e','setTimeout(()=>process.exit(0),10000)') -PassThru -WindowStyle Hidden -RedirectStandardOutput $env:DEADBYTE_REMOTE_TEST_SPAWN_STDOUT -RedirectStandardError $env:DEADBYTE_REMOTE_TEST_SPAWN_STDERR; if ($env:DEADBYTE_REMOTE_TEST_SPAWN_PID) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_SPAWN_PID,[string]$spawned.Id,[Text.Encoding]::UTF8) } }; if ($env:DEADBYTE_REMOTE_TEST_STARTED) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_STARTED,'1',[Text.Encoding]::UTF8) }; Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1' -or ($env:DEADBYTE_REMOTE_TEST_STARTED -and (Test-Path -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED))) { Write-Host 'DEADBYTE MCP = ONLINE'; Write-Host 'Bridge PID : 1001 [verified]'; Write-Host 'Cloud PID : 1002 [verified]'; Write-Host 'Local ready : True'; Write-Host 'Loop supervisor: RUNNING [verified]' } else { Write-Host 'DEADBYTE MCP = OFFLINE' } }\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 'desktopstatus' { Write-Host 'Desktop plane : DISARMED [disarmed]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { if ($env:DEADBYTE_REMOTE_TEST_STARTED) { Remove-Item -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED -Force -ErrorAction SilentlyContinue }; Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
|
|
37
38
|
const structured=`function PlaneArmed([string]$name,[bool]$default) { $base=Join-Path $env:DEADBYTE_STATE_ROOT ('runtime\\' + $name); if (Test-Path -LiteralPath ($base + '.disarmed')) { return $false }; if (Test-Path -LiteralPath ($base + '.armed')) { return $true }; return $default }
|
|
38
|
-
if ($Command -eq 'status' -and $Json) { $names=@('process','coding','autonomous','desktop','host'); $authority=@{}; foreach($name in $names){ $initial=($name -
|
|
39
|
+
if ($Command -eq 'status' -and $Json) { $names=@('process','coding','autonomous','desktop','host'); $authority=@{}; foreach($name in $names){ $initial=(($name -eq 'process') -and ($env:DEADBYTE_REMOTE_TEST_PROCESS_ARMED -eq '1')) -or (($name -eq 'coding') -and ($env:DEADBYTE_REMOTE_TEST_CODING_ARMED -eq '1')) -or (($name -eq 'autonomous') -and ($env:DEADBYTE_REMOTE_TEST_AUTONOMOUS_ARMED -eq '1')) -or (($name -eq 'host') -and ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1')) -or (($name -eq 'desktop') -and ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1')); $armed=PlaneArmed $name $initial; if($name -eq 'host' -and (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE_ALWAYS -eq '1') -or (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE -eq '1') -and (Test-Path -LiteralPath (Join-Path $env:DEADBYTE_STATE_ROOT 'runtime\\host.disarm-attempt'))))){$armed=$false}; $authority[$name]=@{ configured=$true; armed=$armed; reason=$(if($armed){'standing-grant'}else{'disarmed'}) } }; @{schema='deadbyte.controller-observation.v1';state='ONLINE';local_ready=$true;core_root=$env:DEADBYTE_REMOTE_TEST_CORE_ROOT;bridge=@{alive=$true;reason='verified'};core=@{alive=$true;reason='verified'};cloud=@{alive=$true;reason='verified'};supervisor=@{running=$true};authority=$authority} | ConvertTo-Json -Depth 5 -Compress; exit 0 }
|
|
39
40
|
if ($Command -eq 'machinetest' -and $env:DEADBYTE_REMOTE_PROBE_MODE) { $hostArmed=PlaneArmed 'host' ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1'); $desktopArmed=PlaneArmed 'desktop' ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1'); $wantHost=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('host','full'); $wantDesktop=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('desktop','full'); if($hostArmed -ne $wantHost -or $desktopArmed -ne $wantDesktop){ Write-Error 'remote enforcement probe mismatch'; exit 9 } }
|
|
40
41
|
if ($Command -match '^(arm|disarm)(process|coding|autonomous|desktop|host)$') { $name=$Matches[2]; $base=Join-Path $env:DEADBYTE_STATE_ROOT ('runtime\\' + $name); New-Item -ItemType Directory -Path (Split-Path $base) -Force | Out-Null; if ($Matches[1] -eq 'arm') { [IO.File]::WriteAllText(($base+'.armed'),'1'); Remove-Item -LiteralPath ($base+'.disarmed') -Force -ErrorAction SilentlyContinue } elseif ($env:DEADBYTE_REMOTE_TEST_DISARM_NOOP -ne $name) { [IO.File]::WriteAllText(($base+'.disarmed'),'1'); Remove-Item -LiteralPath ($base+'.armed') -Force -ErrorAction SilentlyContinue } else { [IO.File]::WriteAllText(($base+'.disarm-attempt'),'1') } }
|
|
41
42
|
switch ($Command) {`;
|
|
42
43
|
await writeFile(launcher,ps1.replace('param([string]$Command)','param([string]$Command,[switch]$Json)').replace('switch ($Command) {',()=>structured),'utf8');
|
|
43
|
-
|
|
44
|
+
await installExactActivePointer({stateRoot:state,packageRoot:root,launcherPath:launcher});
|
|
45
|
+
return {dir,state,trace,env:{...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_REMOTE_TEST_CORE_ROOT:root,DEADBYTE_REMOTE_TEST_TRACE:trace,DEADBYTE_REMOTE_TEST_FAIL:failCommand,DEADBYTE_REMOTE_TEST_STATUS_ONLINE:statusOnline?'1':'0',DEADBYTE_REMOTE_TEST_STARTED:startedMarker,DEADBYTE_REMOTE_TEST_SPAWN_STDOUT:spawnedStdout,DEADBYTE_REMOTE_TEST_SPAWN_STDERR:spawnedStderr,DEADBYTE_REMOTE_TEST_SPAWN_PID:spawnedPid},spawnedPid};
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
async function traceCommands(file,{read=readFile,retryDelayMs=10,maxAttempts=20}={}){
|
|
@@ -98,21 +100,23 @@ const winTest=isWindows?test:test.skip;
|
|
|
98
100
|
|
|
99
101
|
winTest('bare remote reuses a fully verified ONLINE runtime without invoking blocking start',async()=>{
|
|
100
102
|
const fx=await fixture({failCommand:'start',statusOnline:true});
|
|
103
|
+
fx.env.DEADBYTE_REMOTE_TEST_PROCESS_ARMED='1';
|
|
104
|
+
fx.env.DEADBYTE_REMOTE_TEST_CODING_ARMED='1';
|
|
105
|
+
fx.env.DEADBYTE_REMOTE_TEST_AUTONOMOUS_ARMED='1';
|
|
101
106
|
let child=null;
|
|
102
107
|
try{
|
|
103
|
-
child=spawn(process.execPath,[cliPath,'remote'
|
|
108
|
+
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
104
109
|
const output=await collect(child);
|
|
105
|
-
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=
|
|
106
|
-
assert.
|
|
107
|
-
assert.doesNotMatch(ready.join(','),/(^|,)start(,|$)/);
|
|
110
|
+
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=2,{timeoutMs:fixtureTimeoutMs});
|
|
111
|
+
assert.equal(ready.filter(x=>/^(?:arm|disarm|start|park)/.test(x)).length,0,output.stderr);
|
|
108
112
|
assert.equal(child.exitCode,null,'remote session must remain foreground after reusing ONLINE runtime');
|
|
109
113
|
assert.match(output.stdout,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
|
|
110
114
|
const authDeadline=Date.now()+fixtureTimeoutMs;
|
|
111
115
|
while(Date.now()<authDeadline&&!/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/.test(output.stdout)) await sleep(50);
|
|
112
116
|
assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
|
|
113
117
|
assert.match(output.stdout,/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/);
|
|
114
|
-
child.
|
|
115
|
-
await output.closed;
|
|
118
|
+
child.stdin.end();
|
|
119
|
+
assert.equal((await output.closed).code,0,output.stderr);
|
|
116
120
|
}finally{
|
|
117
121
|
if(child&&child.exitCode===null) child.kill('SIGKILL');
|
|
118
122
|
await safeRm(fx.dir);
|
|
@@ -130,28 +134,28 @@ winTest('R72 bare remote reaches READY with every authority disarmed and never a
|
|
|
130
134
|
assert.match(output.stdout,/\[READY\]/,output.stderr);
|
|
131
135
|
assert.match(output.stdout,/\[AUTH\] process DISARMED \| coding DISARMED \| autonomous DISARMED \| desktop DISARMED \| host DISARMED/);
|
|
132
136
|
const before=await traceCommands(fx.trace);
|
|
133
|
-
assert.deepEqual(before.slice(0,
|
|
134
|
-
assert.equal(before.filter(x=>/^arm(?:process|coding|autonomous|host|desktop)$/.test(x)).length,0,'bare remote must issue zero
|
|
137
|
+
assert.deepEqual(before.slice(0,3),['status','status','machinetest']);
|
|
138
|
+
assert.equal(before.filter(x=>/^(?:arm|disarm)(?:process|coding|autonomous|host|desktop)$/.test(x)).length,0,'bare remote must issue zero authority commands');
|
|
135
139
|
child.stdin.end();
|
|
136
140
|
const ended=await boundedCompletion(output.closed);
|
|
137
141
|
assert.equal(ended.timeout,undefined,'bare remote did not stop');
|
|
138
142
|
assert.equal(ended.code,0,output.stderr);
|
|
139
|
-
assert.equal((await traceCommands(fx.trace)).
|
|
143
|
+
assert.equal((await traceCommands(fx.trace)).includes('park'),false);
|
|
140
144
|
}finally{
|
|
141
145
|
if(child?.exitCode===null)child.kill('SIGKILL');
|
|
142
146
|
await safeRm(fx.dir);
|
|
143
147
|
}
|
|
144
148
|
});
|
|
145
149
|
|
|
146
|
-
winTest('
|
|
150
|
+
winTest('observation-only remote preserves a stale standing Host grant without mutation',async()=>{
|
|
147
151
|
const fx=await fixture({statusOnline:true});
|
|
148
152
|
fx.env.DEADBYTE_REMOTE_TEST_HOST_ARMED='1';
|
|
149
153
|
let child=null;
|
|
150
154
|
try{
|
|
151
155
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
152
156
|
const output=await collect(child);
|
|
153
|
-
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=
|
|
154
|
-
assert.
|
|
157
|
+
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=2,{timeoutMs:fixtureTimeoutMs});
|
|
158
|
+
assert.equal(commands.some(x=>x==='disarmhost'||x==='armhost'||x==='machinetest'),false);
|
|
155
159
|
const deadline=Date.now()+fixtureTimeoutMs;
|
|
156
160
|
while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
|
|
157
161
|
assert.match(output.stdout,/\[READY\]/,output.stderr);
|
|
@@ -163,45 +167,31 @@ winTest('bounded remote disarms a stale standing Host grant before READY',async(
|
|
|
163
167
|
}
|
|
164
168
|
});
|
|
165
169
|
|
|
166
|
-
winTest('remote --full
|
|
170
|
+
winTest('remote --full refuses implicit authority before controller mutation',async()=>{
|
|
167
171
|
const fx=await fixture({statusOnline:true});
|
|
168
172
|
let child=null;
|
|
169
173
|
try{
|
|
170
174
|
child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
171
175
|
const output=await collect(child);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
assert.
|
|
175
|
-
assert.match(output.stdout,/\[READY\]/);
|
|
176
|
-
const before=await traceCommands(fx.trace);
|
|
177
|
-
assert.ok(before.includes('armdesktop')&&before.includes('armhost'));
|
|
178
|
-
child.stdin.end();
|
|
179
|
-
assert.equal((await output.closed).code,0,output.stderr);
|
|
180
|
-
const after=await traceCommands(fx.trace);
|
|
181
|
-
assert.deepEqual(after.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
|
|
176
|
+
assert.notEqual((await output.closed).code,0);
|
|
177
|
+
assert.match(output.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
178
|
+
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
182
179
|
}finally{
|
|
183
180
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
184
181
|
await safeRm(fx.dir);
|
|
185
182
|
}
|
|
186
183
|
});
|
|
187
184
|
|
|
188
|
-
for(const
|
|
189
|
-
winTest(`remote ${flag}
|
|
185
|
+
for(const flag of ['--desktop','--host']){
|
|
186
|
+
winTest(`remote ${flag} refuses implicit authority before controller mutation`,async()=>{
|
|
190
187
|
const fx=await fixture({statusOnline:true});
|
|
191
188
|
let child=null;
|
|
192
189
|
try{
|
|
193
190
|
child=spawn(process.execPath,[cliPath,'remote',flag],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
194
191
|
const output=await collect(child);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
assert.
|
|
198
|
-
assert.match(output.stdout,new RegExp(`${armedPlane} ARMED`));
|
|
199
|
-
assert.match(output.stdout,new RegExp(`${disarmedPlane} DISARMED`));
|
|
200
|
-
const commands=await traceCommands(fx.trace);
|
|
201
|
-
assert.ok(commands.includes(`arm${armedPlane}`));
|
|
202
|
-
assert.ok(!commands.includes(`arm${disarmedPlane}`));
|
|
203
|
-
child.stdin.end();
|
|
204
|
-
assert.equal((await output.closed).code,0,output.stderr);
|
|
192
|
+
assert.notEqual((await output.closed).code,0);
|
|
193
|
+
assert.match(output.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
194
|
+
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
205
195
|
}finally{
|
|
206
196
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
207
197
|
await safeRm(fx.dir);
|
|
@@ -256,7 +246,7 @@ winTest('remote refuses READY when controller snapshot lies about Host enforceme
|
|
|
256
246
|
}
|
|
257
247
|
});
|
|
258
248
|
|
|
259
|
-
winTest('foreground
|
|
249
|
+
winTest('foreground --full rejection cannot create cleanup ownership evidence',async()=>{
|
|
260
250
|
const fx=await fixture({statusOnline:true});
|
|
261
251
|
fx.env.DEADBYTE_REMOTE_TEST_DISARM_NOOP='host';
|
|
262
252
|
fx.env.DEADBYTE_REMOTE_TEST_JSON_HOST_LIE='1';
|
|
@@ -264,43 +254,28 @@ winTest('foreground cleanup retains ownership when status lies about a no-op Hos
|
|
|
264
254
|
try{
|
|
265
255
|
child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
266
256
|
const output=await collect(child);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
assert.
|
|
270
|
-
|
|
271
|
-
assert.notEqual((await output.closed).code,0,'cleanup must fail closed');
|
|
272
|
-
assert.match(output.stderr,/cleanup.*verification|enforcement/i);
|
|
273
|
-
assert.equal(JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8')).schema,'deadbyte.remote-session.v2');
|
|
274
|
-
assert.ok(!(await traceCommands(fx.trace)).includes('park'));
|
|
257
|
+
assert.notEqual((await output.closed).code,0,'elevated foreground must fail closed');
|
|
258
|
+
assert.match(output.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
259
|
+
await assert.rejects(()=>readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'),{code:'ENOENT'});
|
|
260
|
+
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
275
261
|
}finally{
|
|
276
262
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
277
263
|
await safeRm(fx.dir);
|
|
278
264
|
}
|
|
279
265
|
});
|
|
280
266
|
|
|
281
|
-
winTest('
|
|
267
|
+
winTest('observation-only remote never starts a PARKED runtime or inherited child',async()=>{
|
|
282
268
|
const fx=await fixture();
|
|
283
269
|
fx.env.DEADBYTE_REMOTE_TEST_SPAWN_CHILD='1';
|
|
284
270
|
let child=null;
|
|
285
271
|
try{
|
|
286
|
-
|
|
287
|
-
child=spawn(process.execPath,[cliPath,'remote','--operator'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
272
|
+
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
288
273
|
const output=await collect(child);
|
|
289
|
-
const afterStart=await waitFor(fx.trace,commands=>commands.length>=3,{timeoutMs:15000});
|
|
290
|
-
const elapsed=Date.now()-startedAt;
|
|
291
|
-
assert.deepEqual(afterStart.slice(0,3),['status','start','status']);
|
|
292
|
-
assert.ok(elapsed<12000,`PARKED startup waited on inherited child pipe handles: ${elapsed}ms`);
|
|
293
|
-
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4,{timeoutMs:fixtureTimeoutMs});
|
|
294
|
-
assert.deepEqual(ready.slice(0,8),['status','start','status','status','armprocess','armcoding','armautonomous','status']);
|
|
295
|
-
assert.equal(child.exitCode,null,'remote session must remain foreground after PARKED startup');
|
|
296
|
-
assert.match(output.stdout,/\[BOOT\] Starting bridge\/runtime\.\.\./);
|
|
297
|
-
const readyDeadline=Date.now()+fixtureTimeoutMs;
|
|
298
|
-
while(Date.now()<readyDeadline&&!/\[OK\] ONLINE/.test(output.stdout)) await sleep(50);
|
|
299
|
-
assert.match(output.stdout,/\[OK\] ONLINE/);
|
|
300
|
-
child.stdin.end();
|
|
301
274
|
const ended=await boundedCompletion(output.closed);
|
|
302
|
-
assert.equal(ended.timeout,undefined
|
|
303
|
-
assert.
|
|
275
|
+
assert.equal(ended.timeout,undefined);
|
|
276
|
+
assert.notEqual(ended.code,0);
|
|
277
|
+
assert.match(output.stderr,/R175_FOREGROUND_RUNTIME_NOT_READY/);
|
|
278
|
+
assert.equal((await traceCommands(fx.trace)).includes('start'),false);
|
|
304
279
|
}finally{
|
|
305
280
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
306
281
|
try{const pid=Number((await readFile(fx.spawnedPid,'utf8')).trim());if(Number.isInteger(pid)&&pid>0) process.kill(pid,'SIGKILL');}catch{}
|
|
@@ -309,17 +284,17 @@ winTest('bare remote PARKED startup does not wait for a long-lived child holding
|
|
|
309
284
|
}
|
|
310
285
|
});
|
|
311
286
|
|
|
312
|
-
winTest('bare remote uses one
|
|
313
|
-
const fx=await fixture();
|
|
287
|
+
winTest('bare remote uses one live timeline and preserves authority on disconnect',async()=>{
|
|
288
|
+
const fx=await fixture({statusOnline:true});
|
|
314
289
|
let child=null;
|
|
315
290
|
try{
|
|
316
|
-
child=spawn(process.execPath,[cliPath,'remote'
|
|
291
|
+
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
317
292
|
const output=await collect(child);
|
|
318
|
-
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=
|
|
319
|
-
assert.deepEqual(ready.slice(0,
|
|
293
|
+
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=2);
|
|
294
|
+
assert.deepEqual(ready.slice(0,2),['status','status']);
|
|
320
295
|
assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
|
|
321
296
|
const tailDeadline=Date.now()+fixtureTimeoutMs;
|
|
322
|
-
while(Date.now()<tailDeadline&&(!/\[AUTH\] process
|
|
297
|
+
while(Date.now()<tailDeadline&&(!/\[AUTH\] process DISARMED \| coding DISARMED \| autonomous DISARMED \| desktop DISARMED \| host DISARMED/.test(output.stdout)||
|
|
323
298
|
!/\[READY\] DEADBYTE MCP online/.test(output.stdout))) await sleep(50);
|
|
324
299
|
assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
|
|
325
300
|
assert.match(output.stdout,/\[DEVICE\] Starting DEADBYTE MCP/);
|
|
@@ -329,7 +304,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
329
304
|
assert.match(output.stdout,/\[BOOT\] Starting local \+ remote runtime/);
|
|
330
305
|
assert.match(output.stdout,/\[OK\] ONLINE/);
|
|
331
306
|
assert.match(output.stdout,/\[READY\] DEADBYTE MCP online/);
|
|
332
|
-
assert.match(output.stdout,/\[AUTH\] process
|
|
307
|
+
assert.match(output.stdout,/\[AUTH\] process DISARMED \| coding DISARMED \| autonomous DISARMED \| desktop DISARMED \| host DISARMED/);
|
|
333
308
|
assert.match(output.stdout,/\[LOG\] Live tool calls with individual START\/DONE, sanitized arguments and results\. Transport hidden; signed audit remains on disk\./);
|
|
334
309
|
assert.doesNotMatch(output.stdout,/Process plane\s*:/);
|
|
335
310
|
assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
|
|
@@ -354,10 +329,9 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
354
329
|
].join('\n')+'\n','utf8');
|
|
355
330
|
const logDeadline=Date.now()+fixtureTimeoutMs;
|
|
356
331
|
while(Date.now()<logDeadline&&!/^\[.*\] \[FAIL\] Tool call coding_file_read failed \[fedcba98\] 1\.73s$/m.test(output.stdout)) await sleep(50);
|
|
357
|
-
assert.match(output.stdout
|
|
358
|
-
assert.match(output.stdout
|
|
359
|
-
assert.
|
|
360
|
-
assert.equal((output.stdout.match(/\[DONE\] Tool call coding_tree completed \[abcdef12\] 42ms/g)||[]).length,1,'duplicate terminal result must print once');
|
|
332
|
+
assert.match(output.stdout,/\+ PASS\s+coding_tree\s+\| \[abcdef12\]\s+42ms \| projects:src/,'default human view must show bounded path context');
|
|
333
|
+
assert.match(output.stdout,/X FAIL\s+coding_file_read\s+\| \[fedcba98\]\s+1\.73s \| projects:src\/deadbyte-cli\.mjs/);
|
|
334
|
+
assert.equal((output.stdout.match(/\+ PASS\s+coding_tree\s+\| \[abcdef12\]\s+42ms/g)||[]).length,1,'duplicate terminal result must print once');
|
|
361
335
|
assert.doesNotMatch(output.stdout,/\[cursor r1:/,'full replay cursor belongs in the journal, not every human line');
|
|
362
336
|
const resultJournal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`),'utf8'));
|
|
363
337
|
assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2,3,4]);
|
|
@@ -375,10 +349,10 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
375
349
|
const ended=await boundedCompletion(output.closed);
|
|
376
350
|
assert.equal(ended.timeout,undefined,'remote session did not exit after foreground input closed');
|
|
377
351
|
const commands=await traceCommands(fx.trace);
|
|
378
|
-
assert.
|
|
352
|
+
assert.equal(commands.some(command=>/^(?:arm|disarm|park)/.test(command)),false);
|
|
379
353
|
assert.match(output.stdout,/^DEADBYTE MCP$/m);
|
|
380
354
|
assert.match(output.stdout,/Live tool calls with individual START\/DONE/i);
|
|
381
|
-
assert.match(output.stdout
|
|
355
|
+
assert.match(output.stdout,/preserving owner standing grants/i);
|
|
382
356
|
assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmdesktop OK|disarmhost OK|DEADBYTE MCP = PARKED/);
|
|
383
357
|
assert.doesNotMatch(output.stdout,/\p{Extended_Pictographic}/u,'piped Windows fallback must remain ASCII-safe');
|
|
384
358
|
}finally{
|
|
@@ -388,12 +362,12 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
388
362
|
});
|
|
389
363
|
|
|
390
364
|
winTest('remote --resume replays sanitized tool-call and terminal result events after the cursor without duplicates or loss',async()=>{
|
|
391
|
-
const fx=await fixture();
|
|
365
|
+
const fx=await fixture({statusOnline:true});
|
|
392
366
|
let first=null,second=null;
|
|
393
367
|
try{
|
|
394
368
|
first=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
395
369
|
const firstOutput=await collect(first);
|
|
396
|
-
await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=
|
|
370
|
+
await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=2);
|
|
397
371
|
const liveDeadline=Date.now()+3000;
|
|
398
372
|
while(Date.now()<liveDeadline&&!/\[LOG\] Live tool calls/.test(firstOutput.stdout)) await sleep(50);
|
|
399
373
|
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
@@ -451,7 +425,7 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
|
|
|
451
425
|
});
|
|
452
426
|
|
|
453
427
|
winTest('remote --verbose enables sanitized request detail while preserving foreground authority cleanup',async()=>{
|
|
454
|
-
const fx=await fixture();
|
|
428
|
+
const fx=await fixture({statusOnline:true});
|
|
455
429
|
let child=null;
|
|
456
430
|
try{
|
|
457
431
|
child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
@@ -474,21 +448,21 @@ winTest('remote --verbose enables sanitized request detail while preserving fore
|
|
|
474
448
|
assert.equal(ended.timeout,undefined);
|
|
475
449
|
assert.equal(ended.code,0,output.stderr);
|
|
476
450
|
const commands=await traceCommands(fx.trace);
|
|
477
|
-
assert.
|
|
451
|
+
assert.equal(commands.some(command=>/^(?:arm|disarm|park)/.test(command)),false);
|
|
478
452
|
}finally{
|
|
479
453
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
480
454
|
await safeRm(fx.dir);
|
|
481
455
|
}
|
|
482
456
|
});
|
|
483
457
|
|
|
484
|
-
winTest('detached guardian
|
|
485
|
-
const fx=await fixture();
|
|
458
|
+
winTest('detached observation guardian removes its marker without touching authority after owner death',async()=>{
|
|
459
|
+
const fx=await fixture({statusOnline:true});
|
|
486
460
|
let child=null;
|
|
487
461
|
try{
|
|
488
|
-
child=spawn(process.execPath,[cliPath,'remote'
|
|
462
|
+
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
489
463
|
const output=await collect(child);
|
|
490
|
-
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=
|
|
491
|
-
assert.deepEqual(ready.slice(0,
|
|
464
|
+
const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=2);
|
|
465
|
+
assert.deepEqual(ready.slice(0,2),['status','status']);
|
|
492
466
|
const markerPath=path.join(fx.state,'runtime','remote-session.json');
|
|
493
467
|
const markerDeadline=Date.now()+3000;
|
|
494
468
|
let marker=null;
|
|
@@ -505,8 +479,9 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
505
479
|
assert.equal(killed,true,'failed to hard-kill remote owner');
|
|
506
480
|
await Promise.race([output.closed,sleep(3000)]);
|
|
507
481
|
|
|
508
|
-
|
|
509
|
-
|
|
482
|
+
await sleep(750);
|
|
483
|
+
const cleaned=await traceCommands(fx.trace);
|
|
484
|
+
assert.equal(cleaned.some(command=>/^(?:arm|disarm|park)/.test(command)),false);
|
|
510
485
|
const markerGoneDeadline=Date.now()+3000;
|
|
511
486
|
let markerGone=false;
|
|
512
487
|
while(Date.now()<markerGoneDeadline){
|
|
@@ -520,31 +495,24 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
520
495
|
}
|
|
521
496
|
});
|
|
522
497
|
|
|
523
|
-
winTest('
|
|
498
|
+
winTest('remote --full cannot create a guardian or mutate Host authority',async()=>{
|
|
524
499
|
const fx=await fixture({statusOnline:true});
|
|
525
500
|
fx.env.DEADBYTE_REMOTE_TEST_DISARM_NOOP='host';
|
|
526
501
|
let child=null;
|
|
527
502
|
try{
|
|
528
503
|
child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
529
504
|
const output=await collect(child);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
assert.
|
|
533
|
-
|
|
534
|
-
await output.closed;
|
|
535
|
-
await waitFor(fx.trace,commands=>commands.includes('disarmhost'),{timeoutMs:fixtureTimeoutMs});
|
|
536
|
-
await sleep(750);
|
|
537
|
-
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
538
|
-
assert.equal(marker.schema,'deadbyte.remote-session.v2');
|
|
539
|
-
const commands=await traceCommands(fx.trace);
|
|
540
|
-
assert.ok(!commands.includes('park'),'guardian must not park after unverified revocation');
|
|
505
|
+
assert.notEqual((await output.closed).code,0);
|
|
506
|
+
assert.match(output.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
507
|
+
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
508
|
+
await assert.rejects(()=>readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'),{code:'ENOENT'});
|
|
541
509
|
}finally{
|
|
542
510
|
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
543
511
|
await safeRm(fx.dir);
|
|
544
512
|
}
|
|
545
513
|
});
|
|
546
514
|
|
|
547
|
-
winTest('
|
|
515
|
+
winTest('remote --operator cannot reach authority arming or rollback',async()=>{
|
|
548
516
|
const fx=await fixture({failCommand:'armcoding'});
|
|
549
517
|
try{
|
|
550
518
|
const child=spawn(process.execPath,[cliPath,'remote','--operator'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
@@ -553,8 +521,8 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
|
|
|
553
521
|
assert.equal(ended.timeout,undefined,`failed startup did not terminate; stdout=${output.stdout.slice(-1200)} stderr=${output.stderr.slice(-1200)} trace=${(await traceCommands(fx.trace)).join(',')}`);
|
|
554
522
|
assert.notEqual(ended.code,0);
|
|
555
523
|
const commands=await traceCommands(fx.trace);
|
|
556
|
-
assert.deepEqual(commands
|
|
557
|
-
assert.
|
|
524
|
+
assert.deepEqual(commands,[]);
|
|
525
|
+
assert.match(output.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
558
526
|
assert.doesNotMatch(output.stdout,/Live logs/i);
|
|
559
527
|
}finally{await safeRm(fx.dir);}
|
|
560
528
|
});
|
|
@@ -571,7 +539,7 @@ winTest('bare remote retains an owner-unverified stale marker without starting o
|
|
|
571
539
|
const ended=await boundedCompletion(output.closed);
|
|
572
540
|
assert.equal(ended.timeout,undefined);
|
|
573
541
|
assert.notEqual(ended.code,0);
|
|
574
|
-
assert.match(output.stderr,/
|
|
542
|
+
assert.match(output.stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
575
543
|
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
576
544
|
assert.equal(JSON.parse(await readFile(path.join(runtime,'remote-session.json'),'utf8')).pid,2147483000);
|
|
577
545
|
}finally{
|
|
@@ -595,14 +563,14 @@ winTest('PID reuse cannot authorize removal of a marker with a different process
|
|
|
595
563
|
const ended=await boundedCompletion(output.closed);
|
|
596
564
|
assert.equal(ended.timeout,undefined);
|
|
597
565
|
assert.notEqual(ended.code,0);
|
|
598
|
-
|
|
566
|
+
assert.match(output.stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
599
567
|
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
600
568
|
assert.equal(JSON.parse(await readFile(path.join(runtime,'remote-session.json'),'utf8')).session_nonce,marker.session_nonce);
|
|
601
569
|
}finally{await safeRm(fx.dir);}
|
|
602
570
|
});
|
|
603
571
|
|
|
604
572
|
winTest('remote warning lines use deterministic ASCII fallback on piped Windows output',async()=>{
|
|
605
|
-
const fx=await fixture();
|
|
573
|
+
const fx=await fixture({statusOnline:true});
|
|
606
574
|
let child=null;
|
|
607
575
|
try{
|
|
608
576
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
@@ -625,7 +593,7 @@ winTest('remote warning lines use deterministic ASCII fallback on piped Windows
|
|
|
625
593
|
});
|
|
626
594
|
|
|
627
595
|
winTest('remote cleanup never removes a marker with changed ownership identity',async()=>{
|
|
628
|
-
const fx=await fixture();
|
|
596
|
+
const fx=await fixture({statusOnline:true});
|
|
629
597
|
let child=null;
|
|
630
598
|
try{
|
|
631
599
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
@@ -651,18 +619,17 @@ winTest('R58 concurrent cold remote claims have one winner and no loser cleanup'
|
|
|
651
619
|
const fx=await fixture({statusOnline:true});const children=[],outputs=[];
|
|
652
620
|
try{
|
|
653
621
|
for(let i=0;i<3;i++){
|
|
654
|
-
const child=spawn(process.execPath,[cliPath,'remote'
|
|
622
|
+
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
655
623
|
children.push(child);outputs.push(await collect(child));
|
|
656
624
|
}
|
|
657
|
-
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=
|
|
658
|
-
assert.ok(commands.filter(x=>x==='status').length>=
|
|
625
|
+
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=2);
|
|
626
|
+
assert.ok(commands.filter(x=>x==='status').length>=2,outputs.map(o=>o.stderr).join('\n'));
|
|
659
627
|
const deadline=Date.now()+5000;
|
|
660
628
|
while(Date.now()<deadline&&children.filter(c=>c.exitCode===null).length>1)await sleep(50);
|
|
661
629
|
const live=children.filter(c=>c.exitCode===null);
|
|
662
630
|
assert.equal(live.length,1,'exactly one verified foreground may survive');
|
|
663
631
|
const trace=await traceCommands(fx.trace);
|
|
664
|
-
|
|
665
|
-
assert.ok(!trace.some(x=>x.startsWith('disarm')||x==='park'),'losers must not revoke winner authority');
|
|
632
|
+
assert.ok(!trace.some(x=>x.startsWith('arm')||x.startsWith('disarm')||x==='park'),'observers must not mutate authority');
|
|
666
633
|
for(let i=0;i<children.length;i++)if(children[i]!==live[0])assert.match(outputs[i].stderr,/remote session already (claimed|active)/,'concurrent rejection must have deterministic ownership vocabulary');
|
|
667
634
|
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
668
635
|
assert.equal(marker.owner_pid,live[0].pid);
|
|
@@ -676,10 +643,10 @@ winTest('R58 concurrent cold remote claims have one winner and no loser cleanup'
|
|
|
676
643
|
winTest('R58 duplicate remote verifies the real live owner and leaves its authority untouched',async()=>{
|
|
677
644
|
const fx=await fixture({statusOnline:true});let first=null,second=null;
|
|
678
645
|
try{
|
|
679
|
-
first=spawn(process.execPath,[cliPath,'remote'
|
|
646
|
+
first=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
680
647
|
const firstOutput=await collect(first);
|
|
681
|
-
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=
|
|
682
|
-
assert.ok(commands.filter(x=>x==='status').length>=
|
|
648
|
+
const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=2);
|
|
649
|
+
assert.ok(commands.filter(x=>x==='status').length>=2,firstOutput.stderr);
|
|
683
650
|
const ownerReadyDeadline=Date.now()+fixtureTimeoutMs;
|
|
684
651
|
while(Date.now()<ownerReadyDeadline&&!/\[READY\]/.test(firstOutput.stdout)) await sleep(50);
|
|
685
652
|
assert.match(firstOutput.stdout,/\[READY\]/,firstOutput.stderr);
|
|
@@ -688,7 +655,7 @@ winTest('R58 duplicate remote verifies the real live owner and leaves its author
|
|
|
688
655
|
const marker=JSON.parse(await readFile(markerPath,'utf8'));
|
|
689
656
|
assert.equal(marker.owner_identity.pid,first.pid);
|
|
690
657
|
assert.equal(marker.guardian_identity.parent_pid,first.pid);
|
|
691
|
-
second=spawn(process.execPath,[cliPath,'remote'
|
|
658
|
+
second=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
692
659
|
const secondOutput=await collect(second);
|
|
693
660
|
const stopped=await secondOutput.closed;
|
|
694
661
|
assert.notEqual(stopped.code,0);
|
|
@@ -718,7 +685,7 @@ winTest('bare remote refuses unverified legacy ownership without touching author
|
|
|
718
685
|
assert.equal(ended.timeout,undefined);
|
|
719
686
|
assert.notEqual(ended.code,0);
|
|
720
687
|
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
721
|
-
assert.match(output.stderr,/
|
|
688
|
+
assert.match(output.stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
722
689
|
}finally{
|
|
723
690
|
if(child?.exitCode===null) child.kill();
|
|
724
691
|
await safeRm(fx.dir);
|
|
@@ -757,7 +724,7 @@ winTest('explicit remote status keeps the coherent runtime one-shot and silent',
|
|
|
757
724
|
}finally{await safeRm(fx.dir);}
|
|
758
725
|
});
|
|
759
726
|
|
|
760
|
-
winTest('bare remote
|
|
727
|
+
winTest('bare remote reports runtime-not-ready without invoking start',async()=>{
|
|
761
728
|
const fx=await fixture({failCommand:'start'});
|
|
762
729
|
try{
|
|
763
730
|
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
@@ -765,12 +732,12 @@ winTest('bare remote surfaces bounded controller-start-failure evidence instead
|
|
|
765
732
|
const ended=await boundedCompletion(output.closed);
|
|
766
733
|
assert.equal(ended.timeout,undefined,'failed start did not terminate');
|
|
767
734
|
assert.notEqual(ended.code,0);
|
|
768
|
-
assert.match(output.stderr,/
|
|
769
|
-
assert.
|
|
735
|
+
assert.match(output.stderr,/R175_FOREGROUND_RUNTIME_NOT_READY/);
|
|
736
|
+
assert.equal((await traceCommands(fx.trace)).includes('start'),false);
|
|
770
737
|
}finally{await safeRm(fx.dir);}
|
|
771
738
|
});
|
|
772
739
|
|
|
773
|
-
winTest('
|
|
740
|
+
winTest('runtime-not-ready observation does not enter cleanup authority paths',async()=>{
|
|
774
741
|
const fx=await fixture({failCommand:'start,disarmautonomous'});
|
|
775
742
|
try{
|
|
776
743
|
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
|
|
@@ -778,17 +745,16 @@ winTest('failed startup retains primary and cleanup failure plus ownership marke
|
|
|
778
745
|
const ended=await boundedCompletion(output.closed);
|
|
779
746
|
assert.equal(ended.timeout,undefined,'failed startup did not terminate');
|
|
780
747
|
assert.notEqual(ended.code,0);
|
|
781
|
-
assert.match(output.stderr,/
|
|
782
|
-
assert.
|
|
783
|
-
assert.match(output.stderr,/cleanup failed.*disarmautonomous/i);
|
|
748
|
+
assert.match(output.stderr,/R175_FOREGROUND_RUNTIME_NOT_READY/);
|
|
749
|
+
assert.equal((await traceCommands(fx.trace)).some(command=>/^(?:arm|disarm|start|park)/.test(command)),false);
|
|
784
750
|
const retained=await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8').then(()=>true,()=>false);
|
|
785
|
-
assert.equal(retained,
|
|
751
|
+
assert.equal(retained,false);
|
|
786
752
|
}finally{await safeRm(fx.dir);}
|
|
787
753
|
});
|
|
788
754
|
|
|
789
755
|
|
|
790
756
|
winTest('remote --trace --filter selects presentation without dropping journaled events',async()=>{
|
|
791
|
-
const fx=await fixture();
|
|
757
|
+
const fx=await fixture({statusOnline:true});
|
|
792
758
|
let child=null;
|
|
793
759
|
try{
|
|
794
760
|
child=spawn(process.execPath,[cliPath,'remote','--trace','--filter','category=write,status=error,trace=goal-1234'],{
|
|
@@ -811,7 +777,7 @@ winTest('remote --trace --filter selects presentation without dropping journaled
|
|
|
811
777
|
await writeFile(path.join(auditDir,'segment-00000001.jsonl'),audit.map(JSON.stringify).join('\n')+'\n','utf8');
|
|
812
778
|
const deadline=Date.now()+4000;
|
|
813
779
|
while(Date.now()<deadline&&!/machine_fs\.write.*trace=goal-1234/.test(output.stdout)) await sleep(50);
|
|
814
|
-
assert.match(output.stdout,/
|
|
780
|
+
assert.match(output.stdout,/machine_fs\.write.*\[filter-w\].*10ms.*trace=goal-123\.\.\. span=filter-w\.\.\..*denied/i);
|
|
815
781
|
assert.doesNotMatch(output.stdout,/coding_file_read.*missing/);
|
|
816
782
|
assert.doesNotMatch(output.stdout,/SECRET/);
|
|
817
783
|
const journal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',marker.result_session_id+'.json'),'utf8'));
|