Skip to content

feat(den): centralize AI Gateway access and usage management - #5139

Open
OmarMcAdam wants to merge 8 commits into
devfrom
cleanup-ai-gateway-models-replacement
Open

OmarMcAdam wants to merge 8 commits into
devfrom
cleanup-ai-gateway-models-replacement

Conversation

@OmarMcAdam

@OmarMcAdam OmarMcAdam commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Make AI Gateway a normal top-level sidebar page with Overview, AI Providers, Limits, Users & Teams, and the full OpenWork Models subscription/usage/model-lineup content. Retain legacy BYOK pages and access from the AI Providers tab, not the sidebar.
  • Keep provider detail/new/edit flows inside the Gateway shell. Remove Old Gateway routes/helpers/navigation; old list/new/detail/edit URLs return 404. BYOK migration uses the new provider detail route. Older /dashboard/inference links forward to the Models tab with repeated query parameters preserved.
  • Centralize provider/model-group grants and usage-limit assignments in Users & Teams using shared pickers, colored organization/user/team cards, inherited-team filtering, and safe reauthentication/uncertain-write handling.
  • Reorganize Limits into increase requests, a collapsing user-usage inspector, and compact policy rows. Reuse Den UI primitives (DESIGN P5) and progressive disclosure (P3).
  • Add isolated synthetic demo fixtures and regression coverage.

General availability

Retire the organization-level gatewayDashboard rollout flag everywhere it controlled access: dashboard/migration gates, internal client capability types, admin toggle/write semantics, organization creation metadata, fixtures, and current operational documentation. Stored missing/false/malformed flag values cannot block Gateway. No opt-in or backfill is needed.

For staggered-deployment compatibility, the API retains a deprecated gatewayDashboard: true response and accepts validated boolean/null admin inputs as no-ops. They are no longer feature controls. New clients do not consult the field; obsolete metadata is ignored/cleaned. API/SDK contracts were regenerated.

Unchanged: admin/membership/reauthentication rules, provider/model/credential grants, usage enforcement, subscription/DPA rules, and deployment configuration. GATEWAY_ENABLED and deploymentCapabilities.aiGateway remain installation/configuration controls for deployments that may not run the Gateway service. This change does not enable inference without valid configuration and credentials.

Database migration and rollout

One migration relative to dev: 0108_gateway_usage_organization_assignments.sql, plus snapshot/journal. Flag retirement adds no migration.

  • Adds an organization-wide usage-policy target, preserving member/team assignments, exclusive-target validation, and per-organization/policy uniqueness.
  • Everyone covers current and future members with independent usage buckets, not a fixed expansion over existing members.
  • Apply migration before deploying updated readers/writers; upgrade API/Gateway instances before creating Everyone assignments. Old readers do not enforce them.
  • No existing data is deleted. Rollback must account for Everyone assignments already created, not silently ignore them.

Verification

Final head: 33acec1e3530664abffad1258cfb4ca2531b6945.

  • 228 frontend regression tests passed for GA, access guards, navigation, migration exposure, Models independence, and organization switching.
  • 75 API/DB tests passed: 55 capability/auth/deployment unit tests plus 20 isolated admin-capability and provider DB tests. No failures or skips.
  • Den-web and Den API non-incremental TypeScript passed. SDK generation/check passed; API lint: 0 errors, 39 warnings.
  • Earlier unchanged migration/usage coverage passed: 8 unit/schema tests, migration upgrade/data-preservation/uniqueness/invalid-target checks, 6 organization-assignment DB tests, and 4 usage API tests.
  • Final-head cold-boot E2E passed: pnpm evals:e2e gateway-usage-policy --local — 1 passed, 0 failed, 0 skipped in an isolated checkout. The fixture asserts a fresh organization has no Gateway opt-in override and management GETs return 200. The journey verifies central assignments, Models-tab redirect/query preservation, removed routes, nested provider forms, quota blocking, increase approval, Desktop recovery, and unauthorized-member denial using mocked inference. No real Stripe calls.

