import { mkdtempSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path"; import { describe, expect, it } from "@effect/vitest";import { Effect, Logger } from "effect";import { withQueryContext } from "@executor-js/fumadb/query"; import { createExecutor } from "./executor";import { StorageError, type FumaDb } from "./fuma-runtime";import { AuthTemplateSlug, ConnectionName, IntegrationSlug, OAuthClientSlug, ProviderItemId, ProviderKey, ToolName,} from "./ids";import { definePlugin } from "./plugin";import { makeTestConfig, makeTestWorkspaceHarness } from "./test-config";import { serveOAuthTestServer } from "./testing/oauth-test-server"; // An in-flight authorization flow parks its PKCE verifier in `oauth_session` in// plaintext, which is fine while the flow can still spend it. What is not fine is// leaving it there after the flow has died: the happy path and `cancel` delete the// row, but a completion that FAILED did not, and nothing sweeps the table, so the// verifier outlived the flow indefinitely.//// Paired, like every deletion test: an unredeemable session must go, and a// perfectly good one sitting beside it must not. const INTEG = IntegrationSlug.make("acme");const TEMPLATE = AuthTemplateSlug.make("oauth");const CLIENT = OAuthClientSlug.make("acme-app"); const memoryCredentialsPlugin = definePlugin(() => { const store = new Map<string, string>(); return { id: "memory-credentials" as const, storage: () => ({}), credentialProviders: [ { key: ProviderKey.make("memory"), writable: true as const, get: (id: ProviderItemId) => Effect.sync(() => store.get(String(id)) ?? null), set: (id: ProviderItemId, value: string) => Effect.sync(() => { store.set(String(id), value); }), delete: (id: ProviderItemId) => Effect.sync(() => { store.delete(String(id)); }), }, ], };})(); const acmePlugin = definePlugin(() => ({ id: "acme" as const, storage: () => ({}), resolveTools: () => Effect.succeed({ tools: [{ name: ToolName.make("whoami"), description: "whoami" }] }), describeAuthMethods: () => [ { id: "oauth", label: "OAuth2", kind: "oauth" as const, template: String(TEMPLATE), oauth: { scopes: [] }, }, ], invokeTool: ({ credential }) => Effect.succeed({ token: credential.value }), extension: (ctx) => ({ seed: () => ctx.core.integrations.register({ slug: INTEG, description: "Acme", config: {} }), }),}))(); interface SessionRow { readonly pkce_verifier?: string | null; readonly expires_at?: number | null;} /** Read a session straight from storage through the given executor's own db * handle, so the read is scoped exactly like that executor's writes. */const readSession = (db: FumaDb, state: string) => Effect.promise( () => db.findFirst("oauth_session", { where: (b) => b("state", "=", state), }) as Promise<SessionRow | null>, ); /** A test db whose `deleteMany` on ONE table always rejects — the storage fault * the sweep has to survive. `withContext` is forwarded through the wrapper * because the executor re-derives its own owner context from whatever db it is * handed; without that the fault would be unwrapped away on construction. */const withFailingDeletes = (db: FumaDb, table: string): FumaDb => new Proxy(db, { get(target, property) { if (property === "withContext") { return (context: unknown) => withFailingDeletes(withQueryContext(target, context), table); } if (property === "deleteMany") { return async (name: string, ...rest: readonly unknown[]) => { if (name === table) { // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: a FumaDB query reports failure by rejecting, which is the fault being simulated throw new StorageError({ message: `simulated storage failure deleting from "${table}"`, cause: undefined, }); } const forward = Reflect.get(target, property) as ( ...args: readonly unknown[] ) => Promise<unknown>; return forward.call(target, name, ...rest); }; } return Reflect.get(target, property); }, }); describe("a dead authorization flow does not keep its PKCE verifier", () => { it.effect("sweeps an expired verifier the next time authorization starts", () => Effect.scoped( Effect.gen(function* () { const server = yield* serveOAuthTestServer({}); const { executor, config } = yield* makeTestWorkspaceHarness({ plugins: [memoryCredentialsPlugin, acmePlugin] as const, }); yield* executor.acme.seed(); yield* executor.oauth.createClient({ owner: "org", slug: CLIENT, authorizationUrl: server.authorizationEndpoint, tokenUrl: server.tokenEndpoint, grant: "authorization_code", clientId: "test-client", clientSecret: "test-secret", }); // An abandoned flow: started, never returned to. Nothing completes it, so // the lazy expiry check in `complete` never runs for it. const abandoned = yield* executor.oauth.start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("abandoned"), integration: INTEG, template: TEMPLATE, }); if (abandoned.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } // A live flow started beside it, which must survive the sweep. const live = yield* executor.oauth.start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("live"), integration: INTEG, template: TEMPLATE, }); if (live.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } // Age only the abandoned one past its expiry. yield* Effect.promise(() => config.db.updateMany("oauth_session", { where: (b) => b("state", "=", String(abandoned.state)), set: { expires_at: Date.now() - 60_000 }, }), ); expect((yield* readSession(config.db, String(abandoned.state)))?.pkce_verifier).toEqual( expect.any(String), ); // Starting any authorization is what tidies up. const third = yield* executor.oauth.start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("third"), integration: INTEG, template: TEMPLATE, }); if (third.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } expect(yield* readSession(config.db, String(abandoned.state))).toBeNull(); // The unexpired flow is untouched — a sweep must not cancel someone // else's authorization mid-flight. expect((yield* readSession(config.db, String(live.state)))?.pkce_verifier).toEqual( expect.any(String), ); }), ), ); it.effect("drops the session when the completion cannot be retried", () => Effect.scoped( Effect.gen(function* () { const server = yield* serveOAuthTestServer({}); const { executor, config } = yield* makeTestWorkspaceHarness({ plugins: [memoryCredentialsPlugin, acmePlugin] as const, }); yield* executor.acme.seed(); yield* executor.oauth.createClient({ owner: "org", slug: CLIENT, authorizationUrl: server.authorizationEndpoint, tokenUrl: server.tokenEndpoint, grant: "authorization_code", clientId: "test-client", clientSecret: "test-secret", }); const dying = yield* executor.oauth.start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("dying"), integration: INTEG, template: TEMPLATE, }); if (dying.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } const dyingCallback = yield* server.completeAuthorizationCodeFlow({ authorizationUrl: dying.authorizationUrl, }); const bystander = yield* executor.oauth.start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("bystander"), integration: INTEG, template: TEMPLATE, }); if (bystander.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } // The verifier really is sitting there in plaintext. const before = yield* readSession(config.db, String(dying.state)); expect(before?.pkce_verifier).toEqual(expect.any(String)); // Remove the app this flow was started against, so completion fails with // restartRequired — this state can never be redeemed again. yield* executor.oauth.removeClient("org", CLIENT); const failed = yield* Effect.flip( executor.oauth.complete({ state: dying.state, code: dyingCallback.code }), ); expect(JSON.stringify(failed)).toContain("restartRequired"); expect(yield* readSession(config.db, String(dying.state))).toBeNull(); // The other flow is still live and untouched — a cleanup must not sweep // sessions it was not asked about. const survivor = yield* readSession(config.db, String(bystander.state)); expect(survivor?.pkce_verifier).toEqual(expect.any(String)); }), ), ); // The sweep runs with no owner clause of its own — it trusts the table's // delete policy to scope it. That trust is the whole safety argument, and it // is invisible at the call site, so it is pinned here: one member starting an // authorization must never reach into another member's rows, however stale // they are. Two subjects on ONE database is the only way to observe it. it.effect("one member's sweep leaves another member's expired session alone", () => Effect.scoped( Effect.gen(function* () { const server = yield* serveOAuthTestServer({}); const dataDir = mkdtempSync(join(tmpdir(), "oauth-session-sweep-")); const tenant = "shared-tenant"; const plugins = [memoryCredentialsPlugin, acmePlugin] as const; const a = yield* makeTestWorkspaceHarness({ plugins, tenant, subject: "subject-a", dataDir, }); yield* a.executor.acme.seed(); yield* a.executor.oauth.createClient({ owner: "org", slug: CLIENT, authorizationUrl: server.authorizationEndpoint, tokenUrl: server.tokenEndpoint, grant: "authorization_code", clientId: "test-client", clientSecret: "test-secret", }); // A second member of the same tenant, on the same database. const b = yield* makeTestWorkspaceHarness({ plugins, tenant, subject: "subject-b", dataDir, }); // Both members abandon a USER-owned flow. `owner: "user"` is what makes // the rows private to each subject; an org row is tenant-shared by // design and its removal by any member is correct. const startUserFlow = (harness: typeof a, name: string) => Effect.gen(function* () { const started = yield* harness.executor.oauth.start({ owner: "user", client: CLIENT, clientOwner: "org", name: ConnectionName.make(name), integration: INTEG, template: TEMPLATE, }); if (started.status !== "redirect") { return yield* Effect.die("expected a redirect-status OAuth start"); } return started; }); const expire = (db: FumaDb, state: string) => Effect.promise(() => db.updateMany("oauth_session", { where: (builder) => builder("state", "=", state), set: { expires_at: Date.now() - 60_000 }, }), ); const bStale = yield* startUserFlow(b, "b-stale"); yield* expire(b.config.db, String(bStale.state)); const aStale = yield* startUserFlow(a, "a-stale"); yield* expire(a.config.db, String(aStale.state)); // B's row really is a sweep candidate — otherwise surviving would prove // nothing about scoping. const bBefore = yield* readSession(b.config.db, String(bStale.state)); expect(Number(bBefore?.expires_at)).toBeLessThan(Date.now()); // A starts again. Its sweep must reach its OWN expired row and no // further. yield* startUserFlow(a, "a-fresh"); expect(yield* readSession(a.config.db, String(aStale.state))).toBeNull(); expect((yield* readSession(b.config.db, String(bStale.state)))?.pkce_verifier).toEqual( expect.any(String), ); }), ), ); // Best-effort must not mean silent. The sweep is the only thing keeping this // table bounded, and `start` is the only caller that runs it, so a sweep that // fails every time would otherwise reinstate the original leak with nothing // anywhere to say so. it.effect("warns when the sweep fails, and starts the authorization anyway", () => Effect.scoped( Effect.gen(function* () { const server = yield* serveOAuthTestServer({}); const base = makeTestConfig({ plugins: [memoryCredentialsPlugin, acmePlugin] as const, }); const executor = yield* createExecutor({ ...base, db: withFailingDeletes(base.db, "oauth_session"), }); yield* Effect.addFinalizer(() => executor.close().pipe(Effect.ignore)); yield* executor.acme.seed(); yield* executor.oauth.createClient({ owner: "org", slug: CLIENT, authorizationUrl: server.authorizationEndpoint, tokenUrl: server.tokenEndpoint, grant: "authorization_code", clientId: "test-client", clientSecret: "test-secret", }); const warnings: string[] = []; const capture = Logger.make<unknown, void>(({ logLevel, message }) => { if (logLevel === "Warn") warnings.push(JSON.stringify(message)); }); const started = yield* executor.oauth .start({ owner: "org", client: CLIENT, clientOwner: "org", name: ConnectionName.make("undeterred"), integration: INTEG, template: TEMPLATE, }) .pipe(Effect.provide(Logger.layer([capture]))); // The authorization is unharmed: the caller still gets somewhere to go. expect(started.status).toBe("redirect"); expect(warnings.join("\n")).toContain("expired-session sweep failed"); }), ), );});
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "@effect/vitest";
import { Effect, Logger } from "effect";
import { withQueryContext } from "@executor-js/fumadb/query";
import { createExecutor } from "./executor";
import { StorageError, type FumaDb } from "./fuma-runtime";
import {
AuthTemplateSlug,
ConnectionName,
IntegrationSlug,
OAuthClientSlug,
ProviderItemId,
ProviderKey,
ToolName,
} from "./ids";
import { definePlugin } from "./plugin";
import { makeTestConfig, makeTestWorkspaceHarness } from "./test-config";
import { serveOAuthTestServer } from "./testing/oauth-test-server";
// An in-flight authorization flow parks its PKCE verifier in `oauth_session` in
// plaintext, which is fine while the flow can still spend it. What is not fine is
// leaving it there after the flow has died: the happy path and `cancel` delete the
// row, but a completion that FAILED did not, and nothing sweeps the table, so the
// verifier outlived the flow indefinitely.
//
// Paired, like every deletion test: an unredeemable session must go, and a
// perfectly good one sitting beside it must not.
const INTEG = IntegrationSlug.make("acme");
const TEMPLATE = AuthTemplateSlug.make("oauth");
const CLIENT = OAuthClientSlug.make("acme-app");
const memoryCredentialsPlugin = definePlugin(() => {
const store = new Map<string, string>();
return {
id: "memory-credentials" as const,
storage: () => ({}),
credentialProviders: [
{
key: ProviderKey.make("memory"),
writable: true as const,
get: (id: ProviderItemId) => Effect.sync(() => store.get(String(id)) ?? null),
set: (id: ProviderItemId, value: string) =>
Effect.sync(() => {
store.set(String(id), value);
}),
delete: (id: ProviderItemId) =>
Effect.sync(() => {
store.delete(String(id));
}),
},
],
};
})();
const acmePlugin = definePlugin(() => ({
id: "acme" as const,
storage: () => ({}),
resolveTools: () =>
Effect.succeed({ tools: [{ name: ToolName.make("whoami"), description: "whoami" }] }),
describeAuthMethods: () => [
{
id: "oauth",
label: "OAuth2",
kind: "oauth" as const,
template: String(TEMPLATE),
oauth: { scopes: [] },
},
],
invokeTool: ({ credential }) => Effect.succeed({ token: credential.value }),
extension: (ctx) => ({
seed: () => ctx.core.integrations.register({ slug: INTEG, description: "Acme", config: {} }),
}),
}))();
interface SessionRow {
readonly pkce_verifier?: string | null;
readonly expires_at?: number | null;
}
/** Read a session straight from storage through the given executor's own db
* handle, so the read is scoped exactly like that executor's writes. */
const readSession = (db: FumaDb, state: string) =>
Effect.promise(
() =>
db.findFirst("oauth_session", {
where: (b) => b("state", "=", state),
}) as Promise<SessionRow | null>,
);
/** A test db whose `deleteMany` on ONE table always rejects — the storage fault
* the sweep has to survive. `withContext` is forwarded through the wrapper
* because the executor re-derives its own owner context from whatever db it is
* handed; without that the fault would be unwrapped away on construction. */
const withFailingDeletes = (db: FumaDb, table: string): FumaDb =>
new Proxy(db, {
get(target, property) {
if (property === "withContext") {
return (context: unknown) => withFailingDeletes(withQueryContext(target, context), table);
}
if (property === "deleteMany") {
return async (name: string, ...rest: readonly unknown[]) => {
if (name === table) {
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: a FumaDB query reports failure by rejecting, which is the fault being simulated
throw new StorageError({
message: `simulated storage failure deleting from "${table}"`,
cause: undefined,
});
}
const forward = Reflect.get(target, property) as (
...args: readonly unknown[]
) => Promise<unknown>;
return forward.call(target, name, ...rest);
};
}
return Reflect.get(target, property);
},
});
describe("a dead authorization flow does not keep its PKCE verifier", () => {
it.effect("sweeps an expired verifier the next time authorization starts", () =>
Effect.scoped(
Effect.gen(function* () {
const server = yield* serveOAuthTestServer({});
const { executor, config } = yield* makeTestWorkspaceHarness({
plugins: [memoryCredentialsPlugin, acmePlugin] as const,
});
yield* executor.acme.seed();
yield* executor.oauth.createClient({
owner: "org",
slug: CLIENT,
authorizationUrl: server.authorizationEndpoint,
tokenUrl: server.tokenEndpoint,
grant: "authorization_code",
clientId: "test-client",
clientSecret: "test-secret",
});
// An abandoned flow: started, never returned to. Nothing completes it, so
// the lazy expiry check in `complete` never runs for it.
const abandoned = yield* executor.oauth.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("abandoned"),
integration: INTEG,
template: TEMPLATE,
});
if (abandoned.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
// A live flow started beside it, which must survive the sweep.
const live = yield* executor.oauth.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("live"),
integration: INTEG,
template: TEMPLATE,
});
if (live.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
// Age only the abandoned one past its expiry.
yield* Effect.promise(() =>
config.db.updateMany("oauth_session", {
where: (b) => b("state", "=", String(abandoned.state)),
set: { expires_at: Date.now() - 60_000 },
}),
);
expect((yield* readSession(config.db, String(abandoned.state)))?.pkce_verifier).toEqual(
expect.any(String),
);
// Starting any authorization is what tidies up.
const third = yield* executor.oauth.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("third"),
integration: INTEG,
template: TEMPLATE,
});
if (third.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
expect(yield* readSession(config.db, String(abandoned.state))).toBeNull();
// The unexpired flow is untouched — a sweep must not cancel someone
// else's authorization mid-flight.
expect((yield* readSession(config.db, String(live.state)))?.pkce_verifier).toEqual(
expect.any(String),
);
}),
),
);
it.effect("drops the session when the completion cannot be retried", () =>
Effect.scoped(
Effect.gen(function* () {
const server = yield* serveOAuthTestServer({});
const { executor, config } = yield* makeTestWorkspaceHarness({
plugins: [memoryCredentialsPlugin, acmePlugin] as const,
});
yield* executor.acme.seed();
yield* executor.oauth.createClient({
owner: "org",
slug: CLIENT,
authorizationUrl: server.authorizationEndpoint,
tokenUrl: server.tokenEndpoint,
grant: "authorization_code",
clientId: "test-client",
clientSecret: "test-secret",
});
const dying = yield* executor.oauth.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("dying"),
integration: INTEG,
template: TEMPLATE,
});
if (dying.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
const dyingCallback = yield* server.completeAuthorizationCodeFlow({
authorizationUrl: dying.authorizationUrl,
});
const bystander = yield* executor.oauth.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("bystander"),
integration: INTEG,
template: TEMPLATE,
});
if (bystander.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
// The verifier really is sitting there in plaintext.
const before = yield* readSession(config.db, String(dying.state));
expect(before?.pkce_verifier).toEqual(expect.any(String));
// Remove the app this flow was started against, so completion fails with
// restartRequired — this state can never be redeemed again.
yield* executor.oauth.removeClient("org", CLIENT);
const failed = yield* Effect.flip(
executor.oauth.complete({ state: dying.state, code: dyingCallback.code }),
);
expect(JSON.stringify(failed)).toContain("restartRequired");
expect(yield* readSession(config.db, String(dying.state))).toBeNull();
// The other flow is still live and untouched — a cleanup must not sweep
// sessions it was not asked about.
const survivor = yield* readSession(config.db, String(bystander.state));
expect(survivor?.pkce_verifier).toEqual(expect.any(String));
}),
),
);
// The sweep runs with no owner clause of its own — it trusts the table's
// delete policy to scope it. That trust is the whole safety argument, and it
// is invisible at the call site, so it is pinned here: one member starting an
// authorization must never reach into another member's rows, however stale
// they are. Two subjects on ONE database is the only way to observe it.
it.effect("one member's sweep leaves another member's expired session alone", () =>
Effect.scoped(
Effect.gen(function* () {
const server = yield* serveOAuthTestServer({});
const dataDir = mkdtempSync(join(tmpdir(), "oauth-session-sweep-"));
const tenant = "shared-tenant";
const plugins = [memoryCredentialsPlugin, acmePlugin] as const;
const a = yield* makeTestWorkspaceHarness({
plugins,
tenant,
subject: "subject-a",
dataDir,
});
yield* a.executor.acme.seed();
yield* a.executor.oauth.createClient({
owner: "org",
slug: CLIENT,
authorizationUrl: server.authorizationEndpoint,
tokenUrl: server.tokenEndpoint,
grant: "authorization_code",
clientId: "test-client",
clientSecret: "test-secret",
});
// A second member of the same tenant, on the same database.
const b = yield* makeTestWorkspaceHarness({
plugins,
tenant,
subject: "subject-b",
dataDir,
});
// Both members abandon a USER-owned flow. `owner: "user"` is what makes
// the rows private to each subject; an org row is tenant-shared by
// design and its removal by any member is correct.
const startUserFlow = (harness: typeof a, name: string) =>
Effect.gen(function* () {
const started = yield* harness.executor.oauth.start({
owner: "user",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make(name),
integration: INTEG,
template: TEMPLATE,
});
if (started.status !== "redirect") {
return yield* Effect.die("expected a redirect-status OAuth start");
}
return started;
});
const expire = (db: FumaDb, state: string) =>
Effect.promise(() =>
db.updateMany("oauth_session", {
where: (builder) => builder("state", "=", state),
set: { expires_at: Date.now() - 60_000 },
}),
);
const bStale = yield* startUserFlow(b, "b-stale");
yield* expire(b.config.db, String(bStale.state));
const aStale = yield* startUserFlow(a, "a-stale");
yield* expire(a.config.db, String(aStale.state));
// B's row really is a sweep candidate — otherwise surviving would prove
// nothing about scoping.
const bBefore = yield* readSession(b.config.db, String(bStale.state));
expect(Number(bBefore?.expires_at)).toBeLessThan(Date.now());
// A starts again. Its sweep must reach its OWN expired row and no
// further.
yield* startUserFlow(a, "a-fresh");
expect(yield* readSession(a.config.db, String(aStale.state))).toBeNull();
expect((yield* readSession(b.config.db, String(bStale.state)))?.pkce_verifier).toEqual(
expect.any(String),
);
}),
),
);
// Best-effort must not mean silent. The sweep is the only thing keeping this
// table bounded, and `start` is the only caller that runs it, so a sweep that
// fails every time would otherwise reinstate the original leak with nothing
// anywhere to say so.
it.effect("warns when the sweep fails, and starts the authorization anyway", () =>
Effect.scoped(
Effect.gen(function* () {
const server = yield* serveOAuthTestServer({});
const base = makeTestConfig({
plugins: [memoryCredentialsPlugin, acmePlugin] as const,
});
const executor = yield* createExecutor({
...base,
db: withFailingDeletes(base.db, "oauth_session"),
});
yield* Effect.addFinalizer(() => executor.close().pipe(Effect.ignore));
yield* executor.acme.seed();
yield* executor.oauth.createClient({
owner: "org",
slug: CLIENT,
authorizationUrl: server.authorizationEndpoint,
tokenUrl: server.tokenEndpoint,
grant: "authorization_code",
clientId: "test-client",
clientSecret: "test-secret",
});
const warnings: string[] = [];
const capture = Logger.make<unknown, void>(({ logLevel, message }) => {
if (logLevel === "Warn") warnings.push(JSON.stringify(message));
});
const started = yield* executor.oauth
.start({
owner: "org",
client: CLIENT,
clientOwner: "org",
name: ConnectionName.make("undeterred"),
integration: INTEG,
template: TEMPLATE,
})
.pipe(Effect.provide(Logger.layer([capture])));
// The authorization is unharmed: the caller still gets somewhere to go.
expect(started.status).toBe("redirect");
expect(warnings.join("\n")).toContain("expired-session sweep failed");
}),
),
);
});