Files
orca/src
Brennan Benson 9312c5cd9b fix(browser): serialize cookie imports per partition (#15095)
* fix(browser): serialize cookie imports per partition (STA-4601)

Two concurrent cookie imports on one partition can erase a session. Nothing
serialises imports per partition — neither the renderer IPC handler
(browser-session-profile-ipc.ts) nor the runtime RPC method
(orca-runtime-browser.ts) — and the clear lock covered only the clear itself,
so it was released before the writes and before any rollback.

Reachable interleaving:

  import A replaces cookies for its imported domains, releases the lock, and
  later fails a write; import B clears and writes; A's rollback then removes
  cookies B already wrote and reported as imported.

Path B has the same shape: A clears, B clears, then A writes its cookies on top
of B's jar.

Fix: one lock spans the whole live-jar transaction on both paths — the
clear/replace, the writes, and the rollback. withCookieClearLock becomes
acquireCookieMutationLock/withCookieMutationLock so path A can hold it across a
try/finally rather than a single callback.

Rebased onto #15030 (STA-4300), which rewrote this file: imports now write
through CDP identities, so path A's writes moved from an inline cookies.set()
loop into writeImportedCookies(), path B's into the same helper, and — the part
that matters for a lock — importValidatedCookies no longer holds the Electron
Session at all. It receives a CookieImportTarget that hides the Session behind
openWriteStore(), and openWriteStore() builds a FRESH adapter per call, so
keying the lock on anything reachable from it would serialise nothing. The
target therefore carries mutationLockOwner, set to session.fromPartition's
instance, which is the same object the native path locks on. That is what keeps
both paths on one key per partition.

Deliberately bounded: this covers the LIVE JAR only. Staging and cold-start
replay keep their existing semantics, so two other pre-existing hazards in that
subsystem are untouched and still open — the crash window between the clear and
the result, and a permanently-armed replay when replay keeps failing. Both need
the pending-image operations to become provable, which is a different change.

The lock is keyed per owner, so imports into different partitions still run
concurrently.

Mutation-proved against the rebased tree, every mutation structure-preserving
(brace/paren balance pinned) and every one re-run rather than carried over:
dropping path A's acquire reddens the file-import detector; keying path A's
acquire on a fresh object instead of mutationLockOwner reddens it too — the
lock-present-but-miskeyed shape this rebase risks; replacing path B's lock with
a passthrough reddens the native detector; doing both reddens both, which rules
out incidental serialisation; and neutering the cold-init probe's lock reddens
the new probe detector. That last detector is new: neutering the probe lock
previously left all 779 browser tests green, so the probe's protection was
unproven.

The concurrency suite's store stub also gained writeCookieIdentity and
getStoragePath. Without them the STA-4300 code throws a TypeError that
writeImportedCookies catches as a write rejection, so the imports would have
taken the failure path while the ordering assertions still passed. Each real
import test now asserts writeCookieIdentity was CALLED, and that cookies.set
never was.

src/main/browser: 72 files, 780 tests, green; typecheck, oxlint, type-aware
code quality, oxfmt and the max-lines ratchet all clean.

* fix(browser): serialize native cookie staging

* test(browser): pin native flush serialization

* test(browser): pin staged cookie replay ordering

* test(browser): remove vacuous staged replay detector

* test(browser): detect stale native staging images

* test(browser): guard cookie import ordering events

* test(browser): cover staging lock boundary
2026-08-18 15:32:13 -07:00
..