Evidence scope

Focused verification, not the full repository matrix. Deployment-disabled behavior is covered by focused tests, not this browser journey. Current screenshots are synthetic-only 1440×1200 captures. The recorder contains 19 passed assertions and 13 reference screenshots without recorded automated visual validation; formal visual evidence completeness remains Incomplete.

Screenshots

Current-head Users & Teams, Limits, and OpenWork Models, with the top-level AI Gateway navigation.

Users and Teams generally available

Limits generally available

OpenWork Models tab

@OmarMcAdam
OmarMcAdam requested a review from a team September 18, 2026 16:18
@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
openwork-den Ready Ready Preview Sep 18, 2026 6:39pm UTC
3 Skipped Deployments
Project Deployment Actions Updated
openwork-app Ignored Ignored Preview Sep 18, 2026 6:39pm UTC
openwork-diagnostics Ignored Ignored Preview Sep 18, 2026 6:39pm UTC
openwork-landing Ignored Ignored Preview Sep 18, 2026 6:39pm UTC

@vercel
vercel Bot temporarily deployed to Preview – openwork-diagnostics September 18, 2026 18:07 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openwork-app September 18, 2026 18:07 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openwork-landing September 18, 2026 18:07 Inactive
@mintlify

mintlify Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
differentai 🟢 Ready View Preview Sep 18, 2026, 6:38 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@OmarMcAdam

OmarMcAdam commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Test evidence — GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension — Incomplete (visual review pending)

SHA 33acec1 · engine v1

Behavioral journey: passed (1 passed, 0 failed, 0 skipped). Visual evidence: incomplete.

19 recorded assertions passed on the PR head, with no organization Gateway opt-in in the fixture. The 13 reference screenshots have no recorded automated visual validation and are not visual pass claims.

Reproduce: pnpm evals:e2e gateway-usage-policy --local (cold-boot isolated checkout).

