refactor(codex): remove unused auth-scope fingerprinting #19

Merged
eleith merged 1 commits from refactor/codex-account-client-cleanup into main 2026-11-05 20:43:16 +00:00
7 changed files with 123 additions and 153 deletions

No files matched your search

@@ -0,0 +1,5 @@
---
"@eleith-pi/codex": patch
---
Remove unused Codex auth-scope fingerprinting and the auth-only lookup path. Return catalog JSON directly without unused metadata wrappers, preserving account-request authentication, endpoint restrictions, cancellation, timeouts, response bounds, and safe errors.
+98 -97
View File
@@ -1,12 +1,6 @@
import { createHash } from "node:crypto";
import type { Api, AuthResult, Model } from "@earendil-works/pi-ai";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
fetchCodexModelCatalog,
fetchCodexUsage,
readCodexAuthScope,
type CodexAccountRegistry,
} from "./client.ts";
import { fetchCodexModelCatalog, fetchCodexUsage, type CodexAccountRegistry } from "./client.ts";
const BASE_URL = "https://chatgpt.com/backend-api";
const model: Model<Api> = {
@@ -72,67 +66,63 @@ it("uses Pi's existing Codex auth for the fixed account endpoint", async () => {
});
});
it("uses the same Pi credentials for the fixed versioned model catalog", async () => {
it("returns raw catalog JSON using only Pi's resolved routing credentials", async () => {
const f = fixture();
f.auth.auth.headers = {
"ChatGPT-Account-Id": "unused-account",
Authorization: "unused-token",
"X-Unrelated-Secret": "unused-secret",
};
const catalog = { models: [{ slug: "future-model", service_tiers: [{ id: "priority" }] }] };
f.fetcher.mockResolvedValue(json(catalog));
const response = json(catalog);
f.fetcher.mockResolvedValue(response);
const result = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
expect(result.data).toEqual(catalog);
expect(result).toEqual(catalog);
expect(f.registry.getProviderAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
const requestOptions = expect.objectContaining({ method: "GET", redirect: "error" });
expect(f.fetcher).toHaveBeenCalledExactlyOnceWith(
`${BASE_URL}/codex/models?client_version=0.162.0`,
expect.objectContaining({ method: "GET", redirect: "error" }),
requestOptions,
);
const headers = new Headers(f.fetcher.mock.calls[0][1]?.headers);
expect(headers.get("authorization")).toBe(`Bearer ${f.auth.auth.apiKey}`);
expect(headers.get("chatgpt-account-id")).toBe("test-account");
expect(result.authScope).toBe(
createHash("sha256")
.update(JSON.stringify([headers.get("authorization"), headers.get("chatgpt-account-id")]))
.digest("hex"),
);
expect(Object.keys(result).sort()).toEqual(["authScope", "data"]);
expect(JSON.stringify(result)).not.toContain(f.auth.auth.apiKey);
expect(JSON.stringify(result)).not.toContain("test-account");
});
it("reads a stable auth scope without fetching and ignores unused provider headers", async () => {
const f = fixture();
const fetchSpy = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("must not fetch"));
try {
const scope = await readCodexAuthScope(f.registry);
f.auth.auth.headers = {
"ChatGPT-Account-Id": "unused-account",
Authorization: "unused-token",
"X-Unrelated-Secret": "unused-secret",
};
expect(await readCodexAuthScope(f.registry)).toBe(scope);
expect(scope).toMatch(/^[a-f0-9]{64}$/);
expect(scope).not.toContain(f.auth.auth.apiKey);
expect(scope).not.toContain("test-account");
expect(f.registry.getProviderAuth).toHaveBeenCalledTimes(2);
expect(fetchSpy).not.toHaveBeenCalled();
const catalog = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
expect(catalog.authScope).toBe(scope);
} finally {
fetchSpy.mockRestore();
}
const actualHeaders = Object.fromEntries(headers);
expect(actualHeaders).toEqual({
accept: "application/json",
authorization: `Bearer ${f.auth.auth.apiKey}`,
"chatgpt-account-id": "test-account",
});
const serialized = JSON.stringify(result);
expect(serialized).not.toContain(f.auth.auth.apiKey);
expect(serialized).not.toContain("test-account");
});
it.each(["token refresh", "account change"] as const)(
"changes the scope on %s and binds the catalog to its own resolved credentials",
"resolves fresh catalog credentials after %s",
async (change) => {
const f = fixture();
const previous = await readCodexAuthScope(f.registry);
f.auth.auth.apiKey =
change === "token refresh" ? `${token()}-refreshed` : token("another-account");
const catalog = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
expect(catalog.authScope).not.toBe(previous);
expect(await readCodexAuthScope(f.registry)).toBe(catalog.authScope);
const originalToken = f.auth.auth.apiKey;
await fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
const nextAccount = change === "token refresh" ? "test-account" : "another-account";
const nextToken =
change === "token refresh" ? `${originalToken}-refreshed` : token(nextAccount);
f.auth.auth.apiKey = nextToken;
await fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
const credentials = f.fetcher.mock.calls.map(([, init]) => {
const headers = new Headers(init?.headers);
return {
authorization: headers.get("authorization"),
account: headers.get("chatgpt-account-id"),
};
});
expect(credentials).toEqual([
{ authorization: `Bearer ${originalToken}`, account: "test-account" },
{ authorization: `Bearer ${nextToken}`, account: nextAccount },
]);
expect(f.registry.getProviderAuth).toHaveBeenCalledTimes(2);
},
);
describe("auth-only validation without HTTP", () => {
describe("catalog credential validation before HTTP", () => {
const unexpectedFetch = vi.fn<typeof fetch>(async () => {
throw new Error("must not fetch");
});
@@ -156,19 +146,22 @@ describe("auth-only validation without HTTP", () => {
it("rejects an overridden provider endpoint", async () => {
const account = fixture();
account.provider.baseUrl = "https://gateway.invalid";
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unsupportedProvider);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unsupportedProvider);
});
it("rejects a catalog model at a different endpoint", async () => {
const account = fixture();
account.provider.getModels = () => [{ ...model, baseUrl: "https://gateway.invalid" }];
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unsupportedProvider);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unsupportedProvider);
});
it("rejects an endpoint override in resolved credentials", async () => {
const account = fixture();
account.auth.auth.baseUrl = "https://gateway.invalid";
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject({
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject({
code: "unsupported",
message: "Account requests do not support endpoint overrides.",
});
@@ -177,25 +170,30 @@ describe("auth-only validation without HTTP", () => {
it("requires an OAuth availability snapshot", async () => {
const account = fixture();
account.registry.isUsingOAuth.mockReturnValue(false);
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unavailableAuth);
});
it("rejects a non-OAuth resolution even when the snapshot says OAuth", async () => {
const account = fixture();
account.auth.source = "models.json";
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unavailableAuth);
});
it("requires the token's account-routing claim", async () => {
const account = fixture();
account.auth.auth.apiKey = "e30.e30.private-token";
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unavailableAuth);
});
it("does not repeat private Pi refresh errors", async () => {
const account = fixture();
account.registry.getProviderAuth.mockRejectedValue(new Error("private refresh details"));
await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth);
const refreshError = new Error("private refresh details");
account.registry.getProviderAuth.mockRejectedValue(refreshError);
const request = fetchCodexModelCatalog(account.registry);
await expect(request).rejects.toMatchObject(unavailableAuth);
});
});
@@ -428,63 +426,66 @@ it("reports invalid JSON without repeating returned content", async () => {
});
});
it.each(["usage", "catalog", "auth"] as const)(
"cancels during the public %s wrapper handoff",
async (resource) => {
const f = fixture();
const controller = new AbortController();
const abort = AbortController.prototype.abort;
// The private flow aborts its deadline in finally, immediately before handing off.
const cleanup = vi.spyOn(AbortController.prototype, "abort").mockImplementation(function (
this: AbortController,
reason?: unknown,
) {
abort.call(this, reason);
if (this !== controller) abort.call(controller);
});
try {
let request: Promise<unknown>;
if (resource === "auth") request = readCodexAuthScope(f.registry, controller.signal);
else if (resource === "usage")
request = fetchCodexUsage(f.registry, controller.signal, f.fetcher);
else request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher);
await expect(request).rejects.toMatchObject({ code: "cancelled" });
} finally {
cleanup.mockRestore();
}
},
);
it.each([
["usage", fetchCodexUsage],
["catalog", fetchCodexModelCatalog],
] as const)("cancels during the public %s wrapper handoff", async (_resource, fetchData) => {
const f = fixture();
const controller = new AbortController();
const abort = AbortController.prototype.abort;
// The private flow aborts its deadline in finally, immediately before handing off.
const cleanup = vi.spyOn(AbortController.prototype, "abort").mockImplementation(function (
this: AbortController,
reason?: unknown,
) {
abort.call(this, reason);
if (this !== controller) abort.call(controller);
});
try {
const request = fetchData(f.registry, controller.signal, f.fetcher);
await expect(request).rejects.toMatchObject({ code: "cancelled" });
} finally {
cleanup.mockRestore();
}
});
it("does not resolve auth for an already-cancelled scope read", async () => {
it("does not resolve auth for an already-cancelled catalog request", async () => {
const f = fixture();
const controller = new AbortController();
controller.abort();
await expect(readCodexAuthScope(f.registry, controller.signal)).rejects.toMatchObject({
code: "cancelled",
});
const request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher);
await expect(request).rejects.toMatchObject({ code: "cancelled" });
expect(f.registry.getProviderAuth).not.toHaveBeenCalled();
expect(f.fetcher).not.toHaveBeenCalled();
});
it("ignores auth resolving after a scope read is cancelled", async () => {
it("ignores auth resolving after a catalog request is cancelled", async () => {
const f = fixture();
const auth = deferred<AuthResult>();
f.registry.getProviderAuth.mockReturnValue(auth.promise);
const controller = new AbortController();
const request = readCodexAuthScope(f.registry, controller.signal);
const request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher);
controller.abort();
await expect(request).rejects.toMatchObject({ code: "cancelled" });
auth.resolve(f.auth);
await auth.promise;
expect(f.fetcher).not.toHaveBeenCalled();
});
it("bounds auth-only resolution by the same ten-second deadline", async () => {
it("bounds the catalog's wait for Pi auth by the ten-second deadline", async () => {
vi.useFakeTimers();
const f = fixture();
f.registry.getProviderAuth.mockReturnValue(new Promise(() => {}));
const result = expect(readCodexAuthScope(f.registry)).rejects.toMatchObject({ code: "timeout" });
const auth = deferred<AuthResult>();
f.registry.getProviderAuth.mockReturnValue(auth.promise);
const request = fetchCodexModelCatalog(f.registry, undefined, f.fetcher);
const failure = expect(request).rejects.toMatchObject({ code: "timeout" });
await vi.advanceTimersByTimeAsync(10_000);
await result;
expect(vi.getTimerCount()).toBe(0);
await failure;
auth.resolve(f.auth);
await auth.promise;
expect(f.fetcher).not.toHaveBeenCalled();
const timerCount = vi.getTimerCount();
expect(timerCount).toBe(0);
});
it("cancels during handoff of an already-buffered response", async () => {
+9 -32
View File
@@ -1,4 +1,3 @@
import { createHash } from "node:crypto";
import type { AuthResult, Provider } from "@earendil-works/pi-ai";
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -8,8 +7,7 @@ const ACCOUNT_URLS = {
usage: `${BASE_URL}/wham/usage`,
catalog: `${BASE_URL}/codex/models?client_version=0.162.0`,
} as const;
type NetworkResource = keyof typeof ACCOUNT_URLS;
type AccountResource = NetworkResource | "auth";
type AccountResource = keyof typeof ACCOUNT_URLS;
const TIMEOUT_MS = 10_000;
const MAX_RESPONSE_BYTES = 1024 * 1024;
@@ -20,13 +18,6 @@ export type CodexAccountRegistry = Pick<
getProvider(id: string): Pick<Provider, "baseUrl" | "getModels"> | undefined;
};
interface AccountData {
data: unknown;
authScope: string;
}
export type CodexModelCatalog = AccountData;
type Failure = "auth" | "unsupported" | "cancelled" | "timeout" | "network" | "response" | "http";
export class CodexAccountError extends Error {
@@ -46,28 +37,19 @@ export async function fetchCodexUsage(
signal?: AbortSignal,
fetcher: typeof fetch = fetch,
): Promise<unknown> {
const result = await readAccountData("usage", registry, signal, fetcher);
const data = await readAccountData("usage", registry, signal, fetcher);
if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled.");
return result.data;
return data;
}
export async function fetchCodexModelCatalog(
registry: CodexAccountRegistry,
signal?: AbortSignal,
fetcher: typeof fetch = fetch,
): Promise<CodexModelCatalog> {
const result = await readAccountData("catalog", registry, signal, fetcher);
): Promise<unknown> {
const data = await readAccountData("catalog", registry, signal, fetcher);
if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled.");
return result;
}
export async function readCodexAuthScope(
registry: CodexAccountRegistry,
signal?: AbortSignal,
): Promise<string> {
const result = await readAccountData("auth", registry, signal);
if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled.");
return result.authScope;
return data;
}
async function readAccountData(
@@ -75,7 +57,7 @@ async function readAccountData(
registry: CodexAccountRegistry,
signal: AbortSignal | undefined,
fetcher: typeof fetch = fetch,
): Promise<AccountData> {
): Promise<unknown> {
const deadline = new AbortController();
const timer = setTimeout(() => deadline.abort(), TIMEOUT_MS);
const requestSignal = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
@@ -83,14 +65,9 @@ async function readAccountData(
requestSignal.throwIfAborted();
const headers = await resolveAccountHeaders(registry, requestSignal);
requestSignal.throwIfAborted();
// Hash only the normalized credentials we actually send, never persist raw headers.
const authScope = createHash("sha256")
.update(JSON.stringify([headers.get("authorization"), headers.get("chatgpt-account-id")]))
.digest("hex");
if (resource === "auth") return { data: undefined, authScope };
const data = await requestAccountJson(resource, headers, requestSignal, fetcher);
requestSignal.throwIfAborted();
return { data, authScope };
return data;
} catch (error) {
if (signal?.aborted)
throw new CodexAccountError("cancelled", "Codex account request cancelled.");
@@ -194,7 +171,7 @@ function authError(): CodexAccountError {
}
async function requestAccountJson(
resource: NetworkResource,
resource: AccountResource,
headers: Headers,
requestSignal: AbortSignal,
fetcher: typeof fetch,
-1
View File
@@ -554,7 +554,6 @@ function installResponseTransports(transport: Transport) {
function createHttpResponses(transport: Transport, payloads: Record<string, unknown>[]) {
const catalog = vi.fn<typeof fetch>(async () =>
// The client wraps this internally as { data, authScope }; the network JSON does not.
Response.json({ models: [{ slug: "gpt-6-sol", service_tiers: [{ id: "priority" }] }] }),
);
const sse = vi.fn<typeof fetch>(async (_url, init) => {
+9 -14
View File
@@ -5,11 +5,7 @@ import type {
SessionEntry,
} from "@earendil-works/pi-coding-agent";
import { describe, expect, it, vi } from "vitest";
import {
CodexAccountError,
type CodexModelCatalog,
type fetchCodexModelCatalog,
} from "./client.ts";
import { CodexAccountError, type fetchCodexModelCatalog } from "./client.ts";
import { FastController, type FastHost } from "./fast.ts";
import { FAST_ENTRY, type FastPreference } from "./fast-state.ts";
import { CONTEXT_ENTRY } from "./state.ts";
@@ -27,9 +23,8 @@ const model: Model<Api> = {
maxTokens: 128_000,
};
const otherModel: Model<Api> = { ...model, id: "gpt-6-astra", name: "Astra" };
const catalog: CodexModelCatalog = {
authScope: "scope-a",
data: { models: [{ slug: model.id, service_tiers: [{ id: "default" }, { id: "priority" }] }] },
const catalog = {
models: [{ slug: model.id, service_tiers: [{ id: "default" }, { id: "priority" }] }],
};
describe("commands and ownership", () => {
@@ -113,7 +108,7 @@ describe("commands and ownership", () => {
it("persists on only after cost confirmation and matching catalog discovery complete", async () => {
const fast = createFastControllerFixture();
const confirmation = deferred<boolean>();
const response = deferred<CodexModelCatalog>();
const response = deferred<unknown>();
const started = deferred<void>();
fast.ctx.ui.confirm.mockReturnValue(confirmation.promise);
fast.fetchCatalog.mockImplementationOnce(() => {
@@ -134,7 +129,7 @@ describe("commands and ownership", () => {
fast.ctx.modelRegistry,
expect.any(AbortSignal),
);
// No separate auth-scope preflight; the account client resolves auth for its one request.
// The account client resolves auth for its one request; no separate preflight.
expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
@@ -166,7 +161,7 @@ describe("commands and ownership", () => {
},
])("does not enable without established model support: %j", async ({ data, reason }) => {
const fast = createFastControllerFixture();
fast.fetchCatalog.mockResolvedValue({ ...catalog, data });
fast.fetchCatalog.mockResolvedValue(data);
await fast.controller.handle("on", fast.ctx);
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
@@ -401,7 +396,7 @@ describe("asynchronous enable ownership", () => {
"discards catalog discovery after the %s changes",
async (changed) => {
const fast = createFastControllerFixture();
const response = deferred<CodexModelCatalog>();
const response = deferred<unknown>();
const started = deferred<void>();
fast.fetchCatalog.mockImplementationOnce(() => {
started.resolve();
@@ -420,7 +415,7 @@ describe("asynchronous enable ownership", () => {
it("aborts invalidated discovery without letting a late result clear a newer operation", async () => {
const fast = createFastControllerFixture();
const oldCatalog = deferred<CodexModelCatalog>();
const oldCatalog = deferred<unknown>();
const oldStarted = deferred<void>();
fast.fetchCatalog.mockImplementationOnce(() => {
oldStarted.resolve();
@@ -458,7 +453,7 @@ describe("asynchronous enable ownership", () => {
for (const outcome of ["resolve", "reject"]) {
const fast = createFastControllerFixture();
const confirmation = deferred<boolean>();
const response = deferred<CodexModelCatalog>();
const response = deferred<unknown>();
const started = deferred<void>();
if (stage === "confirmation") fast.ctx.ui.confirm.mockReturnValue(confirmation.promise);
fast.fetchCatalog.mockImplementationOnce(() => {
+1 -1
View File
@@ -235,7 +235,7 @@ export class FastController {
): Promise<boolean> {
const catalog = await this.fetchCatalog(ctx.modelRegistry, preparation.abortController.signal);
if (!this.canApplyPreparation(ctx, preparation) || !ctx.model) return false;
const eligibility = codexFastEligibility(catalog.data, ctx.model.id);
const eligibility = codexFastEligibility(catalog, ctx.model.id);
if (eligibility === "supported") return true;
const reason =
eligibility === "unsupported"
+1 -8
View File
@@ -8,14 +8,13 @@ import {
import type { Api, Model } from "@earendil-works/pi-ai";
import { afterAll, afterEach, expect, it, vi } from "vitest";
import codex from "./index.ts";
import { fetchCodexModelCatalog, fetchCodexUsage, readCodexAuthScope } from "./client.ts";
import { fetchCodexModelCatalog, fetchCodexUsage } from "./client.ts";
import { customInteractionUI } from "./custom-interaction.test-helper.ts";
vi.mock("./client.ts", async (importOriginal) => ({
...(await importOriginal<typeof import("./client.ts")>()),
fetchCodexUsage: vi.fn(),
fetchCodexModelCatalog: vi.fn(),
readCodexAuthScope: vi.fn(),
}));
const directory = await vi.hoisted(async () => {
@@ -43,7 +42,6 @@ afterEach(() => {
async function load(prefix = "eleith", mode: ExtensionCommandContext["mode"] = "print") {
vi.mocked(fetchCodexUsage).mockReset();
vi.mocked(fetchCodexModelCatalog).mockReset();
vi.mocked(readCodexAuthScope).mockReset();
writeFileSync(configPath, JSON.stringify({ commandPrefix: prefix }));
type Command = Parameters<ExtensionAPI["registerCommand"]>[1];
type Listener = (
@@ -154,7 +152,6 @@ it("wires restore/reconcile hooks without provider calls for an unsupported mode
expect(context.pi.on.mock.calls.map(([name]) => name)).not.toContain("before_agent_start");
expect(fetchCodexUsage).not.toHaveBeenCalled();
expect(fetchCodexModelCatalog).not.toHaveBeenCalled();
expect(readCodexAuthScope).not.toHaveBeenCalled();
});
it("rejects removed commands and additional control arguments without side effects", async () => {
@@ -176,7 +173,6 @@ it("rejects removed commands and additional control arguments without side effec
expect(context.notify).toHaveBeenLastCalledWith(usage, "warning");
}
expect(fetchCodexModelCatalog).not.toHaveBeenCalled();
expect(readCodexAuthScope).not.toHaveBeenCalled();
expect(fetchCodexUsage).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
expect(context.pi.setModel).not.toHaveBeenCalled();
@@ -265,7 +261,6 @@ it("combines context and fast mode in a read-only dashboard with the configured
expect(getProviderAuth).not.toHaveBeenCalled();
expect(fetchCodexUsage).not.toHaveBeenCalled();
expect(fetchCodexModelCatalog).not.toHaveBeenCalled();
expect(readCodexAuthScope).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
expect(context.pi.setModel).not.toHaveBeenCalled();
const dashboard = context.notify.mock.calls[0][0];
@@ -277,7 +272,6 @@ it("combines context and fast mode in a read-only dashboard with the configured
expect(getProviderAuth).not.toHaveBeenCalled();
expect(fetchCodexUsage).not.toHaveBeenCalled();
expect(fetchCodexModelCatalog).not.toHaveBeenCalled();
expect(readCodexAuthScope).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
expect(context.pi.setModel).not.toHaveBeenCalled();
await context.emit("session_shutdown");
@@ -324,7 +318,6 @@ it("routes every TUI read-only alias to the shared overview inspector, not notif
expect(context.notify).not.toHaveBeenCalled();
expect(fetchCodexUsage).not.toHaveBeenCalled();
expect(fetchCodexModelCatalog).not.toHaveBeenCalled();
expect(readCodexAuthScope).not.toHaveBeenCalled();
expect(context.pi.setModel).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
await context.emit("session_shutdown");