diff --git a/README.md b/README.md index 9622c24..1673e03 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ pnpm demo # Open http://127.0.0.1:4173 (or pnpm exec next dev --webpack --hostname 127.0.0.1 --port 4187 for a different port) ``` -The root page compares parallel Codex CLI runs with full versus Jev-selected synthetic MCP tools. Choose one of four example cards, inspect its task, then explicitly run the comparison. Details open over the results in modal drawers. Save a personal TypeSafe key under **API key**; **Usage** tracks reported Jev usage. The host records proposed patches without applying them. +The root page compares parallel Codex CLI runs with full versus Jev-selected synthetic MCP tools. Choose one of four example cards, inspect its task, then explicitly run the comparison. Details open over the results in modal drawers. Save a personal TypeSafe key under **Settings → Jev API key**; **Usage** tracks reported Jev usage. The host records proposed patches without applying them. Completed and interrupted comparisons are saved in this browser, up to 30 runs within 2 MB. **History** reopens evidence and compares input tokens or duration for matching tasks and harness setups, including Jev overhead. Unknown measurements remain unknown; a run is not a benchmark. The old `/arena` URL redirects to `/`; the Routing room and Example lab pages have been removed. See [the demo guide and browser verification](docs/routing-demo.md). diff --git a/app/globals.css b/app/globals.css index f2d7608..5b254cb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -472,3 +472,17 @@ body:has(dialog[open]) { overflow: hidden; } .detail-dialog .arena-answer { max-width: none; } @media (hover: hover) { .detail-trigger:hover { color: var(--pink); } } @media (max-width: 600px) { .detail-dialog-heading { padding: 18px; gap: 12px; } .detail-dialog-heading h2 { font-size: 20px; } .detail-dialog-body { padding: 20px 18px; } .run-inspector > .detail-trigger { padding: 18px; } .arena-method > .detail-trigger { min-height: 40px; } .lane-activity > .detail-trigger { padding-bottom: 6px; } .detail-dialog .inspector-tabs { display: grid; grid-template-columns: 1fr 1fr; } } + +/* Settings keeps the manual key source explicit without revealing a credential. */ +.settings-dialog { width: min(560px, 100%); } +.settings-dialog h3 { font-size: 18px; margin: 0 0 10px; } +.key-override-dot { display: inline-block; width: 6px; height: 6px; margin-left: 8px; border-radius: 50%; background: var(--pink); vertical-align: middle; } +.key-source-card { margin: 24px 0; padding: 18px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); } +.key-source-card > span { display: block; font-size: 11px; color: var(--muted); } +.key-source-card strong { display: block; margin-top: 8px; font-size: 16px; font-weight: 500; } +.key-source-card.has-override strong { color: var(--pink); } +.key-source-card p { color: var(--muted); font-size: 13px; line-height: 1.8; margin: 10px 0 0; } +.settings-dialog .dialog-actions { flex-wrap: wrap; margin-top: 20px; gap: 10px; } +.key-message { font-size: 13px; line-height: 1.8; color: var(--muted); } +.key-error { color: var(--pink); } +.key-message:empty { display: none; } diff --git a/components/header.tsx b/components/header.tsx index c9238a5..fd09d51 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -1,26 +1,43 @@ "use client"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; -import { readApiKey, saveApiKey } from "../examples/routing/api-key"; +import { KEY_STORAGE, readApiKey, saveApiKey } from "../examples/routing/api-key"; import { clearUsage, getUsage, summarizeUsage, type UsageEntry } from "../examples/routing/usage"; export function Header() { const keyDialog = useRef(null), usageDialog = useRef(null); - const [saved, setSaved] = useState(false), [draft, setDraft] = useState(""), [error, setError] = useState(""); + const [saved, setSaved] = useState(false), [draft, setDraft] = useState(""), [error, setError] = useState(""), [notice, setNotice] = useState(""); const [entries, setEntries] = useState([]); const total = summarizeUsage(entries); useEffect(() => { const syncKey = () => setSaved(Boolean(readApiKey())); const syncUsage = () => setEntries([...getUsage()]); + const syncStoredKey = (event: StorageEvent) => { if (event.key === KEY_STORAGE || event.key === null) { syncKey(); setDraft(""); setError(""); setNotice(""); } }; syncKey(); syncUsage(); - window.addEventListener("storage", syncKey); window.addEventListener("jev-key-change", syncKey); window.addEventListener("jev-usage-change", syncUsage); - return () => { window.removeEventListener("storage", syncKey); window.removeEventListener("jev-key-change", syncKey); window.removeEventListener("jev-usage-change", syncUsage); }; + window.addEventListener("storage", syncStoredKey); window.addEventListener("jev-key-change", syncKey); window.addEventListener("jev-usage-change", syncUsage); + return () => { window.removeEventListener("storage", syncStoredKey); window.removeEventListener("jev-key-change", syncKey); window.removeEventListener("jev-usage-change", syncUsage); }; }, []); function save(value: string) { - try { saveApiKey(value); setDraft(""); setError(""); setSaved(Boolean(value.trim())); window.dispatchEvent(new Event("jev-key-change")); return true; } - catch { setError("Could not save. Check the key format and browser storage permissions."); return false; } + try { saveApiKey(value); setDraft(""); setError(""); setSaved(Boolean(value.trim())); setNotice(value.trim() ? "Manual override saved. Your next comparison will use this key." : "Manual override removed. Your next comparison will use the server default if configured."); window.dispatchEvent(new Event("jev-key-change")); return true; } + catch { setNotice(""); setError("Could not update the override. Check the key format and browser storage permissions."); return false; } } - return <>
jev / agent arena
- { setDraft(""); setError(""); }}>

