ReviewOS

also looking at this

stacks/drone

test: retain bounded browser launch evidence on failure

#70
Merged chris wants to merge codex/browser-startup-evidence into main
18 files +620 -23

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/workflows/ci.ymlmodified+4-0
Changes to .github/workflows/ci.yml
@@ -451,6 +451,8 @@ jobs:
451451 path: |
452452 ${{ runner.temp }}/workspace-browser-evidence/*.png
453453 ${{ runner.temp }}/workspace-browser-evidence/compiled.html
454 ${{ runner.temp }}/workspace-browser-evidence/browser-launch.json
455 ${{ runner.temp }}/workspace-browser-evidence/chrome-startup-stderr.log
454456 execution-browser:
455457 runs-on: ubuntu-latest
456458 timeout-minutes: 10
@@ -480,6 +482,8 @@ jobs:
480482 path: |
481483 ${{ runner.temp }}/execution-browser-evidence/*.png
482484 ${{ runner.temp }}/execution-browser-evidence/compiled.html
485 ${{ runner.temp }}/execution-browser-evidence/browser-launch.json
486 ${{ runner.temp }}/execution-browser-evidence/chrome-startup-stderr.log
483487 purge-namespace:
484488 runs-on: ubuntu-latest
485489 timeout-minutes: 30
docs/validation.mdmodified+20-0
Changes to docs/validation.md
@@ -1,5 +1,25 @@
11# Validation evidence
22
3## Browser qualification diagnostics
4
5Browser fixtures keep their failed private directory after owned process-group
6cleanup. It contains native fixture credentials, database state and the browser
7profile, so CI does not upload that directory. Local retained paths are reported;
8CI runner disposal still ends access to private files that were not exported.
9
10CI exports a closed launch-child observation and at most 64 KiB of startup stderr.
11Public stderr capture stops before any page visit can receive credentials. Later
12browser stderr remains in the private fixture, while exported metadata reports
13that capture was withheld. Refused evidence destinations report unavailable and
14preserve the original browser failure. Launch exit observation, signal and cleanup
15intent are separate fields; they do not prove browser/page/thread liveness or the
16kernel's exact exit time. Post-renewal CDP liveness has its own operation label.
17
18Discovery, transport, stream and outer-test limits remain unchanged. These
19observations support the open investigations in [#44](https://github.com/stacksjs/drone/issues/44)
20and [#64](https://github.com/stacksjs/drone/issues/64); retaining a new attempt does
21not explain an earlier failure whose Chrome evidence was removed.
22
323## Owned mission simulation slice
424
525- Full release suite: 211 Bun tests, 17,424 assertions, zero failures. Buddy lint, application/runtime/view
tests/helpers/browser-launch-evidence.tsadded+59-0
Changes to tests/helpers/browser-launch-evidence.ts
@@ -0,0 +1,59 @@
1import { Buffer } from 'node:buffer'
2import { writeSync } from 'node:fs'
3import { mkdir, open, writeFile } from 'node:fs/promises'
4import { join } from 'node:path'
5
6export interface BrowserLaunchObservation {
7 phase: 'startup-unavailable' | 'discovery-failed' | 'closed'
8 elapsedMs: number
9 launch: { pid: number, exitObserved: boolean, settledExitCode: number | null, signalCode: string | number | null, exitObservedBeforeCleanup: boolean, cleanupRequested: boolean, exitElapsedMs: number | null }
10 portProbe: { attempts: number, successfulReads: number, failedReads: number, lastReadError: string | null, discovered: boolean }
11}
12export function browserFileErrorCode(error: unknown): string {
13 const code = (error as NodeJS.ErrnoException | null)?.code
14 return typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,31}$/.test(code) ? code : 'OTHER'
15}
16function unavailable(target: 'local' | 'export', error: unknown) {
17 const bytes = new TextEncoder().encode(`${JSON.stringify({ oracle: 'browser-launch-evidence-unavailable', target, code: browserFileErrorCode(error) })}\n`)
18 for (const descriptor of [2, 1]) {
19 try { if (writeSync(descriptor, bytes) === bytes.byteLength) return }
20 catch { /* Reporting must preserve the original browser failure. */ }
21 }
22}
23/** Public stderr is captured only before any page can receive native fixture credentials. */
24export async function retainBrowserLaunchEvidence(root: string, observation: BrowserLaunchObservation, options: { startupStderr: boolean, exportDirectory?: string }) {
25 let stderr: { status: 'retained' | 'unavailable' | 'withheld', retainedBytes: number, totalBytes: number | null, truncated: boolean, code: string | null } = { status: 'withheld', retainedBytes: 0, totalBytes: null, truncated: false, code: null }
26 let retained: Buffer | undefined
27 if (options.startupStderr) {
28 try {
29 const handle = await open(join(root, 'chrome.log'), 'r')
30 try {
31 const stat = await handle.stat()
32 if (!stat.isFile()) throw new Error('Startup stderr is not a regular file')
33 const size = Math.min(stat.size, 65536), bytes = Buffer.alloc(size)
34 const { bytesRead } = await handle.read(bytes, 0, size, Math.max(0, stat.size - size))
35 retained = bytes.subarray(0, bytesRead)
36 stderr = { status: 'retained', retainedBytes: bytesRead, totalBytes: stat.size, truncated: stat.size > bytesRead, code: null }
37 }
38 finally { await handle.close() }
39 }
40 catch (error) { stderr = { status: 'unavailable', retainedBytes: 0, totalBytes: null, truncated: false, code: browserFileErrorCode(error) } }
41 }
42 const outcomes = { local: false, export: false }
43 for (const target of ['local', 'export'] as const) {
44 const directory = target === 'local' ? root : options.exportDirectory
45 if (!directory) continue
46 try {
47 if (target === 'export') await mkdir(directory, { recursive: true })
48 let snapshot = stderr
49 if (retained) {
50 try { await writeFile(join(directory, 'chrome-startup-stderr.log'), retained, { mode: 0o600 }) }
51 catch (error) { snapshot = { ...stderr, status: 'unavailable', retainedBytes: 0, code: browserFileErrorCode(error) } }
52 }
53 await writeFile(join(directory, 'browser-launch.json'), `${JSON.stringify({ version: 1, evidence: 'owned-browser-launch-observation', ...observation, stderr: snapshot }, null, 2)}\n`, { mode: 0o600 })
54 outcomes[target] = true
55 }
56 catch (error) { unavailable(target, error) }
57 }
58 return outcomes
59}
tests/helpers/workspace-browser-driver.tsmodified+1-1
Changes to tests/helpers/workspace-browser-driver.ts
@@ -89,7 +89,7 @@ try {
8989 console.log(`QA driver33s timer drift ms: ${Date.now() - waitStart - 33000}`)
9090 const status = await control('status')
9191 console.log(`QA actual native recipient streams: ${status.recipientStreams}`)
92 assert.equal(await second.evaluate('1'), 1, 'Existing actual browser tab must stay responsive')
92 await second.checkLiveness()
9393 assert(status.recipientStreams > baseline.recipientStreams, 'Actual native stream must reconnect during the measured lifetime window')
9494 const starts = status.recipientStreamStarts as number[]
9595 assert(starts.slice(baseline.recipientStreams).some((start, index) => start >= waitStart && start - starts[baseline.recipientStreams + index - 1]! >= 30000), 'Measured renewal must follow an actual default30s stream lifetime')
tests/helpers/workspace-browser.tsmodified+19-6
Changes to tests/helpers/workspace-browser.ts
@@ -2,6 +2,8 @@ import assert from 'node:assert/strict'
22import { mkdir, readFile } from 'node:fs/promises'
33import { join } from 'node:path'
44import { observeBrowserOperation } from './browser-operation'
5import { browserFileErrorCode, retainBrowserLaunchEvidence } from './browser-launch-evidence'
6import type { BrowserLaunchObservation } from './browser-launch-evidence'
57
68/** Dependency-free CDP, with a fresh profile. Credentials never enter command arguments. */
79async function openSocket(url: string): Promise<WebSocket> {
@@ -19,15 +21,24 @@ export async function startWorkspaceBrowser(root: string) {
1921 await mkdir(profile)
2022 const child = Bun.spawn([process.env.BROWSE_BROWSER ?? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '--headless=new', '--disable-gpu', '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--disable-backgrounding-occluded-windows', '--disable-features=CalculateNativeWinOcclusion', '--disable-dev-shm-usage', '--disable-extensions', '--disable-component-update', '--no-first-run', '--no-default-browser-check', '--disable-background-networking', '--remote-debugging-port=0', `--user-data-dir=${profile}`, 'about:blank'], { stdout: 'ignore', stderr: Bun.file(join(root, 'chrome.log')) })
2123 const started = Date.now()
24 const began = performance.now()
2225 let closing = false
23 void child.exited.then(code => { if (!closing) console.log(`QA isolated Chrome exited unexpectedly: ${code}, elapsedMs:${Date.now() - started}`) })
26 const launch: BrowserLaunchObservation['launch'] = { pid: child.pid, exitObserved: false, settledExitCode: null, signalCode: null, exitObservedBeforeCleanup: false, cleanupRequested: false, exitElapsedMs: null }
27 const portProbe: BrowserLaunchObservation['portProbe'] = { attempts: 0, successfulReads: 0, failedReads: 0, lastReadError: null, discovered: false }
28 let pageVisited = false
29 const retain = (phase: BrowserLaunchObservation['phase']) => retainBrowserLaunchEvidence(root, { phase, elapsedMs: performance.now() - began, launch: { ...launch }, portProbe: { ...portProbe } }, { startupStderr: !pageVisited, exportDirectory: process.env.WORKSPACE_BROWSER_EVIDENCE ?? process.env.EXECUTION_BROWSER_EVIDENCE })
30 void child.exited.then(code => {
31 launch.exitObserved = true; launch.settledExitCode = code; launch.signalCode = child.signalCode; launch.exitObservedBeforeCleanup = !closing; launch.exitElapsedMs = performance.now() - began
32 if (!closing) console.log(`QA isolated Chrome exited unexpectedly: ${code}, elapsedMs:${Date.now() - started}`)
33 })
2434 let port = ''
2535 for (let attempt = 0; attempt < 400; attempt++) {
26 port = await readFile(join(profile, 'DevToolsActivePort'), 'utf8').then(value => value.split('\n')[0]!).catch(() => '')
27 if (port) break
36 portProbe.attempts++
37 port = await readFile(join(profile, 'DevToolsActivePort'), 'utf8').then(value => { portProbe.successfulReads++; portProbe.lastReadError = null; return value.split('\n')[0]! }).catch(error => { portProbe.failedReads++; portProbe.lastReadError = browserFileErrorCode(error); return '' })
38 if (port) { portProbe.discovered = true; break }
2839 await Bun.sleep(50)
2940 }
30 if (!port) { closing = true; if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]); throw new Error('Fresh browser must start') }
41 if (!port) { closing = true; launch.cleanupRequested = true; if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]); await retain('startup-unavailable'); throw new Error('Fresh browser must start') }
3142 // Keep a browser-level session, as well as page sessions, for the owned lifecycle.
3243 let browserSocket: WebSocket
3344 try {
@@ -35,13 +46,14 @@ export async function startWorkspaceBrowser(root: string) {
3546 const version = await observeBrowserOperation('version-json', () => response.json()) as { webSocketDebuggerUrl: string }
3647 browserSocket = await observeBrowserOperation('browser-socket', () => openSocket(version.webSocketDebuggerUrl))
3748 }
38 catch (error) { closing = true; if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]); throw error }
49 catch (error) { closing = true; launch.cleanupRequested = true; if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]); await retain('discovery-failed'); throw error }
3950 let browserSequence = 0
4051 browserSocket.onmessage = () => {}
4152 const keepAlive = setInterval(() => { if (browserSocket.readyState === WebSocket.OPEN) browserSocket.send(JSON.stringify({ id: ++browserSequence, method: 'Browser.getVersion' })) }, 5000)
4253 const pages: Array<{ close: () => void }> = []
4354 return {
4455 async page(url: string, javascript = true) {
56 pageVisited = true
4557 const response = await observeBrowserOperation('target-fetch', () => fetch(`http://127.0.0.1:${port}/json/new?about:blank`, { method: 'PUT', signal: AbortSignal.timeout(5000) }))
4658 const target = await observeBrowserOperation('target-json', () => response.json()) as { webSocketDebuggerUrl: string }
4759 const socket = await observeBrowserOperation('page-socket', () => openSocket(target.webSocketDebuggerUrl))
@@ -73,6 +85,7 @@ export async function startWorkspaceBrowser(root: string) {
7385 while (Date.now() < end) { if (await evaluate(expression)) return; await Bun.sleep(50) }
7486 throw new Error(message)
7587 },
88 async checkLiveness() { phase = 'post-renewal liveness'; assert.equal(await evaluate('1'), 1) },
7689 async fill(selector: string, value: string) { phase = `field ${selector}`; await evaluate(`(() => { const element = document.querySelector(${JSON.stringify(selector)}); if (!element) throw Error('Missing form field'); element.value = ${JSON.stringify(value)}; element.dispatchEvent(new Event('input',{bubbles:true})); element.dispatchEvent(new Event('change',{bubbles:true})); })()`) },
7790 async click(text: string) { phase = `action ${text}`; await evaluate(`(() => { const element = [...document.querySelectorAll('button')].find(value => value.textContent.trim() === ${JSON.stringify(text)} && !value.disabled); if (!element) throw Error('Missing enabled action'); element.click(); })()`) },
7891 async screenshot(path: string, width = 1280, height = 1000) { await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: width < 500 }); await Bun.sleep(100); const shot = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false }); await Bun.write(path, Buffer.from(shot.data, 'base64')) },
@@ -83,6 +96,6 @@ export async function startWorkspaceBrowser(root: string) {
8396 await page.wait(javascript ? "document.querySelector('#incident-key') !== null && document.querySelector('[data-workspace-status]')?.textContent.trim() === 'disconnected'" : "document.querySelector('#incident-key') !== null", 'Compiled workspace must initialize')
8497 return page
8598 },
86 async close() { console.log(`QA browser.close requested, elapsedMs:${Date.now() - started}`); closing = true; clearInterval(keepAlive); browserSocket.close(); for (const page of pages) page.close(); if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]) },
99 async close() { console.log(`QA browser.close requested, elapsedMs:${Date.now() - started}`); closing = true; launch.cleanupRequested = true; clearInterval(keepAlive); browserSocket.close(); for (const page of pages) page.close(); if (child.exitCode === null) child.kill(); await Promise.race([child.exited, Bun.sleep(2000)]); await retain('closed') },
87100 }
88101}
tests/unit/public-safety-browser-launch-evidence.test.tsadded+59-0
Changes to tests/unit/public-safety-browser-launch-evidence.test.ts
@@ -0,0 +1,59 @@
1import { expect, test } from 'bun:test'
2import { Buffer } from 'node:buffer'
3import { mkdtemp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
4import { tmpdir } from 'node:os'
5import { join } from 'node:path'
6import { retainBrowserLaunchEvidence } from '../helpers/browser-launch-evidence'
7import type { BrowserLaunchObservation } from '../helpers/browser-launch-evidence'
8
9const observation: BrowserLaunchObservation = { phase: 'startup-unavailable', elapsedMs: 20000, launch: { pid: 12345, exitObserved: true, settledExitCode: 23, signalCode: null, exitObservedBeforeCleanup: true, cleanupRequested: true, exitElapsedMs: 10 }, portProbe: { attempts: 400, successfulReads: 0, failedReads: 400, lastReadError: 'ENOENT', discovered: false } }
10
11test('startup capture retains only a bounded native stderr tail at both evidence destinations', async () => {
12 const root = await mkdtemp(join(tmpdir(), 'drone-browser-evidence-')), exported = join(root, 'export')
13 try {
14 const source = 'unretained-prefix'.repeat(5000) + 'x'.repeat(65500) + 'controlled-tail'
15 await writeFile(join(root, 'chrome.log'), source)
16 expect(await retainBrowserLaunchEvidence(root, observation, { startupStderr: true, exportDirectory: exported })).toEqual({ local: true, export: true })
17 for (const directory of [root, exported]) {
18 const report = JSON.parse(await readFile(join(directory, 'browser-launch.json'), 'utf8'))
19 expect(report.stderr.totalBytes).toBe(Buffer.byteLength(source))
20 expect(report.stderr.retainedBytes).toBe(65536)
21 expect(report.stderr.truncated).toBe(true)
22 const captured = await readFile(join(directory, 'chrome-startup-stderr.log'))
23 expect(captured.byteLength).toBe(65536)
24 expect(captured.equals(Buffer.from(source).subarray(-65536))).toBe(true)
25 expect((await stat(join(directory, 'browser-launch.json'))).mode & 0o777).toBe(0o600)
26 }
27 expect(await readFile(join(root, 'chrome.log'), 'utf8')).toBe(source)
28 }
29 finally { await rm(root, { recursive: true, force: true }) }
30})
31
32test('post-navigation capture withholds private stderr and exports only lifecycle scalars', async () => {
33 const root = await mkdtemp(join(tmpdir(), 'drone-browser-evidence-')), exported = join(root, 'export')
34 try {
35 await writeFile(join(root, 'chrome.log'), 'PRIVATE_FIXTURE_CREDENTIAL_SENTINEL')
36 const result = await retainBrowserLaunchEvidence(root, { ...observation, phase: 'closed' }, { startupStderr: false, exportDirectory: exported })
37 expect(result).toEqual({ local: true, export: true })
38 expect(await readdir(exported)).toEqual(['browser-launch.json'])
39 const publicText = await readFile(join(exported, 'browser-launch.json'), 'utf8')
40 expect(publicText).not.toContain('PRIVATE_FIXTURE_CREDENTIAL_SENTINEL')
41 expect(JSON.parse(publicText).stderr.status).toBe('withheld')
42 expect(await readFile(join(root, 'chrome.log'), 'utf8')).toBe('PRIVATE_FIXTURE_CREDENTIAL_SENTINEL')
43 }
44 finally { await rm(root, { recursive: true, force: true }) }
45})
46
47test('actual refused metadata and export paths report unavailable without replacing the browser failure', async () => {
48 const root = await mkdtemp(join(tmpdir(), 'drone-browser-evidence-')), exported = join(root, 'not-a-directory')
49 try {
50 await writeFile(join(root, 'chrome.log'), 'controlled startup message')
51 await mkdir(join(root, 'browser-launch.json'))
52 await writeFile(exported, 'owned obstruction')
53 const original = new Error('original browser failure')
54 const run = async () => { await retainBrowserLaunchEvidence(root, observation, { startupStderr: true, exportDirectory: exported }); throw original }
55 await expect(run()).rejects.toBe(original)
56 expect(await readFile(exported, 'utf8')).toBe('owned obstruction')
57 }
58 finally { await rm(root, { recursive: true, force: true }) }
59})
tests/unit/public-safety-browser-launch.test.tsadded+48-0
Changes to tests/unit/public-safety-browser-launch.test.ts
@@ -0,0 +1,48 @@
1import { expect, test } from 'bun:test'
2import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3import { tmpdir } from 'node:os'
4import { join } from 'node:path'
5import process from 'node:process'
6import { startWorkspaceBrowser } from '../helpers/workspace-browser'
7
8test.skipIf(process.platform === 'win32')('failed native launch retains startup stderr, actual exit and port observations at the original discovery bound', async () => {
9 const supplied = process.env.BROWSER_LAUNCH_EVIDENCE
10 const root = supplied ? (await mkdir(supplied), supplied) : await mkdtemp(join(tmpdir(), 'drone-browser-launch-'))
11 const executable = join(root, 'launch.ts'), pidPath = join(root, 'launch.pid')
12 let passed = false
13 const original = process.env.BROWSE_BROWSER
14 try {
15 await writeFile(executable, `#!${process.execPath}\nimport { writeFileSync } from 'node:fs'\nwriteFileSync(${JSON.stringify(pidPath)}, String(process.pid))\nconsole.error('controlled native browser startup refusal')\nprocess.exit(23)\n`, { mode: 0o700 })
16 process.env.BROWSE_BROWSER = executable
17 await expect(startWorkspaceBrowser(root)).rejects.toThrow('Fresh browser must start')
18 const actualPid = Number(await readFile(pidPath, 'utf8'))
19 const report = JSON.parse(await readFile(join(root, 'browser-launch.json'), 'utf8'))
20 expect(report.phase).toBe('startup-unavailable')
21 expect(report.launch.pid).toBe(actualPid)
22 expect(report.launch.exitObserved).toBe(true)
23 expect(report.launch.settledExitCode).toBe(23)
24 expect(report.launch.exitObservedBeforeCleanup).toBe(true)
25 expect(report.launch.cleanupRequested).toBe(true)
26 expect(report.portProbe.attempts).toBe(400)
27 expect(report.portProbe.lastReadError).toBe('ENOENT')
28 expect(report.stderr.status).toBe('retained')
29 expect(report.stderr.retainedBytes).toBeLessThanOrEqual(65536)
30 expect(await readFile(join(root, 'chrome-startup-stderr.log'), 'utf8')).toContain('controlled native browser startup refusal')
31 expect(() => process.kill(actualPid, 0)).toThrow()
32 passed = true
33 }
34 finally {
35 if (original === undefined) delete process.env.BROWSE_BROWSER
36 else process.env.BROWSE_BROWSER = original
37 // The owned native program exits23 before discovery completes; never delete a live fixture.
38 const actualPid = Number(await readFile(pidPath, 'utf8').catch(() => '0'))
39 if (actualPid > 1) {
40 let gone = false
41 try { process.kill(actualPid, 0) }
42 catch (error) { gone = (error as NodeJS.ErrnoException).code === 'ESRCH' }
43 expect(gone).toBe(true)
44 }
45 if (passed && !supplied) await rm(root, { recursive: true, force: true })
46 else console.log(JSON.stringify({ evidence: 'controlled-native-launch', retainedRoot: root, passed }))
47 }
48}, 30000)
tests/unit/public-safety-execution-browser.test.tsmodified+7-1
Changes to tests/unit/public-safety-execution-browser.test.ts
@@ -7,6 +7,7 @@ import { spawnWorkspaceFixture } from '../helpers/workspace-process'
77test.skipIf(process.env.EXECUTION_BROWSER_QA !== 'true')('compiled execution panel uses real native routes and a genuine Worker', async () => {
88 const directory = await mkdtemp(join(tmpdir(), 'drone-incident-access-'))
99 let owned: ReturnType<typeof spawnWorkspaceFixture> | undefined
10 let passed = false
1011 try {
1112 const config = join(directory, 'bunfig.toml'); await writeFile(config, 'preload=[]\n')
1213 owned = spawnWorkspaceFixture([process.execPath, `--config=${config}`, '--no-env-file', `${import.meta.dir}/../fixtures/public-safety-access/execution-browser.ts`], directory, { ...process.env, APP_ENV: 'test', APP_DEBUG: 'false', NODE_ENV: 'test', DB_CONNECTION: 'sqlite', DB_DATABASE_PATH: join(directory, 'access.sqlite'), STX_DIR: join(directory, 'stx') })
@@ -16,10 +17,15 @@ test.skipIf(process.env.EXECUTION_BROWSER_QA !== 'true')('compiled execution pan
1617 const [code, stdout, stderr] = await Promise.all([child.exited, new Response(child.stdout as ReadableStream).text(), new Response(child.stderr as ReadableStream).text()])
1718 expect(code, `${stdout}\n${stderr}`).toBe(0)
1819 expect(stdout).toContain('PASS actual execution browser: native live-before-return')
20 passed = true
1921 }
2022 finally { clearTimeout(timeout) }
2123 }
22 finally { await owned?.stop(); await rm(directory, { recursive: true, force: true }) }
24 finally {
25 await owned?.stop()
26 if (passed) await rm(directory, { recursive: true, force: true })
27 else console.log(JSON.stringify({ evidence: 'retained-private-browser-fixture', directory }))
28 }
2329}, 110000)
2430
2531test('execution browser fixture refuses an unguarded directory before database or application writes', async () => {
tests/unit/public-safety-workspace-browser.test.tsmodified+4-1
Changes to tests/unit/public-safety-workspace-browser.test.ts
@@ -8,6 +8,7 @@ import { spawnWorkspaceFixture } from '../helpers/workspace-process'
88test.skipIf(process.env.WORKSPACE_BROWSER_QA !== 'true')('actual rendered workspace with native credentials and routes', async () => {
99 const directory = await mkdtemp(join(tmpdir(), 'drone-incident-access-'))
1010 let owned: ReturnType<typeof spawnWorkspaceFixture> | undefined
11 let passed = false
1112 try {
1213 const config = join(directory, 'bunfig.toml'); await writeFile(config, 'preload=[]\n')
1314 owned = spawnWorkspaceFixture([process.execPath, `--config=${config}`, '--no-env-file', `${import.meta.dir}/../fixtures/public-safety-access/workspace-browser.ts`], directory, { ...process.env, APP_ENV: 'test', APP_DEBUG: 'false', NODE_ENV: 'test', DB_CONNECTION: 'sqlite', DB_DATABASE_PATH: join(directory, 'access.sqlite'), STX_DIR: join(directory, 'stx') })
@@ -17,12 +18,14 @@ test.skipIf(process.env.WORKSPACE_BROWSER_QA !== 'true')('actual rendered worksp
1718 const [code, stdout, stderr] = await Promise.all([child.exited, new Response(child.stdout as ReadableStream).text(), new Response(child.stderr as ReadableStream).text()])
1819 expect(code, `${stdout}\n${stderr}`).toBe(0)
1920 expect(stdout).toContain('PASS actual compiled workspace browser')
21 passed = true
2022 }
2123 finally { clearTimeout(timeout) }
2224 }
2325 finally {
2426 await owned?.stop()
25 await rm(directory, { recursive: true, force: true })
27 if (passed) await rm(directory, { recursive: true, force: true })
28 else console.log(JSON.stringify({ evidence: 'retained-private-browser-fixture', directory }))
2629 }
2730}, 110000)
2831