Skip to content

Commit ac4461f

Browse files
committed
feat(browser): compose paired client host lifecycle
1 parent b4b0e97 commit ac4461f

8 files changed

Lines changed: 766 additions & 41 deletions

config/reliability-gates.jsonc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,7 @@
25702570
"bounded client page-command dispatch and replay",
25712571
"server-owned page-command issue and result ledger",
25722572
"same-socket paired-runtime control requests",
2573+
"paired desktop browser-host lifecycle composition",
25732574
"dedicated paired-runtime E2EE tunnel subscription"
25742575
],
25752576
"platforms": ["macos", "linux", "windows"],
@@ -2580,7 +2581,7 @@
25802581
"motivatingLinks": [
25812582
"https://linear.app/stably/issue/STA-4150/refactor-remote-browser-to-client-hosted-electron-webviews"
25822583
],
2583-
"invariant": "A client-hosted browser network route exists only for an exact live server-owned browser-host lease, authority epoch, host generation, paired identity, execution-host revision or SSH provider authority, and server-owned route generation. A client page is usable only when its runtime ID, authority epoch, host identity and generation, logical page ID, page-host generation, current placement, and still-live lease all match atomically; a retired, retirement-pending, or server-placed page cannot be claimed as live client placement. Live logical placements and their identities are bounded, an occupied logical page cannot change placement until exact two-phase retirement completes, cancellation restores the same authority, and the runtime-wide page generation stays monotonic without tombstones. Server-issued page commands and results remain bounded, FIFO per page, immutable after admission, authenticated to the exact paired lease, attach connection, and live placement, replayable only when byte-equivalent, and outcome-unknown after authority or result-transport loss. Non-native descriptors require explicit capability negotiation and an exact runtime-minted lease-bound execution-host grant. Host selection never chooses arbitrarily, stale cleanup or settlement cannot remove a replacement, destination names remain unresolved until the execution host, credit returns only after writes settle, and route loss never falls back to desktop DNS or sockets. A main-owned local listener remains stable across upstream transport loss, rejects CONNECT while offline, accepts only a strictly newer tunnel generation, ignores superseded callbacks, and never reuses a stream ID within one generation. Pending opens, admitted open rate, nested control requests, per-route application bytes, aggregate browser-host/process bytes, claims, and socket sources remain bounded with exact release on settlement and retirement. Queued transport drain yields after a bounded quantum. The local SOCKS endpoint accepts only loopback CONNECT and rejects unsupported commands.",
2584+
"invariant": "A client-hosted browser network route exists only for an exact live server-owned browser-host lease, authority epoch, host generation, paired identity, execution-host revision or SSH provider authority, and server-owned route generation. A client page is usable only when its runtime ID, authority epoch, host identity and generation, logical page ID, page-host generation, current placement, and still-live lease all match atomically; a retired, retirement-pending, or server-placed page cannot be claimed as live client placement. Live logical placements and their identities are bounded, an occupied logical page cannot change placement until exact two-phase retirement completes, cancellation restores the same authority, and the runtime-wide page generation stays monotonic without tombstones. Server-issued page commands and results remain bounded, FIFO per page, immutable after admission, authenticated to the exact paired lease, attach connection, and live placement, replayable only when byte-equivalent, and outcome-unknown after authority or result-transport loss. Desktop composition installs exact command authority before delivery, keeps result settlements below nested-request capacity, and closes transport before aborting owned handlers. Non-native descriptors require explicit capability negotiation and an exact runtime-minted lease-bound execution-host grant. Host selection never chooses arbitrarily, stale cleanup or settlement cannot remove a replacement, destination names remain unresolved until the execution host, credit returns only after writes settle, and route loss never falls back to desktop DNS or sockets. A main-owned local listener remains stable across upstream transport loss, rejects CONNECT while offline, accepts only a strictly newer tunnel generation, ignores superseded callbacks, and never reuses a stream ID within one generation. Pending opens, admitted open rate, nested control requests, per-route application bytes, aggregate browser-host/process bytes, claims, and socket sources remain bounded with exact release on settlement and retirement. Queued transport drain yields after a bounded quantum. The local SOCKS endpoint accepts only loopback CONNECT and rejects unsupported commands.",
25842585
"oracle": "Attach two browser hosts and require unqualified selection to fail ambiguous while exact selection succeeds. Reject a second identity on one connection and a fifth identity for one paired device, release one exact lease, then admit its replacement without starving another device. Saturate the browser-host long-poll sub-cap, require an ordinary wait to remain admitted, close the socket, and prove both counters and handlers release. Replace one same-device connection, require the old subscription and route to fence, then prove late old cleanup leaves the new generation live. Mint an exact execution-host grant, reject a different key, prove late old grant cleanup leaves its replacement, and invalidate every grant with lease replacement. Reject client-to-client, client-to-server, server-to-server, and server-to-client placement overwrite until exact retirement. Begin retirement once idempotently, reject client use and every replacement while pending, cancel the exact token to restore the same placement, and reject delayed cancellation or completion after a newer retirement begins. Complete only the exact pending token, retain capacity until completion, and require a reused client page ID to receive a higher global generation without retaining a tombstone. Require a client page through the exact runtime, epoch, host, and page tuple; reject every mismatch, released or replaced leases, retirement-pending pages, retired pages, server placements, invalid identities, and the 257th live placement. Admit 16 pending destination opens and reject the seventeenth until one connects; admit 128 opens in one monotonic 10-second window and reject the 129th until the window expires. Fill 8 MiB across destination-to-client queues and separately across unsettled client-to-destination writes, release one exact retired stream, admit one replacement, and require the next byte claim to close the route without stale-callback or reentrant-close underflow. Across multiple routes, charge application copies, encrypted queue entries, and dynamic native socket bytes to one 32 MiB browser-host and 128 MiB process ceiling; reject zero-byte claim/socket-source floods, fence released leases, synchronously release JavaScript queue claims while retaining native claims through exact socket close, and yield a parked queue after four frames without reordering. Route nested responses by exact ID under reordering, reject unknown and duplicate IDs, cap pending requests and queued bytes, and tear down every concurrent waiter once on timeout or close. Drive one real paired attach, command, and result through one exact connection ID; reject missing send support and malformed or wrong-runtime acknowledgements while preserving old non-v1 attaches. Drop an authenticated transport, require the same listener to reject CONNECT before and during reconnect, attach the exact existing native v1 payload and capability pair to a strictly newer generation, ignore late old callbacks, and roll an exhausted stream-ID ledger only by replacing the generation. Require SSH descriptors to add an execution-host capability and exact grant, reject missing grants and stale provider authority before resolver or binary registration, pass exact domains to ssh2 and system-SSH SOCKS without local resolution, sanitize connector diagnostics, and close every stream and standalone route process once on timeout, synchronous failure, or rotation. Drive the accepted lease through real paired E2EE native SOCKS-to-HTTP and ephemeral Docker ssh2 remote-only DNS journeys, then exercise exact frame, credit, half-close, stale-stream, remote-DNS, unsupported-command, offline, and teardown assertions.",
25852586
"commands": [
25862587
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host-page-retirement.test.ts src/main/runtime/browser-host-page-placement-replacement.test.ts src/main/runtime/browser-host-page-placement.test.ts src/main/runtime/browser-host-lease-registry.test.ts",
@@ -2592,6 +2593,7 @@
25922593
"pnpm exec vitest run --config config/vitest.config.ts src/shared/remote-runtime-client.test.ts",
25932594
"pnpm exec vitest run --config config/vitest.config.ts src/shared/remote-runtime-subscription-request.test.ts",
25942595
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-client-host-command-dispatcher.test.ts",
2596+
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/paired-runtime-browser-client-host.test.ts src/main/browser/paired-runtime-browser-host-lease.test.ts src/main/browser/browser-client-host-command-dispatcher.test.ts",
25952597
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host-command-ledger.test.ts src/main/runtime/browser-host-command-ledger-capacity.test.ts src/main/runtime/browser-host-lease-registry.test.ts src/main/runtime/rpc/methods/browser-client-host.test.ts",
25962598
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser-host-lease-registry.test.ts src/main/browser/browser-network-deferred-socket.test.ts src/main/browser/browser-network-execution-route.test.ts src/main/browser/paired-runtime-browser-network-route.test.ts src/main/runtime/rpc/methods/browser-network-tunnel.test.ts src/main/browser/ssh-browser-network-execution-route.test.ts src/main/browser/system-ssh-socks-client-socket.test.ts src/main/ssh/system-ssh-dynamic-forward-process.test.ts src/shared/browser-client-host-protocol.test.ts src/shared/browser-network-capabilities.test.ts src/main/ssh/system-ssh-forward-process.test.ts src/main/ssh/ssh-system-fallback.test.ts src/main/ssh/ssh-port-forward.test.ts",
25972599
"ORCA_RUN_DOCKER_SSH_BROWSER_E2E=1 pnpm exec vitest run --config config/vitest.config.ts tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts"
@@ -2609,6 +2611,7 @@
26092611
"src/main/runtime/runtime-rpc-browser-host-admission.test.ts",
26102612
"src/main/runtime/rpc/methods/browser-client-host.test.ts",
26112613
"src/main/browser/paired-runtime-browser-host-lease.test.ts",
2614+
"src/main/browser/paired-runtime-browser-client-host.test.ts",
26122615
"src/main/browser/browser-client-host-command-dispatcher.test.ts",
26132616
"src/main/browser/browser-network-tunnel-session.test.ts",
26142617
"src/main/browser/browser-network-tunnel-session-aggregate-memory.test.ts",
@@ -2699,7 +2702,19 @@
26992702
"stale authority commands close the exact lease before handler delivery",
27002703
"completed and failed results submit exact command authority on the established subscription",
27012704
"missing result transport, handler failure, server rejection, and malformed or wrong-runtime acknowledgement close the lease",
2702-
"exact duplicate result acknowledgement remains idempotent"
2705+
"exact duplicate result acknowledgement remains idempotent",
2706+
"result settlement concurrency stays below nested-request capacity and excess unsettled results fail closed"
2707+
]
2708+
},
2709+
{
2710+
"file": "src/main/browser/paired-runtime-browser-client-host.test.ts",
2711+
"assertions": [
2712+
"exact dispatcher authority exists before the first same-socket command can arrive",
2713+
"explicit close shuts transport before aborting every owned page handler",
2714+
"dispatcher abort still runs when lease cleanup rejects",
2715+
"lease transport failure aborts every owned handler and reports one terminal error",
2716+
"retirement and forgetting accept only the exact owned page generation",
2717+
"timed-out retirement stays fenced until the exact late handler settles"
27032718
]
27042719
},
27052720
{
@@ -2935,6 +2950,15 @@
29352950
}
29362951
],
29372952
"evidenceRuns": [
2953+
{
2954+
"date": "2026-08-14",
2955+
"runner": "local",
2956+
"platform": "macos",
2957+
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/paired-runtime-browser-client-host.test.ts src/main/browser/paired-runtime-browser-host-lease.test.ts src/main/browser/browser-client-host-command-dispatcher.test.ts",
2958+
"result": "passed",
2959+
"durationSeconds": 0.8,
2960+
"summary": "Fifty tests passed synchronous authority activation, hard 16/256 result bounds, exact transport-close-before-handler-abort ordering, cleanup rejection with guaranteed abort, same-socket result settlement, exact page retirement and forgetting, and late non-cooperative retirement settlement. The broader runtime/browser/shared run passed 9,711 tests with 24 existing skips."
2961+
},
29382962
{
29392963
"date": "2026-08-14",
29402964
"runner": "local",

src/main/browser/browser-client-host-command-authority.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,16 @@ export function snapshotBrowserClientHostLeaseAuthority(
2323
): BrowserClientHostLeaseAuthority {
2424
return Object.freeze({ ...authority })
2525
}
26+
27+
export function sameBrowserClientHostLeaseAuthority(
28+
left: BrowserClientHostLeaseAuthority,
29+
right: BrowserClientHostLeaseAuthority
30+
): boolean {
31+
return (
32+
left.authorityRuntimeId === right.authorityRuntimeId &&
33+
left.authorityEpoch === right.authorityEpoch &&
34+
left.browserHostClientId === right.browserHostClientId &&
35+
left.browserHostGeneration === right.browserHostGeneration &&
36+
left.pageCommandProtocolVersion === right.pageCommandProtocolVersion
37+
)
38+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import type {
2+
BrowserClientHostCommandEvent,
3+
BrowserClientHostCommandResult
4+
} from '../../shared/browser-client-host-protocol'
5+
6+
// Preserve half of the subscription's 32 nested-request slots for future control traffic.
7+
const DEFAULT_MAX_CONCURRENT_COMMAND_RESULTS = 16
8+
// Mirror the server ledger ceiling so an honest host never over-admits completed work.
9+
const DEFAULT_MAX_UNSETTLED_COMMAND_RESULTS = 256
10+
11+
export type BrowserHostCommandResultAdmission = { active: boolean }
12+
13+
type PendingCommandResult = {
14+
admission: BrowserHostCommandResultAdmission
15+
command: BrowserClientHostCommandEvent
16+
result: BrowserClientHostCommandResult
17+
rejectReady: (error: Error) => void
18+
}
19+
20+
type BrowserHostCommandResultSettlerOptions = {
21+
maxConcurrent?: number
22+
maxUnsettled?: number
23+
submit: (
24+
command: BrowserClientHostCommandEvent,
25+
result: BrowserClientHostCommandResult
26+
) => Promise<void>
27+
onError: (error: Error, rejectReady: (error: Error) => void) => void
28+
}
29+
30+
export class BrowserHostCommandResultSettler {
31+
private readonly maxConcurrent: number
32+
private readonly maxUnsettled: number
33+
private readonly pending: PendingCommandResult[] = []
34+
private unsettled = 0
35+
private inFlight = 0
36+
private closed = false
37+
38+
constructor(private readonly options: BrowserHostCommandResultSettlerOptions) {
39+
this.maxConcurrent = boundedLimit(options.maxConcurrent, DEFAULT_MAX_CONCURRENT_COMMAND_RESULTS)
40+
this.maxUnsettled = boundedLimit(options.maxUnsettled, DEFAULT_MAX_UNSETTLED_COMMAND_RESULTS)
41+
if (this.maxConcurrent > this.maxUnsettled) {
42+
throw new Error('Browser host command result limits are inconsistent')
43+
}
44+
}
45+
46+
admit(): BrowserHostCommandResultAdmission | null {
47+
if (this.closed || this.unsettled >= this.maxUnsettled) {
48+
return null
49+
}
50+
this.unsettled += 1
51+
return { active: true }
52+
}
53+
54+
enqueue(
55+
admission: BrowserHostCommandResultAdmission,
56+
command: BrowserClientHostCommandEvent,
57+
result: BrowserClientHostCommandResult,
58+
rejectReady: (error: Error) => void
59+
): void {
60+
if (this.closed) {
61+
this.release(admission)
62+
return
63+
}
64+
this.pending.push({ admission, command, result, rejectReady })
65+
this.drain()
66+
}
67+
68+
release(admission: BrowserHostCommandResultAdmission): void {
69+
if (!admission.active) {
70+
return
71+
}
72+
admission.active = false
73+
this.unsettled -= 1
74+
}
75+
76+
close(): void {
77+
if (this.closed) {
78+
return
79+
}
80+
this.closed = true
81+
for (const pending of this.pending) {
82+
this.release(pending.admission)
83+
}
84+
this.pending.length = 0
85+
}
86+
87+
private drain(): void {
88+
while (!this.closed && this.inFlight < this.maxConcurrent && this.pending.length > 0) {
89+
const pending = this.pending.shift()
90+
if (!pending) {
91+
return
92+
}
93+
this.inFlight += 1
94+
void Promise.resolve()
95+
.then(() => this.options.submit(pending.command, pending.result))
96+
.catch((error) =>
97+
this.options.onError(
98+
error instanceof Error ? error : new Error(String(error)),
99+
pending.rejectReady
100+
)
101+
)
102+
.finally(() => {
103+
this.inFlight -= 1
104+
this.release(pending.admission)
105+
this.drain()
106+
})
107+
}
108+
}
109+
}
110+
111+
function boundedLimit(value: number | undefined, maximum: number): number {
112+
const resolved = value ?? maximum
113+
if (!Number.isInteger(resolved) || resolved < 1 || resolved > maximum) {
114+
throw new Error('Browser host command result limit is invalid')
115+
}
116+
return resolved
117+
}

0 commit comments

Comments
 (0)