Your TypeSafe API key

{saved ? "A personal key is saved for this origin. It overrides the host's default." : "No personal key saved. Live requests use the host's default if configured."}

{ event.preventDefault(); if (draft.trim() && save(draft)) keyDialog.current?.close(); }}> setDraft(event.target.value)} autoComplete="off" autoCapitalize="none" spellCheck={false} maxLength={1024} required placeholder="Paste your TypeSafe API key" />

Saved unencrypted in this browser until removed. Masked on screen; sent only with live requests through this local host. The saved value is never displayed.

{error}

+ return <>
jev / agent arena
+ { setDraft(""); setError(""); setNotice(""); }}> +

Agent arena

Settings

+
+
+

Jev API key

+

Set a manual TypeSafe API key override for comparisons in this browser.

+
Active key source{saved ? "Manual override" : "Server default"}

{saved ? "Your saved key takes priority over the server key. Replace it below or remove the override to return to the default." : "Uses the server key when configured. Save your own key below to override it."}

+
{ event.preventDefault(); if (draft.trim()) save(draft); }}> + + setDraft(event.target.value)} aria-describedby="key-storage-note" autoComplete="off" autoCapitalize="none" spellCheck={false} maxLength={1024} required placeholder="Paste your TypeSafe API key" /> +

Stored unencrypted on this device for this origin. The saved key is never shown again or included in results. Only explicit live requests use it.

+
+

{error}

{notice}

+
+
+
+

Usage & budget

This tab's latest 200 live Jev requests · survives refresh. Account-wide quotas and balances are unavailable.

Requests{total.requests}
Reported input{total.input.toLocaleString()}
Reported output{total.output.toLocaleString()}

{total.unknown ? `${total.unknown} requests have incomplete usage. Totals include reported tokens only.` : total.requests ? "All listed requests have reported token usage." : "Run a comparison to see Jev usage. Agent CLI usage is shown in each result."}

Reported-input price estimate${(total.input * 42 / 1_000_000_000).toFixed(8)}

At $42 per billion input tokens (checked Sep 22, 2026). Excludes output, unknown usage and account-specific pricing. Not an invoice.

Request history
{["Time", "Status", "Input", "Output", "Duration"].map(label => )}{entries.map((entry, index) => )}
Provider-reported tokens. Missing usage stays unknown.
{label}
{new Date(entry.at).toLocaleTimeString()}{entry.status} · {entry.keySource} key{entry.input ?? "Unknown"}{entry.output ?? "Unknown"}{entry.latencyMs === null ? "Unknown" : `${(entry.latencyMs / 1000).toFixed(2)} s`}
Request limits

Local host: 30 starts/minute, 2 concurrent across keys and tabs. Local safeguards, not account quotas. Billing (402) and rate-limit (429) failures remain visible. No automatic retries.

