perf(fast-browser): compute scoped origin policy without worker dispatch
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createBrowserRuntimeFactory, withRuntimeOriginPolicy } from "./runtime.ts";
|
||||
import { createBrowserRuntimeFactory } from "./runtime.ts";
|
||||
import { BrowserOperationError } from "./contracts.ts";
|
||||
import type {
|
||||
BrowserOperation,
|
||||
BrowserRuntimeOptions,
|
||||
@@ -9,7 +10,6 @@ import type {
|
||||
BrowserTransportResultFor,
|
||||
PrivateArtifactMetadata,
|
||||
PrivateArtifactStore,
|
||||
RuntimeOperationContext,
|
||||
} from "./contracts.ts";
|
||||
|
||||
const tab = (id: string, url = "https://example.test/form") => ({
|
||||
@@ -153,18 +153,105 @@ test("factory forwards pinned Python selection to the transport", async () => {
|
||||
await runtime.close();
|
||||
});
|
||||
|
||||
test("wildcard narrowing is scoped while restricted bases cannot broaden", async () => {
|
||||
const first = artifacts();
|
||||
test("narrowing is local, lazy, and propagates only its effective context", async () => {
|
||||
const store = artifacts();
|
||||
const fixture = transportFixture();
|
||||
const runtime = createBrowserRuntimeFactory(options(first.store, fixture.transport).transportFactory).create(options(first.store, fixture.transport));
|
||||
await withRuntimeOriginPolicy(runtime, ["https://example.test"], (context) => runtime.read({}, new AbortController().signal, context));
|
||||
const reads = fixture.calls.filter((call) => call.operation.op === "read");
|
||||
assert.deepEqual((reads[0]?.context as RuntimeOperationContext).allowedOrigins, ["https://example.test"]);
|
||||
let created = 0;
|
||||
const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create(options(store.store, fixture.transport));
|
||||
|
||||
const context = await runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal);
|
||||
assert.deepEqual(context, { allowedOrigins: ["https://example.test"] });
|
||||
assert.equal(created, 0);
|
||||
assert.equal(fixture.calls.length, 0);
|
||||
|
||||
await runtime.read({}, new AbortController().signal, context);
|
||||
await runtime.read({}, new AbortController().signal);
|
||||
assert.equal(fixture.calls.at(-1)?.context, undefined);
|
||||
const restricted = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create({ ...options(first.store, fixture.transport), allowedOrigins: ["https://example.test"] });
|
||||
await assert.rejects(restricted.narrowOriginPolicy({ allowedOrigins: "*" }, new AbortController().signal), /broadened/);
|
||||
await runtime.close(); await restricted.close();
|
||||
assert.equal(created, 1);
|
||||
assert.deepEqual(fixture.calls[0]?.context, { allowedOrigins: ["https://example.test"] });
|
||||
assert.equal(fixture.calls[1]?.context, undefined);
|
||||
assert.equal(fixture.calls.some((call) => call.operation.op === "narrowOriginPolicy"), false);
|
||||
await runtime.close();
|
||||
});
|
||||
|
||||
test("narrowing validates invalid and broadened policies without dispatch", async () => {
|
||||
const store = artifacts();
|
||||
const fixture = transportFixture();
|
||||
let created = 0;
|
||||
const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create({
|
||||
...options(store.store, fixture.transport), allowedOrigins: ["https://example.test"],
|
||||
});
|
||||
|
||||
await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: [] }, new AbortController().signal), /must not be empty/);
|
||||
await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: "*" }, new AbortController().signal), (error: unknown) => {
|
||||
assert.ok(error instanceof BrowserOperationError);
|
||||
assert.equal(error.details.code, "policy_denied");
|
||||
assert.equal(error.details.mutationState, "not_attempted");
|
||||
return true;
|
||||
});
|
||||
assert.equal(created, 0);
|
||||
assert.equal(fixture.calls.length, 0);
|
||||
await runtime.close();
|
||||
});
|
||||
|
||||
test("narrowing preserves closed and aborted lifecycle errors", async () => {
|
||||
const store = artifacts();
|
||||
const fixture = transportFixture();
|
||||
const runtime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport));
|
||||
await runtime.close();
|
||||
await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => {
|
||||
assert.ok(error instanceof BrowserOperationError);
|
||||
assert.equal(error.details.code, "browser_closed");
|
||||
return true;
|
||||
});
|
||||
|
||||
const parent = new AbortController();
|
||||
parent.abort(new Error("browser session closed"));
|
||||
const abortedRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport), parent.signal);
|
||||
await assert.rejects(abortedRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => {
|
||||
assert.ok(error instanceof BrowserOperationError);
|
||||
assert.equal(error.details.code, "browser_closed");
|
||||
return true;
|
||||
});
|
||||
const operation = new AbortController();
|
||||
operation.abort();
|
||||
const liveRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport));
|
||||
await assert.rejects(liveRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, operation.signal), (error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.equal(error.name, "AbortError");
|
||||
return true;
|
||||
});
|
||||
await abortedRuntime.close();
|
||||
await liveRuntime.close();
|
||||
});
|
||||
|
||||
test("narrowing remains serialized behind an in-flight transport operation", async () => {
|
||||
const store = artifacts();
|
||||
const fixture = transportFixture();
|
||||
let releaseRead!: () => void;
|
||||
let readStarted!: () => void;
|
||||
const readGate = new Promise<void>((resolve) => { releaseRead = resolve; });
|
||||
const started = new Promise<void>((resolve) => { readStarted = resolve; });
|
||||
const transport: BrowserTransport = {
|
||||
async dispatch<Operation extends BrowserOperation>(request: BrowserTransportRequest<Operation>, signal: AbortSignal): Promise<BrowserTransportResultFor<Operation>> {
|
||||
if (request.operation.op === "read") {
|
||||
readStarted();
|
||||
await readGate;
|
||||
}
|
||||
return fixture.transport.dispatch(request, signal);
|
||||
},
|
||||
async close() { return fixture.transport.close(); },
|
||||
};
|
||||
const runtime = createBrowserRuntimeFactory({ create() { return transport; } }).create(options(store.store, transport));
|
||||
const read = runtime.read({}, new AbortController().signal);
|
||||
await started;
|
||||
let narrowed = false;
|
||||
const narrow = runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal).then(() => { narrowed = true; });
|
||||
await Promise.resolve();
|
||||
assert.equal(narrowed, false);
|
||||
releaseRead();
|
||||
await Promise.all([read, narrow]);
|
||||
assert.equal(narrowed, true);
|
||||
await runtime.close();
|
||||
});
|
||||
|
||||
test("policy is enforced before navigation and tabs are origin filtered", async () => {
|
||||
|
||||
@@ -449,6 +449,30 @@ export function createBrowserRuntime(
|
||||
return run;
|
||||
};
|
||||
|
||||
// Policy narrowing is a runtime concern, not a worker operation. Keep it
|
||||
// on the same lane so it observes the same lifecycle and serialization
|
||||
// boundaries without starting a transport or emitting JSONL.
|
||||
const invokeLocal = <T>(
|
||||
operation: BrowserRuntimeOperationName,
|
||||
signal: AbortSignal,
|
||||
action: () => T,
|
||||
): Promise<T> => {
|
||||
const run = lane.then(() => {
|
||||
if (closed && operation !== "close") throw operationError(operation, "lifecycle", "browser_closed", "browser session is closed", "not_attempted");
|
||||
if (parentSignal.aborted && operation !== "close") throw operationError(operation, "lifecycle", "browser_closed", "browser session is unavailable", "not_attempted");
|
||||
// Match invoke(): a pre-aborted operation signal is rejected before
|
||||
// dispatch/error shaping, while lifecycle failures remain typed.
|
||||
throwIfAborted(signal);
|
||||
try {
|
||||
return action();
|
||||
} catch (error) {
|
||||
throw asOperationError(error, operation, "dispatch", false);
|
||||
}
|
||||
});
|
||||
lane = run.catch(() => undefined);
|
||||
return run;
|
||||
};
|
||||
|
||||
const captureAfterFailure = async (operation: BrowserRuntimeOperationName, context?: RuntimeOperationContext): Promise<FailureCaptureSummary> => {
|
||||
if (parentSignal.aborted) return { status: "unavailable", reason: "browser session is unavailable" };
|
||||
if (operation === "captureEvidence" || operation === "close" || operation === "narrowOriginPolicy") {
|
||||
@@ -500,6 +524,10 @@ export function createBrowserRuntime(
|
||||
const controller = new AbortController();
|
||||
const timed = timedSignal(controller.signal, OPERATION_BUDGETS.close);
|
||||
try {
|
||||
// Wait for local-only operations too. A narrow policy must not race
|
||||
// session teardown, and waiting here still keeps an unused transport
|
||||
// unstarted.
|
||||
await lane;
|
||||
if (transport) {
|
||||
await invoke("close", { op: "close" }, timed.signal, undefined, () => undefined).catch(() => undefined);
|
||||
await transport.close().catch(() => {});
|
||||
@@ -612,11 +640,7 @@ export function createBrowserRuntime(
|
||||
const allowedOrigins = normalizePolicy(request.allowedOrigins);
|
||||
if (basePolicy !== "*" && (allowedOrigins === "*" || allowedOrigins.some((origin) => !basePolicy.includes(origin)))) throw operationError("narrowOriginPolicy", "policy", "policy_denied", "origin policy cannot be broadened", "not_attempted");
|
||||
const context = { allowedOrigins };
|
||||
const result = await invoke("narrowOriginPolicy", { op: "narrowOriginPolicy", request: { allowedOrigins } }, signal, { allowedOrigins: basePolicy }, (raw) => {
|
||||
if (!raw || typeof raw !== "object" || !("allowedOrigins" in raw)) throw new Error("worker returned invalid origin policy");
|
||||
return context;
|
||||
});
|
||||
return result;
|
||||
return invokeLocal("narrowOriginPolicy", signal, () => context);
|
||||
},
|
||||
close,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user