test(serve): strengthen renderer containment contract

This commit is contained in:
Jinwoo-H
2026-08-23 18:50:29 -04:00
parent d00a53d62c
commit d4dfaddf9b
2 changed files with 84 additions and 6 deletions
+77 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"updatedAt": "2026-08-20",
"updatedAt": "2026-08-23",
"policy": {
"maturityLevels": ["experimental", "soak", "blocking", "accepted-gap", "deprecated"],
"blockingPromotion": {
@@ -2361,6 +2361,82 @@
],
"demotionRule": "Demote if a reload revision settles more than once, a stale document or competing window can publish, a timer survives settlement, desktop failure stays indefinitely reloading, headless fallback returns after successful promotion, or either isolated Electron journey loses runtime or PTY identity."
},
{
"id": "runtime.headless-renderer-process-containment",
"title": "Headless renderer process death stays isolated from runtime-owned PTYs",
"maturity": "experimental",
"protection": "partial",
"owner": "runtime-platform",
"layer": "electron-process-containment",
"surfaces": ["headless orca serve", "offscreen browser page", "daemon-backed PTYs"],
"platforms": ["macos", "linux", "windows"],
"providers": ["local", "daemon"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "daemon"],
"coverageNotes": "An isolated headless serve host creates two daemon-backed terminal sessions and one offscreen browser renderer. Exact WebContents and OS-process fencing prevents a fault from reaching an unrelated process. The oracle is host-local and does not claim paired-viewer continuity, renderer reload, systemd cgroup restart persistence, SSH or WSL coverage, or aggregate memory policy.",
"motivatingLinks": [
"https://linear.app/stably/issue/STA-5228",
"https://github.com/stablyai/orca/issues/16084",
"https://github.com/stablyai/orca/pull/16120"
],
"invariant": "Killing one exact offscreen Chromium renderer cannot replace or terminate the headless main process, runtime, daemon, or unrelated daemon-backed PTYs. The host session inventory retains each original tab-to-PTY binding, and both original PTY processes continue accepting input.",
"oracle": "Start an isolated headless serve runtime with zero initial BrowserWindows, create two daemon-backed terminal fixtures, and record main, runtime, daemon, tab, PTY, fixture-process, WebContents, and renderer-process identities. Fence one marked offscreen page by WebContents, URL, and OS process, force that renderer to crash, and require its original process to die while main/runtime/daemon identities, both host tab-to-PTY bindings, and I/O through both original fixture processes remain unchanged.",
"commands": [
"pnpm exec electron-vite build --mode e2e",
"SKIP_BUILD=1 pnpm exec playwright test tests/e2e/headless-serve-renderer-kill-retention.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1"
],
"testFiles": ["tests/e2e/headless-serve-renderer-kill-retention.spec.ts"],
"assertionRefs": [
{
"file": "tests/e2e/headless-serve-renderer-kill-retention.spec.ts",
"assertions": [
"the exact original renderer process dies after the fenced WebContents fault",
"main, runtime, and daemon identities remain unchanged and reachable",
"both original host tab-to-PTY bindings remain published",
"both original PTY processes accept post-fault input"
]
}
],
"evidenceRuns": [
{
"date": "2026-08-23",
"runner": "local",
"platform": "macos",
"command": "SKIP_BUILD=1 pnpm exec playwright test tests/e2e/headless-serve-renderer-kill-retention.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
"result": "passed",
"durationSeconds": 31,
"summary": "The exact offscreen renderer process died while the headless main process, runtime, daemon, both original host tab-to-PTY bindings, and both original PTY processes remained live; both PTYs accepted post-fault input."
}
],
"runtimeBudget": {
"p95Seconds": 60,
"scope": "one isolated headless Electron host, one offscreen renderer fault, and two daemon-backed PTY fixtures"
},
"flakeHistory": {
"status": "unknown",
"evidence": "Fresh local macOS runs are green; CI and cross-platform soak history are not yet available."
},
"redGreenEvidence": {
"status": "partial",
"evidence": "The byte-identical oracle passes on reported v1.4.184 and current main, falsifying renderer-only death as the cause of runtime/session loss. The candidate adds characterization coverage only; reverting it removes the oracle without changing product behavior."
},
"performanceBudget": {
"required": false,
"evidence": "The change adds test and gate metadata only. Production startup, fault handling, session inventory, and PTY hot paths are unchanged."
},
"promotionCriteria": [
"Pass the changed-spec Ubuntu/Xvfb PR lane.",
"Collect at least 100 consecutive CI or soak passes or 14 days without an unexplained flake.",
"Add Windows containment evidence if Electron process behavior diverges."
],
"knownGaps": [
"The fault is an exact WebContents renderer crash, not kernel OOM victim selection or systemd cgroup failure.",
"The host-local runtime client proves control-plane reachability but not a separate paired viewer, reconnect, or replay path.",
"The test does not require the crashed browser surface to recover; browser-page recovery is a separate lifecycle contract.",
"SSH, WSL, folder-workspace, multi-client, mixed-version, and whole-service restart journeys are uncollected; no production or wire behavior changes in this gate."
],
"demotionRule": "Demote if the exact renderer fault changes main/runtime/daemon identity, removes either tab-to-PTY binding, replaces either fixture process, loses post-fault PTY I/O, or cannot identify the killed renderer unambiguously."
},
{
"id": "runtime.websocket-heartbeat-cadence",
"title": "Runtime WebSockets enter an owned shared heartbeat cadence",
@@ -7,6 +7,7 @@ import { expect, test } from './helpers/orca-app'
import {
createHostCliTerminal,
proveSameLivePty,
readHostTerminalInventory,
writeRetentionFixture
} from './helpers/host-created-terminal-retention-oracle'
import { launchHeadlessPairedRuntimeHost } from './helpers/headless-paired-runtime-host'
@@ -114,6 +115,9 @@ test('STA-5228 renderer death keeps headless runtime and PTYs alive', async ({ t
)
await proveSameLivePty(call, terminalA, 'BEFORE_RENDERER_KILL_A')
await proveSameLivePty(call, terminalB, 'BEFORE_RENDERER_KILL_B')
const beforeInventory = await readHostTerminalInventory(call, worktreeId)
expect(beforeInventory.ptyIdByTabId[terminalA.tabId]).toBe(terminalA.ptyId)
expect(beforeInventory.ptyIdByTabId[terminalB.tabId]).toBe(terminalB.ptyId)
const pageId = `sta-5228-${Date.now()}`
const markerUrl = `data:text/html,${encodeURIComponent(
@@ -212,13 +216,11 @@ test('STA-5228 renderer death keeps headless runtime and PTYs alive', async ({ t
expect(await processIdentityIsAlive(daemonIdentity)).toBe(true)
await proveSameLivePty(call, terminalA, 'AFTER_RENDERER_KILL_A')
await proveSameLivePty(call, terminalB, 'AFTER_RENDERER_KILL_B')
const afterInventory = await readHostTerminalInventory(call, worktreeId)
expect(afterInventory.ptyIdByTabId[terminalA.tabId]).toBe(terminalA.ptyId)
expect(afterInventory.ptyIdByTabId[terminalB.tabId]).toBe(terminalB.ptyId)
const afterRenderer = await readOffscreenRenderer(launchedHost.app, pageId, false)
expect(afterRenderer).toMatchObject({
crashed: true,
pid: 0,
webContentsId: renderer.webContentsId
})
console.log('[STA-5228] renderer lifecycle snapshot', {
afterRenderer,
beforeRenderer: renderer,