; } diff --git a/docs/routing-demo.md b/docs/routing-demo.md index 1657e75..9d148ce 100644 --- a/docs/routing-demo.md +++ b/docs/routing-demo.md @@ -24,7 +24,7 @@ Primary details open in a modal drawer over the content: **How the comparison wo ## Key and usage controls -**API key** follows the playground's personal-override convention: masked input, save/replace/remove, origin-local `typesafe-api-key-override` storage. The saved value is never filled back into the input. Storage is unencrypted and accessible to same-origin scripts; origins include ports. A personal key overrides the host's server-only `TYPESAFE_API_KEY`. Server keys never reach the browser. +**Settings → Jev API key** exposes the manual override and current key source. Saving activates it immediately; replacing updates it, and **Remove override** restores the server default for the next comparison. It follows the playground's personal-override convention: masked input, save/replace/remove, origin-local `typesafe-api-key-override` storage. The saved value is never filled back into the input. Storage is unencrypted and accessible to same-origin scripts; origins include ports. A personal key overrides the host's server-only `TYPESAFE_API_KEY`. Server keys never reach the browser. **Usage** opens reported input/output totals, a price estimate and request history for this tab's latest 200 live Jev requests. Session storage preserves scalar telemetry across refresh, without prompts or keys. **Clear local usage history** removes only that telemetry. Missing usage on failed/cancelled calls is not free; partial totals are labeled. Personal and host-key calls are identified separately. CLI usage is shown per arena lane, not mixed into the Jev totals. @@ -75,6 +75,6 @@ pnpm build pnpm check:secrets ``` -Tests use fake provider/CLI transports and a real local synthetic MCP process; they never consume provider credits. Browser verifiers accept an existing Playwright Page without adding a second test dependency. See `scripts/verify-arena-browser.mjs`, `scripts/verify-arena-history-browser.mjs` and `scripts/verify-arena-controls-browser.mjs`. Run each verifier in a fresh browser context. +Tests use fake provider/CLI transports and a real local synthetic MCP process; they never consume provider credits. Browser verifiers accept an existing Playwright Page without adding a second test dependency. See `scripts/verify-arena-browser.mjs`, `scripts/verify-arena-history-browser.mjs` `scripts/verify-arena-controls-browser.mjs` and `scripts/verify-arena-settings-browser.mjs`. Run each verifier in a fresh browser context. The Settings entry point is `verifyArenaSettings(page, baseURL, screenshotDir?)`; pass the context's Page, the running demo URL and an optional screenshot directory. It checks the manual key lifecycle with synthetic credentials and intercepted requests; see the [Settings verification record](verification/arena-key-settings-2026-09-22.json). The [arena history verification](verification/arena-history-2026-09-22.json) records offline unit, browser and build checks for the sole arena, example selection, overlays, persistence, unknown/partial outcomes and responsive layouts. Recorded runs live in [verification/](verification/). Automated browser checks and screenshot inspection are not human keyboard-only or VoiceOver acceptance. Cross-browser device testing and a repeated live benchmark remain outstanding. diff --git a/docs/verification/arena-key-settings-2026-09-22.json b/docs/verification/arena-key-settings-2026-09-22.json new file mode 100644 index 0000000..800e22c --- /dev/null +++ b/docs/verification/arena-key-settings-2026-09-22.json @@ -0,0 +1,97 @@ +{ + "date": "2026-09-22", + "scope": "Settings manual Jev API key override", + "baseCommit": "bc2434ae1e336b47a744604ab3b0cebcaac9b088", + "checks": { + "typecheck": "passed", + "offlineTests": { + "passed": 84, + "failed": 0 + }, + "productionBuild": "passed in isolated copy", + "secretScan": "passed", + "diffWhitespace": "passed", + "hostCredentialPrecedence": "Server default, personal override, replacement and return to default tested with an injected fake provider" + }, + "browser": { + "settings": { + "checks": [ + "manual override is discoverable under Settings", + "settings identifies the default key source", + "empty drafts cannot replace an existing key", + "manual entry is masked", + "saving clearly activates the manual override", + "saving clears the draft and makes no provider request", + "settings drawer fits 320px", + "settings drawer fits 390px", + "settings drawer fits 768px", + "settings drawer fits 1440px", + "Escape returns focus to Settings", + "the comparison sends the manual override header", + "replacement persists across refresh without revealing its value", + "the next request uses the replacement key", + "removing the override restores the default source", + "default mode omits the override header", + "closing settings discards an unsaved draft", + "storage failure does not claim an override was activated", + "a key change in another tab updates Settings and invalidates stale results", + "cross-tab key changes clear stale drafts and save confirmations", + "cross-tab removal clears the active override indicator" + ], + "count": 21, + "providerCalls": 0, + "liveCliCalls": 0, + "humanAccessibilityAcceptance": "not performed" + }, + "arenaControls": { + "checks": [ + "four examples expose their intent before selection", + "keyboard selection updates the task preview", + "selecting examples never starts a request", + "details open with focus inside the overlay at 1440px", + "opening details preserves the underlying layout at 1440px", + "drawer fits and locks background scrolling at 1440px", + "background controls are inert while details are open at 1440px", + "Escape closes details and restores focus at 1440px", + "details open with focus inside the overlay at 390px", + "opening details preserves the underlying layout at 390px", + "drawer fits and locks background scrolling at 390px", + "background controls are inert while details are open at 390px", + "Escape closes details and restores focus at 390px", + "details open with focus inside the overlay at 320px", + "opening details preserves the underlying layout at 320px", + "drawer fits and locks background scrolling at 320px", + "background controls are inert while details are open at 320px", + "Escape closes details and restores focus at 320px", + "clicking the backdrop closes the drawer", + "saved credentials are never redisplayed after reload", + "personal credentials travel only in the request header", + "run snapshots exclude credentials", + "storage failure is visible and preserves the current results", + "quota failure preserves the previous cache", + "failed unsaved runs retain an explicit in-memory result", + "unsaved early failures can download their original status and timestamps", + "usage dashboard records reported Jev usage independently", + "clearing usage preserves saved comparisons", + "clearing comparisons preserves the API key", + "removing a saved API key clears the browser override" + ], + "count": 30, + "providerCalls": 0, + "liveCliCalls": 0, + "humanAccessibilityAcceptance": "not performed" + } + }, + "visualInspection": { + "sizes": [ + 390, + 1440 + ], + "surface": "Settings drawer with manual override active", + "result": "reviewed screenshots" + }, + "limitations": [ + "No live credential validation or provider calls.", + "Human keyboard-only and VoiceOver acceptance not performed." + ] +} diff --git a/scripts/verify-arena-controls-browser.mjs b/scripts/verify-arena-controls-browser.mjs index 593144d..e3e96d5 100644 --- a/scripts/verify-arena-controls-browser.mjs +++ b/scripts/verify-arena-controls-browser.mjs @@ -39,11 +39,11 @@ export async function verifyArenaControls(page, baseURL, screenshotDir) { await page.getByRole("button", { name: "How the comparison works" }).click(); await page.mouse.click(10, 400); check(await page.locator(".detail-dialog[open]").count() === 0, "clicking the backdrop closes the drawer"); - await page.locator("#key-settings").click(); await page.locator("#api-key").fill(credential); await page.getByRole("button", { name: "Save key", exact: true }).click(); - await page.reload(); await page.getByRole("button", { name: "API key · saved", exact: true }).waitFor(); + await page.locator("#key-settings").click(); await page.locator("#api-key").fill(credential); await page.getByRole("button", { name: "Save override", exact: true }).click(); + await page.reload(); await page.getByRole("button", { name: "Settings, manual key override active", exact: true }).waitFor(); await page.locator("#key-settings").click(); check(await page.locator("#api-key").inputValue() === "", "saved credentials are never redisplayed after reload"); - await page.getByRole("button", { name: "Close API key settings" }).click(); + await page.getByRole("button", { name: "Close settings" }).click(); await run(); check(requests[0].headers["x-typesafe-api-key"] === credential && !requests[0].body.includes(credential), "personal credentials travel only in the request header"); check(await page.evaluate(([key, value]) => { const raw = localStorage.getItem(key); return JSON.parse(raw).runs.length === 1 && !raw.includes(value); }, [historyKey, credential]), "run snapshots exclude credentials"); @@ -68,7 +68,7 @@ export async function verifyArenaControls(page, baseURL, screenshotDir) { await page.getByRole("tab", { name: "History" }).click(); await page.getByRole("button", { name: "Clear local history", exact: true }).click(); await page.getByRole("button", { name: "Clear history", exact: true }).click(); await page.getByText("Your next run starts the timeline", { exact: true }).waitFor(); check(await page.evaluate(key => Boolean(localStorage.getItem(key)), keyStorage), "clearing comparisons preserves the API key"); - await page.locator("#key-settings").click(); await page.getByRole("button", { name: "Remove key", exact: true }).click(); + await page.locator("#key-settings").click(); await page.getByRole("button", { name: "Remove override", exact: true }).click(); check(await page.evaluate(key => localStorage.getItem(key) === null, keyStorage), "removing a saved API key clears the browser override"); return { checks, count: checks.length, providerCalls: 0, liveCliCalls: 0, humanAccessibilityAcceptance: "not performed" }; } finally { await page.unroute("**/api/arena"); } diff --git a/scripts/verify-arena-settings-browser.mjs b/scripts/verify-arena-settings-browser.mjs new file mode 100644 index 0000000..2571d3f --- /dev/null +++ b/scripts/verify-arena-settings-browser.mjs @@ -0,0 +1,55 @@ +/** Manual override lifecycle, with synthetic credentials and intercepted requests only. */ +export async function verifyArenaSettings(page, baseURL, screenshotDir) { + const checks = [], requests = []; + const key = "typesafe-api-key-override", first = "synthetic-manual-credential", second = "synthetic-replacement-credential"; + const check = (ok, label) => { if (!ok) throw Error(label); checks.push(label); }; + await page.route("**/api/arena", route => { requests.push(route.request().headers()); return route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ error: "Synthetic unavailable response; no provider called." }) }); }); + const open = () => page.locator("#key-settings").click(); + const close = () => page.getByRole("button", { name: "Close settings", exact: true }).click(); + const run = async () => { await page.getByRole("button", { name: "Run comparison" }).click(); await page.waitForFunction(() => !document.querySelector(".arena-run-actions .primary").disabled); }; + try { + await page.goto(baseURL); + check(await page.getByRole("button", { name: "Settings", exact: true }).isVisible(), "manual override is discoverable under Settings"); + await open(); + check(await page.getByRole("heading", { name: "Jev API key", exact: true }).isVisible() && await page.getByText("Server default", { exact: true }).isVisible(), "settings identifies the default key source"); + check(await page.getByRole("button", { name: "Save override", exact: true }).isDisabled(), "empty drafts cannot replace an existing key"); + check(await page.getByLabel("Override API key", { exact: true }).getAttribute("type") === "password", "manual entry is masked"); + await page.locator("#api-key").fill(first); await page.getByRole("button", { name: "Save override", exact: true }).click(); + check(await page.getByText("Manual override", { exact: true }).isVisible() && await page.getByText("Manual override saved.", { exact: false }).isVisible(), "saving clearly activates the manual override"); + check(await page.locator("#api-key").inputValue() === "" && requests.length === 0, "saving clears the draft and makes no provider request"); + for (const width of [320, 390, 768, 1440]) { + await page.setViewportSize({ width, height: 900 }); + check(await page.locator("#key-dialog").evaluate(e => { const rect = e.getBoundingClientRect(); return rect.left >= 0 && rect.right <= innerWidth + 1 && rect.height <= innerHeight + 1 && e.scrollWidth <= e.clientWidth; }), `settings drawer fits ${width}px`); + if (screenshotDir && [390, 1440].includes(width)) await page.screenshot({ path: `${screenshotDir}/arena-settings-${width}.png` }); + } + await page.keyboard.press("Escape"); + check(await page.locator("#key-settings").evaluate(e => e === document.activeElement), "Escape returns focus to Settings"); + await run(); + check(requests[0]["x-typesafe-api-key"] === first, "the comparison sends the manual override header"); + await open(); await page.getByLabel("Replacement API key", { exact: true }).fill(second); await page.getByRole("button", { name: "Replace override", exact: true }).click(); await close(); + await page.reload(); await open(); + check(await page.getByText("Manual override", { exact: true }).isVisible() && await page.locator("#api-key").inputValue() === "", "replacement persists across refresh without revealing its value"); + await close(); await run(); + check(requests[1]["x-typesafe-api-key"] === second, "the next request uses the replacement key"); + await open(); await page.getByRole("button", { name: "Remove override", exact: true }).click(); + check(await page.getByText("Server default", { exact: true }).isVisible() && await page.evaluate(key => localStorage.getItem(key) === null, key), "removing the override restores the default source"); + await close(); await run(); + check(requests[2]["x-typesafe-api-key"] === undefined, "default mode omits the override header"); + await open(); await page.locator("#api-key").fill("synthetic-unsaved-draft"); await page.keyboard.press("Escape"); await open(); + check(await page.locator("#api-key").inputValue() === "", "closing settings discards an unsaved draft"); + await page.evaluate(key => { window.originalKeyWrite = Storage.prototype.setItem; Storage.prototype.setItem = function (name, value) { if (name === key) throw new DOMException("Synthetic storage failure", "QuotaExceededError"); return window.originalKeyWrite.call(this, name, value); }; }, key); + await page.locator("#api-key").fill(first); await page.getByRole("button", { name: "Save override", exact: true }).click(); + check(await page.getByRole("alert").getByText("Could not update the override", { exact: false }).isVisible() && await page.getByText("Server default", { exact: true }).isVisible(), "storage failure does not claim an override was activated"); + await page.evaluate(() => { Storage.prototype.setItem = window.originalKeyWrite; }); await close(); + const other = await page.context().newPage(); await other.goto(baseURL); + await other.evaluate(([key, value]) => localStorage.setItem(key, value), [key, first]); + await page.getByRole("button", { name: "Settings, manual key override active", exact: true }).waitFor(); + check(await page.locator(".arena-welcome").isVisible(), "a key change in another tab updates Settings and invalidates stale results"); + await open(); await page.locator("#api-key").fill(second); await page.getByRole("button", { name: "Replace override", exact: true }).click(); await page.locator("#api-key").fill("synthetic-unsaved-draft"); + await other.evaluate(key => localStorage.removeItem(key), key); await other.close(); + await page.getByRole("button", { name: "Settings", exact: true }).waitFor(); + check(await page.locator("#api-key").inputValue() === "" && (await page.locator("#key-dialog .key-message[role=status]").textContent()) === "", "cross-tab key changes clear stale drafts and save confirmations"); + check(await page.locator(".key-override-dot").count() === 0, "cross-tab removal clears the active override indicator"); + return { checks, count: checks.length, providerCalls: 0, liveCliCalls: 0, humanAccessibilityAcceptance: "not performed" }; + } finally { await page.unroute("**/api/arena"); } +} diff --git a/tests/live-host.test.ts b/tests/live-host.test.ts index b2cf7db..9f608de 100644 --- a/tests/live-host.test.ts +++ b/tests/live-host.test.ts @@ -56,3 +56,18 @@ test("local origin validates the actual Host when Next canonicalizes the request assert.equal(localOrigin(new Request(url, { headers: { origin: "http://evil.example:4173", host: "evil.example:4173" } })), false); assert.equal(localOrigin(new Request(url, { headers: { origin: "http://127.0.0.1:4173", host: "localhost:4173" } })), false); }); + +test("manual override takes precedence and removing it restores the server key", async () => { + const authorizations: string[] = []; + const handler = createLiveHandler({ serverKey: "synthetic-server-credential", fetch: async (_url, init) => { + authorizations.push(new Headers(init?.headers).get("authorization") ?? ""); + return Response.json({ model: "jev-1.13.0", answers: { tool: { type: "choice", choice: "read_file", confidence: .9, probabilities: { read_file: .9, needs_clarification: .1 } } } }); + } }); + const base = "http://127.0.0.1:4173"; + for (const override of [undefined, "synthetic-manual-credential", "synthetic-replacement-credential", undefined]) { + const response = await handler(new Request(base + "/api/route", { method: "POST", headers: { origin: base, "content-type": "application/json", ...(override ? { "x-typesafe-api-key": override } : {}) }, body: JSON.stringify({ intent: "Read the synthetic file", availableIds: ["read_file"] }) })); + assert.equal(response.status, 200); + assert.ok(!(await response.text()).includes("credential")); + } + assert.deepEqual(authorizations, ["Bearer synthetic-server-credential", "Bearer synthetic-manual-credential", "Bearer synthetic-replacement-credential", "Bearer synthetic-server-credential"]); +});