[world] den(local) · [seed] api GET /v1/org · [seed] api GET /v1/inference-providers?scope=manageable · [seed] api GET /v1/gateway/usage-limit-policies · [seed] api POST /v1/inference-providers · [seed] api GET /v1/inference-providers/ipr_01m2twp15kek7r1fhy178m64s0/connect · web(signed in) · desktop(as member)
[probe] api(GET /v1/gateway/usage-limits/me) ×2
[user] navigate(/dashboard/ai-gateway) · see(label=Create policy, timeoutMs=90000)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=limits) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="Limits"]")
[user] notSee(testId=gateway-users-teams) · label=Create policy · click(label=Create policy) · see(label=Timeframe 1, value="1 month") · type(label=Policy name, "Journey monthly allowance") · type(label=USD Amount 1, "1.000000")
[probe] dom("[role="dialog"]") ×2 · eventually(Den policy editor settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · label=Save policy · click(label=Save policy) · see(label=Edit Journey monthly allowance) · notSee(label=Assignments for Journey monthly allowance)
[probe] dom("[role="dialog"]") · eventually(Den editor closes after save) · api(GET /v1/gateway/usage-limit-policies) · dom("[aria-labelledby="gateway-usage-limits-heading"] tbody tr") ×3 · eventually(Den Limits policy rows settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · label=Users & Teams · click(label=Users & Teams) · see(testId=gateway-users-teams)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=users-and-teams) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="Users & Teams"]")
[user] notSee(label=Create policy) · notSee(label=Find person to inspect) · notSee(label=Refresh requests) · label=Apply new usage limit · click(label=Apply new usage limit)
[probe] dom("[role="dialog"] button:disabled")
[user] label=People (0) · click(label=People (0)) · type(placeholder=Search people..., "") · label=/Usage Member[\s\S]*usage-member@example.test/ · click(label=/Usage Member[\s\S]*usage-member@example.test/) · label=Continue · click(label=Continue) · label=Usage limit policy · click(label=Usage limit policy) · label=/Journey monthly allowance/ · click(label=/Journey monthly allowance/)
[probe] dom("[role="dialog"]") ×2 · eventually(Den central usage assignment form settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · label=Apply usage limit · click(label=Apply usage limit)
[probe] dom("[role="dialog"]") ×2 · eventually(Den editor closes after save)
[user] see(label=Unassign Journey monthly allowance from Usage Member)
[probe] api(GET /v1/gateway/usage-limit-policies) · eventually(Den assignment persisted) · api(GET /v1/gateway/usage-limit-policies) · dom("[data-testid="gateway-subject-card"]") · dom("[data-subject="member:om_01m2twnzcseyca3crkf057cy49"]") · dom("[data-subject="member:om_01m2twnznwe7eatw68nz6ms7ny"]") · dom("[data-subject="organization"]") · dom("[data-testid="gateway-subject-card"]") ×2 · eventually(Den Users and Teams assignment cards settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot
[probe] api(GET /v1/gateway/usage-limits/me)
[user] label=AI Providers · click(label=AI Providers) · see(testId=gateway-provider-create)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=ai-providers) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="AI Providers"]")
[user] notSee(testId=gateway-users-teams) · testId=gateway-provider-create · click(testId=gateway-provider-create) · see(label=Add an AI Gateway provider)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway/providers/new) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="AI Providers"]")
[user] see(label=Provider) · see(testId=gateway-provider-name, value="") · label=Back · click(label=Back) · see(testId=gateway-provider-open) · testId=gateway-provider-open · click(testId=gateway-provider-open) · see(testId=gateway-provider-edit)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway/providers/ipr_01m2twp15kek7r1fhy178m64s0) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="AI Providers"]")
[user] testId=gateway-provider-edit · click(testId=gateway-provider-edit) · see(testId=gateway-provider-name, value="Usage journey provider")
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway/providers/ipr_01m2twp15kek7r1fhy178m64s0/edit) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="AI Providers"]")
[user] see(label=Save provider and models) · notSee(label=Apply new usage limit) · label=Limits · click(label=Limits) · see(label=Find person to inspect)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=limits) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="Limits"]")
[user] label=Find person to inspect · click(label=Find person to inspect) · type(label=Find person to inspect, "") · label=/Usage Member[\s\S]*usage-member@example.test/ · click(label=/Usage Member[\s\S]*usage-member@example.test/) · see(text=Within allowance)
[probe] dom("[aria-label="Usage for Usage Member"]")
[user] see(text=$0.00 used) · label=Change person · click(label=Change person) · label=Find person to inspect · click(label=Find person to inspect) · type(label=Find person to inspect, "") · label=/Usage Control[\s\S]*usage-control@example.test/ · click(label=/Usage Control[\s\S]*usage-control@example.test/) · see(text=Unlimited)
[probe] dom("[aria-label="Usage for Usage Control"]")
[user] notSee(text=$0.00 used) · label=Change person · click(label=Change person) · notSee(label=Old Gateway) · navigate(/dashboard/gateway-providers) · see(label=404) · notSee(testId=gateway-provider-open) · notSee(testId=ai-gateway-tabs)
[probe:raw] [probe:raw] eval()
[user] navigate(/dashboard/gateway-providers/new) · see(label=404) · notSee(testId=gateway-provider-open) · notSee(testId=ai-gateway-tabs)
[probe:raw] [probe:raw] eval()
[user] navigate(/dashboard/gateway-providers/ipr_01m2twp15kek7r1fhy178m64s0) · see(label=404) · notSee(testId=gateway-provider-open) · notSee(testId=ai-gateway-tabs)
[probe:raw] [probe:raw] eval()
[user] navigate(/dashboard/gateway-providers/ipr_01m2twp15kek7r1fhy178m64s0/edit) · see(label=404) · notSee(testId=gateway-provider-open) · notSee(testId=ai-gateway-tabs)
[probe:raw] [probe:raw] eval()
[user] navigate(/dashboard/inference) · see(label=OpenWork Models)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=openwork-models&source=legacy&tag=one&tag=two) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="OpenWork Models"]")
[user] see(label=Models) · notSee(label=OpenWork Models) · notSee(label=Apply new usage limit)
[probe] dom("h1") · dom("[role="tabpanel"][aria-label="OpenWork Models"]") ×2 · eventually(Den OpenWork Models tab settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · navigate(/dashboard/ai-gateway) · see(label=Refresh requests)
[probe:raw] [probe:raw] eval()
[probe] eventually(Den route /dashboard/ai-gateway?tab=limits) · dom("[role="tab"][aria-selected="true"]") · dom("[role="tabpanel"][aria-label="Limits"]") · api(GET /v1/gateway/usage-limit-policies)
[agent] run(route.settings.appearance)
[user] label=Light · click(label=Light)
[probe:raw] [probe:raw] eval()
[probe] eventually(source Light theme selected for readable screenshots)
[user] label=Back to app · click(label=Back to app)
[agent] createSession("Gateway usage composer verification") · run(session.model_picker.open)
[user] type(placeholder=Search providers and models..., "gwm_01m2twp15sewnt22gyyty3vd78_01m2twp15pe82rh7ffkbdgx8pa_01m2twp15nevd9d5sbpsaxeqqz") · see(label=/gwm_01m2twp15sewnt22gyyty3vd78_01m2twp15pe82rh7ffkbdgx8pa_01m2twp15nevd9d5sbpsaxeqqz/, timeoutMs=90000) · label=/gwm_01m2twp15sewnt22gyyty3vd78_01m2twp15pe82rh7ffkbdgx8pa_01m2twp15nevd9d5sbpsaxeqqz/ · click(label=/gwm_01m2twp15sewnt22gyyty3vd78_01m2twp15pe82rh7ffkbdgx8pa_01m2twp15nevd9d5sbpsaxeqqz/) · notSee(placeholder=Search providers and models...)
[probe] composer
[user] notSee(testId=gateway-usage-notice)
[probe] api(GET /v1/gateway/usage-limits/me?memberId=om_01m2twnzcseyca3crkf057cy49) · api(GET /v1/gateway/usage-limit-policies) · api(GET /v1/gateway/usage-limit-reset-requests) · api(GET /v1/gateway/usage-limits/members/om_01m2twnznwe7eatw68nz6ms7ny)
[seed] [seed] api POST /v1/gateway/usage-limit-policies
[probe] api(GET /v1/gateway/usage-limit-policies)
[user] see(label=Usage limits, timeoutMs=90000) · label=Usage limits · click(label=Usage limits) · see(label=Monthly) · see(text=$0.00 used / $1.00 total) · notSee(label=Request Increase — Monthly)
[probe] api(GET /v1/gateway/usage-limits/me) ×2 · eventually(real Gateway settles reported $1) · api(GET /v1/gateway/usage-limits/me) ×2
[user] label=Refresh usage · click(label=Refresh usage) · see(text=$1.00 used / $1.00 total) · see(label=Request Increase — Monthly)
[probe] dom("[aria-label="Monthly usage"]") ×2 · eventually(Desktop exhausted usage settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · press(Escape)
[probe] eventually(direct Gateway rejection finalized)
[user] type(composer, "Summarize the remaining work for this synthetic review.")
[probe] composer
[agent] run(composer.send)
[probe] eventually(native engine request rejected by the real Gateway) · eventually(native engine records the submitted prompt and terminal assistant error) · api(GET /v1/gateway/usage-limits/me)
[user] see(testId=gateway-usage-notice, text=/Out of usage/)
[probe] dom("[data-testid="gateway-usage-notice"]") · composer · dom("[data-testid="gateway-usage-notice"]") ×2 · eventually(Desktop blocked composer settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · see(label=/^Request Increase$/)
[seed] [seed] api POST /v1/gateway/usage-limit-reset-requests
[probe] api(GET /v1/gateway/usage-limit-reset-requests)
[user] label=/^Request Increase$/ · click(label=/^Request Increase$/) · see(label=Reason (required)) · type(label=Reason (required), "Finish the synthetic integration review")
[probe] dom("[role="dialog"]") ×2 · eventually(Desktop direct increase form settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot
[probe] dom("[role="dialog"] button[type="submit"]") · dom("button")
[user] label=/^Request Increase$/ · click(label=/^Request Increase$/)
[probe] api(GET /v1/gateway/usage-limit-reset-requests/me) ×2 · eventually(one Desktop increase request persisted) · dom("[role="dialog"]") · eventually(submitted increase dialog closes)
[user] notSee(label=Reason (required)) · label=Usage limits · click(label=Usage limits) · see(text=Increase request pending) · notSee(label=Request Increase — Monthly)
[probe] dom("[aria-label="Monthly usage"]") ×2 · eventually(Desktop pending increase settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot
[probe] api(GET /v1/gateway/usage-limit-reset-requests) · api(GET /v1/gateway/usage-limit-reset-requests/me) ×2
[seed] [seed] api POST /v1/gateway/usage-limit-reset-requests/67786603-8270-48d4-9f4e-c88b1bfd5398/approve
[probe] api(GET /v1/gateway/usage-limits/me)
[user] label=Refresh requests · click(label=Refresh requests) · see(text=Finish the synthetic integration review) · see(text=+$0.25 allowance ($1.25 total); may increase provider charges; no undo.)
[probe] dom("[aria-label="Pending increase request pages"] tbody tr") ×2 · eventually(Den pending request row settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · label=Approve 25% for Usage Member, 1 month · click(label=Approve 25% for Usage Member, 1 month) · see(text=Request approved. Check the queue and history for the latest state.)
[probe] api(GET /v1/gateway/usage-limits/me) · eventually(approval restores real allowance) · api(GET /v1/gateway/usage-limit-reset-requests) · api(GET /v1/gateway/usage-limit-reset-requests?view=history) · api(GET /v1/gateway/usage-limit-reset-requests/me?view=history) ×2
[user] see(text=No pending requests)
[probe] dom("[aria-label="Pending increase request pages"] tbody tr")
[user] notSee(label=Approve 25% for Usage Member, 1 month) · text=Previous requests · click(text=Previous requests) · see(text=Reviewer: Usage Admin)
[probe] dom("#gateway-reset-history tbody tr:nth-child(odd)") · dom("#gateway-reset-history tbody tr:nth-child(even)")
[user] notSee(label=Load more history) · see(text=Reviewer: Usage Admin)
[probe] dom("#gateway-reset-history tbody tr") ×2 · eventually(Den approved history settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot · label=Refresh usage · click(label=Refresh usage) · see(text=$1.00 used / $1.25 total) · see(text=Increase request: approved) · notSee(text=Increase request pending) · notSee(label=Request Increase — Monthly) · press(Escape) · notSee(testId=gateway-usage-notice) · see(testId=gateway-usage-approved-notice, text=/Usage increase approved/)
[probe] composer · dom("[data-testid="gateway-usage-approved-notice"]") ×2 · eventually(Desktop approved increase settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot
[probe] api(GET /v1/gateway/usage-limits/me)
[user] type(composer, "Continue the synthetic review with one brief plain-text summary. Do not use tools.")
[probe] composer · eventually(approved session is ready for a new composer send)
[agent] run(composer.send)
[probe] eventually(post-approval native assistant finishes successfully)
[user] see(text=Complete café) · see(Run task, timeoutMs=30000)
[probe] composer · eventually(post-approval native stream settles known upstream cost) · api(GET /v1/gateway/usage-limits/me) · eventually(native completion accounting writes finish) · api(GET /v1/gateway/usage-limits/me) · dom("[data-message-role="assistant"][data-message-id="msg_0b5cdf5ad001ED92fSBQFbLon3"]") ×3 · eventually(Desktop native recovery completed settled visible geometry)
[probe:raw] [probe:raw] eval()
[user] screenshot
steps 1 ✅ admin creates and assigns the monthly hard policy in rendered Den (19.2s) · 2 ✅ nested provider forms retain AI Providers and Limits owns the member inspector (48.0s) · 3 ✅ member selects the actual managed Gateway model in a real Desktop session (8.5s) · 4 ✅ own status is identity scoped and members cannot administer policies or another person's usage (3.2s) · 5 ✅ known settled upstream cost exhausts the bucket and a second real Gateway admission is blocked (1.0s) · 6 ✅ real composer submission reaches the Gateway through the native engine and shows truthful own-status exhaustion (1.5s) · 7 ✅ member submits a required reason from Desktop and cannot review the request (7.7s) · 8 ✅ admin reviews the actual reason in Den and approves exactly 25% without forgiving consumption (74.1s) · 9 ✅ after approval the same Desktop composer completes a native assistant turn through the real Gateway (1.5s)
verdict passed · 87 user observations (see ×45, notSee ×29, screenshot ×13) · 166 probes · steps 9/9

ℹ️ ASSERTION — 1. Den policy editor: real app screenshot and DOM dimensions

{"hash":"dd3b1cec0be06e52c294f0610807d71c40d08bfe23c10cce588829667d0cdbf5","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Den policy editor: real app screenshot and DOM dimensions — {"hash":"dd3b1cec0be06e52c294f0610807d71c40d08bfe23c10cce588829667d0cdbf5","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 2. Den Limits policy rows: real app screenshot and DOM dimensions

{"hash":"ba812a04602e7f242ec435a07bb6949ad243e7b79b9c06deeb4bc2ae2a954538","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Den Limits policy rows: real app screenshot and DOM dimensions — {"hash":"ba812a04602e7f242ec435a07bb6949ad243e7b79b9c06deeb4bc2ae2a954538","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 3. Den central usage assignment form: real app screenshot and DOM dimensions

{"hash":"586b363ea9d694e285e20e8efc7d1cb9a0eda4bc65130bb6c01d51c5fd69bc54","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Den central usage assignment form: real app screenshot and DOM dimensions — {"hash":"586b363ea9d694e285e20e8efc7d1cb9a0eda4bc65130bb6c01d51c5fd69bc54","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 4. Den Users and Teams assignment cards: real app screenshot and DOM dimensions

{"hash":"3367c8aa257b48bc616a583ca6db0d4580885f2650ad0a6f92a3dc793b3111b5","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

  • PASS Den Users and Teams assignment cards: real app screenshot and DOM dimensions — {"hash":"3367c8aa257b48bc616a583ca6db0d4580885f2650ad0a6f92a3dc793b3111b5","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

ℹ️ ASSERTION — 5. Den OpenWork Models tab: real app screenshot and DOM dimensions

{"hash":"bcd206eb35a62b2dc5da6338f5682912a717d29b9d0a746d826355234acebbe3","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Den OpenWork Models tab: real app screenshot and DOM dimensions — {"hash":"bcd206eb35a62b2dc5da6338f5682912a717d29b9d0a746d826355234acebbe3","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 6. AI Gateway tab and nested form ownership

New/edit provider forms stay within AI Providers without saving changes. The old Gateway sidebar link is absent and its list/new/detail/edit URLs show 404 without redirecting. Central Users & Teams assigns only Usage Member; Limits inspector reports that member within allowance and the unassigned control unlimited. The legacy Models URL forwards to the OpenWork Models tab with repeated query values intact and no duplicate sidebar link or page heading. No upstream inference calls occurred.

  • PASS AI Gateway tab and nested form ownership — New/edit provider forms stay within AI Providers without saving changes. The old Gateway sidebar link is absent and its list/new/detail/edit URLs show 404 without redirecting. Central Users & Teams assigns only Usage Member; Limits inspector reports that member within allowance and the unassigned control unlimited. The legacy Models URL forwards to the OpenWork Models tab with repeated query values intact and no duplicate sidebar link or page heading. No upstream inference calls occurred.

ℹ️ ASSERTION — 7. Rendered Den assignment reaches only the intended member's real Desktop

Monthly $1 hard/reset-enabled policy persisted through Den UI. Own-status identity injection did not change the control member; management requests returned 403; Desktop rendered zero used of $1.

  • PASS Rendered Den assignment reaches only the intended member's real Desktop — Monthly $1 hard/reset-enabled policy persisted through Den UI. Own-status identity injection did not change the control member; management requests returned 403; Desktop rendered zero used of $1.

ℹ️ ASSERTION — 8. Desktop exhausted usage: real app screenshot and DOM dimensions

{"hash":"a9dc721415af79757803910a3428aacd70ca51102ede806cd2fe3dfcda0c1716","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop exhausted usage: real app screenshot and DOM dimensions — {"hash":"a9dc721415af79757803910a3428aacd70ca51102ede806cd2fe3dfcda0c1716","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 9. Gateway, not Desktop, blocks after known consumption

First request settled 1000000 micro-USD; second returned trusted policy HTTP 429 without a second upstream call or charge. Desktop rendered exhaustion; the unassigned control stayed unlimited.

  • PASS Gateway, not Desktop, blocks after known consumption — First request settled 1000000 micro-USD; second returned trusted policy HTTP 429 without a second upstream call or charge. Desktop rendered exhaustion; the unassigned control stayed unlimited.

ℹ️ ASSERTION — 10. Desktop blocked composer: real app screenshot and DOM dimensions

{"hash":"f429ee4dd30fddb69b08eafd6e8ea1ebceffeacde35ddd177e40c8bb982f68e3","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop blocked composer: real app screenshot and DOM dimensions — {"hash":"f429ee4dd30fddb69b08eafd6e8ea1ebceffeacde35ddd177e40c8bb982f68e3","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 11. Native composer reaches the real Gateway and own status corroborates the custom notice

{"engine":"v1","rejectedBefore":1,"rejectedAfter":2,"upstreamRequests":1,"nativePromptRecorded":true,"nativeAssistantErrorRecorded":true,"usedMicroUsd":1000000}

  • PASS Native composer reaches the real Gateway and own status corroborates the custom notice — {"engine":"v1","rejectedBefore":1,"rejectedAfter":2,"upstreamRequests":1,"nativePromptRecorded":true,"nativeAssistantErrorRecorded":true,"usedMicroUsd":1000000}

ℹ️ ASSERTION — 12. Desktop direct increase form: real app screenshot and DOM dimensions

{"hash":"d22de898a6994a38781caafdd2363a6f9cb6e228d50981b0e82f4b15e227fb8b","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop direct increase form: real app screenshot and DOM dimensions — {"hash":"d22de898a6994a38781caafdd2363a6f9cb6e228d50981b0e82f4b15e227fb8b","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 13. Desktop pending increase: real app screenshot and DOM dimensions

{"hash":"ebafa453aa5b31f3e1633a283247ef507509fcdcf588555cdf54c2657757347e","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop pending increase: real app screenshot and DOM dimensions — {"hash":"ebafa453aa5b31f3e1633a283247ef507509fcdcf588555cdf54c2657757347e","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 14. Den pending request row: real app screenshot and DOM dimensions

{"hash":"b194d2c6947e44179da0c8cc8cf4102c90a498a776744370b5aa0b05decf9bef","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

  • PASS Den pending request row: real app screenshot and DOM dimensions — {"hash":"b194d2c6947e44179da0c8cc8cf4102c90a498a776744370b5aa0b05decf9bef","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

ℹ️ ASSERTION — 15. Den approved history: real app screenshot and DOM dimensions

{"hash":"04f83a206c0b469b0727987ceb1ec83e02fdb64e60f75c7e692e97b057b07b40","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

  • PASS Den approved history: real app screenshot and DOM dimensions — {"hash":"04f83a206c0b469b0727987ceb1ec83e02fdb64e60f75c7e692e97b057b07b40","width":1440,"height":1200,"viewport":{"width":1440,"height":1200,"scale":1,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":2}

ℹ️ ASSERTION — 16. Desktop approved increase: real app screenshot and DOM dimensions

{"hash":"08492fd0a333db27eaa35ea5a4cad1d55d8d76767bf37f0397cef856dfe7cd08","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop approved increase: real app screenshot and DOM dimensions — {"hash":"08492fd0a333db27eaa35ea5a4cad1d55d8d76767bf37f0397cef856dfe7cd08","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 17. Desktop native recovery completed: real app screenshot and DOM dimensions

{"hash":"3dc58fbc0a3c73567ae7d361913f65ad0401c1b888c6c7d0fca1793ac36970ca","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

  • PASS Desktop native recovery completed: real app screenshot and DOM dimensions — {"hash":"3dc58fbc0a3c73567ae7d361913f65ad0401c1b888c6c7d0fca1793ac36970ca","width":2360,"height":1640,"viewport":{"width":1180,"height":820,"scale":2,"readyState":"complete","visibility":"visible","focused":true,"theme":"light"},"elements":1}

ℹ️ ASSERTION — 18. Den approval restores actual Desktop composer completion, not a direct HTTP bypass

{"engine":"v1","nativeAssistantCompleted":true,"renderedAnswer":"Complete café","upstreamRequests":2,"streamed":true,"settledCostMicroUsd":1000000,"totalUsedMicroUsd":2000000,"additionalRejections":0}

  • PASS Den approval restores actual Desktop composer completion, not a direct HTTP bypass — {"engine":"v1","nativeAssistantCompleted":true,"renderedAnswer":"Complete café","upstreamRequests":2,"streamed":true,"settledCostMicroUsd":1000000,"totalUsedMicroUsd":2000000,"additionalRejections":0}

ℹ️ ASSERTION — 19. Desktop request and Den approval restore the same native session

The member submitted a required reason through the blocked-notice dialog; Den displayed it and granted exactly 250000 micro-USD without forgiving consumption. After approval, a real Desktop composer send produced a completed native assistant and rendered answer with one streaming upstream call and a settled $1 cost. Total usage became $2, correctly exhausting the $1.25 allowance again; the control member remained unlimited.

  • PASS Desktop request and Den approval restore the same native session — The member submitted a required reason through the blocked-notice dialog; Den displayed it and granted exactly 250000 micro-USD without forgiving consumption. After approval, a real Desktop composer send produced a completed native assistant and rendered answer with one streaming upstream call and a settled $1 cost. Total usage became $2, correctly exhausting the $1.25 allowance again; the control member remained unlimited.

⚪ UNVALIDATED — 20. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 1

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 1

⚪ UNVALIDATED — 21. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 3

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 3

⚪ UNVALIDATED — 22. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 5

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 5

⚪ UNVALIDATED — 23. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 7

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 7

⚪ UNVALIDATED — 24. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 9

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 9

⚪ UNVALIDATED — 25. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 13

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 13

⚪ UNVALIDATED — 26. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 16

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 16

⚪ UNVALIDATED — 27. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 19

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 19

⚪ UNVALIDATED — 28. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 21

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 21

⚪ UNVALIDATED — 29. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 23

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 23

⚪ UNVALIDATED — 30. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 25

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 25

⚪ UNVALIDATED — 31. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 27

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 27

⚪ UNVALIDATED — 32. GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 29

  • UNVALIDATED — no visual expectations recorded.

GATEWAY-USAGE-01 admin policy blocks member Gateway calls until a reviewed 25% extension artifact 29


Test run created 2026-09-18T18:32:26.652Z · Source: evals/results/test-runs/2026-09-18T18-32-26-651Z-gateway-usage-01-admin-policy-blocks-member-gateway-calls-until-a-reviewed-25-ex/test-run.json · Repro: pnpm --dir evals artifacts:publish -- --pr 5139 --test-run 2026-09-18T18-32-26-651Z-gateway-usage-01-admin-policy-blocks-member-gateway-calls-until-a-reviewed-25-ex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant