Skip to content

feat: add access_profile_id to virtual keys for direct access profile assignment - #3560

Merged
akshaydeo merged 1 commit into
devfrom
05-18-feat_assign_vk_to_access_profile
May 19, 2026
Merged

akshaydeo merged 1 commit into
devfrom
05-18-feat_assign_vk_to_access_profile

Conversation

@BearTS

@BearTS BearTS commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for attaching virtual keys directly to an access profile, enabling enterprise users to associate VKs with access profile templates as an alternative to team or customer assignment. Previously, virtual keys could only be assigned to a team or a customer (mutually exclusive). This change extends that model to include access profiles as a third mutually exclusive assignment target.

Changes

  • Added access_profile_id column and index to governance_virtual_keys via a new database migration (migrationAddVKAccessProfileIDColumn).
  • Extended TableVirtualKey with an AccessProfileID *uint field.
  • Updated CreateVirtualKeyRequest and UpdateVirtualKeyRequest HTTP handler structs to accept access_profile_id, and replaced the binary team/customer mutual-exclusion check with a three-way entity count guard.
  • In the update path, access_profile_id is persisted via a separate Updates call because the existing UpdateVirtualKey select list excludes it to protect config-sync paths.
  • Added useGetAccessProfilesQuery stub to the OSS fallback API layer so the UI degrades gracefully when the enterprise backend is absent.
  • Added GetAccessProfilesParams and GetAccessProfilesResponse types to the OSS fallback type definitions.
  • Updated VirtualKeySheet to support access_profile as a form entity type, including a defaultAccessProfileId prop, a locked-assignment mode (isAPLocked), a reassignment confirmation dialog, and a combobox for selecting an access profile.
  • Updated VirtualKeysTable to display an "AP: <name>" badge for VKs assigned to an access profile and to include access profile names in CSV exports.
  • Registered AccessProfileVirtualKeys as a cache tag in the base RTK API.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

  1. Run the service — the migration will automatically add access_profile_id to governance_virtual_keys.
  2. Create a virtual key via the API with access_profile_id set and verify it is persisted.
  3. Attempt to create a virtual key with both team_id and access_profile_id set — expect a 400 error.
  4. Update a virtual key to assign it to an access profile and confirm the previous team/customer assignment is cleared.
  5. In the UI (enterprise build), open the virtual key sheet and verify the "Assign to Access Profile" option appears when access profiles exist, the combobox populates correctly, and the reassignment warning dialog triggers when changing an already-assigned profile.
  6. Verify the virtual keys table shows the "AP: <name>" badge and that CSV export includes the access profile name.
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build

Breaking changes

  • Yes
  • No

Security considerations

access_profile_id follows the same access-control patterns as team_id and customer_id. No new auth surfaces are introduced. The OSS fallback returns undefined data, ensuring no enterprise-only data is exposed in non-enterprise builds.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Virtual keys can be assigned to access profiles; listing/filtering supports access_profile_id and exports include access profile names/IDs.
  • UI

    • Access profile option added to create/edit forms, with optional preselected/locked assignment and change confirmation.
    • Table shows Access Profile badges; CSV exports include access profile names.
    • OSS fallback hook and types added; combobox gains test-id prop.
  • Bug Fixes / Validation

    • Create/update enforce single assignment (team, customer, or access profile) and persist consistently.
  • Migrations

    • Migration adds access_profile_id column and index (idempotent; rollback supported).

Walkthrough

Adds Access Profile assignment for Virtual Keys across DB, backend, and frontend: migration and TableVirtualKey field; create/update validation and persistence; TypeScript types, fallback API, form UI with confirmation, table display, CSV exports, and a small combobox testid prop.

Changes

Virtual Key Access Profile Assignment

Layer / File(s) Summary
Database schema and migration
framework/configstore/tables/virtualkey.go, framework/configstore/migrations.go, framework/configstore/store.go, framework/configstore/rdb.go
TableVirtualKey adds optional AccessProfileID and mutual-exclusion comment. New idempotent migration creates access_profile_id column and index; query params gain AccessProfileID and paginated query supports filtering by it.
Backend request contracts and validation
transports/bifrost-http/handlers/governance.go
CreateVirtualKeyRequest and UpdateVirtualKeyRequest extended with AccessProfileID. Handlers parse access_profile_id query param, validate that exactly one of team_id/customer_id/access_profile_id is set, reconcile/clear conflicting assignments, persist access_profile_id on create and via explicit Updates call on update.
Frontend types and API integration
ui/lib/types/governance.ts, ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts, ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts, ui/lib/store/apis/baseApi.ts, ui/lib/store/apis/governanceApi.ts
Add access_profile_id to governance types and params. Define GetAccessProfilesParams and GetAccessProfilesResponse. Provide OSS fallback hook useGetAccessProfilesQuery. Add RTK Query tag type AccessProfileVirtualKeys and include access_profile_id in governance API requests when supplied.
Virtual Key form component
ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
Component accepts defaultAccessProfileId. Form schema adds entityType: "access_profile" and required accessProfileId. Loads access profiles, supports locked default assignment, clears/validates on entityType change, shows reassignment confirmation dialog on edit, and includes access_profile_id in create/update payloads.
Virtual Keys table display and CSV export
ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
Load access profiles and build a name lookup. CSV export helper accepts this mapping and exports "Access Profile: <name
UI prop change
ui/components/ui/combobox.tsx
Adds optional data-testid prop to ComboboxSelect props and applies it to the single-select trigger button.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • akshaydeo
  • danpiths

Poem

🐰 A key finds a profile, a new snug little den,
Migrations nibble soil, handlers tie the pen,
Forms ask twice before hopping a home,
Tables and CSVs sing names where they roam,
A rabbit applauds: tidy fields, one attachment again.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main feature: adding access_profile_id to virtual keys for direct access profile assignment.
Description check ✅ Passed The PR description comprehensively covers all required sections: summary, detailed changes, type of change, affected areas, testing instructions, breaking changes, security considerations, and a checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 05-18-feat_assign_vk_to_access_profile

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

BearTS commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@BearTS BearTS changed the title feat: assign VK to access profile feat: add access_profile_id to virtual keys for direct access profile assignment May 18, 2026
@BearTS
BearTS marked this pull request as ready for review May 18, 2026 08:39
@greptile-apps

greptile-apps Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Safe to merge after fixing the update-handler assignment branch; the zero-value edge case can corrupt entity assignment for a VK.

The update handler's else if req.AccessProfileID != nil branch lacks the > 0 guard applied everywhere else. Sending access_profile_id: 0 in a PATCH request silently writes 0 (not NULL) to the column while also clearing the existing team_id/customer_id.

transports/bifrost-http/handlers/governance.go — the update assignment if-else chain

Important Files Changed

Filename Overview
transports/bifrost-http/handlers/governance.go Extends create/update/list handlers for access profile assignment. The update assignment branch lacks the > 0 guard present in both the entityCount check and the BeforeSave hook.
ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx Adds full access-profile assignment UI: form schema extension, locked-assignment mode, reassignment confirmation dialogs, and combobox.
framework/configstore/tables/virtualkey.go Adds AccessProfileID *uint field and updates BeforeSave to a three-way mutual-exclusion count using a consistent > 0 guard.
framework/configstore/migrations.go Adds migrationAddVKAccessProfileIDColumn with idempotent column/index creation and rollback support.
ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx Adds AP badge display and CSV export support using a memoised id-to-name map.

Reviews (4): Last reviewed commit: "feat: assign VK to access profile" | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/governance.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/configstore/tables/virtualkey.go`:
- Around line 208-212: Update the VirtualKey model's BeforeSave hook to enforce
full 3-way exclusivity for TeamID, CustomerID, and AccessProfileID (not just
Team+Customer): in BeforeSave (on VirtualKey) count how many of TeamID != nil,
CustomerID != nil, and AccessProfileID != nil/zero are set and return an error
if more than one is set (and optionally validate if zero set is allowed by your
domain rules); ensure you treat AccessProfileID's zero value correctly when
checking. This centralizes the invariant so non-handler write paths cannot
create invalid combinations.

In `@ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx`:
- Around line 1323-1354: The Reassign AP dialog's interactive elements lack
data-testid attributes; add data-testid to the AlertDialogCancel and
AlertDialogAction (e.g., data-testid="reassign-ap-cancel" and
data-testid="reassign-ap-confirm") inside the showReassignAPWarning block and
also add a data-testid to the access profile selector control used to pick
pendingAccessProfileId (e.g., data-testid="access-profile-selector") so E2E
tests can reliably target form.setValue("accessProfileId", ...) flows and the AP
selector component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: efba1e25-5ec5-45e5-bf15-c7e7e3a80030

📥 Commits

Reviewing files that changed from the base of the PR and between 89a99f0 and 781c9a0.

📒 Files selected for processing (9)
  • framework/configstore/migrations.go
  • framework/configstore/tables/virtualkey.go
  • transports/bifrost-http/handlers/governance.go
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/types/governance.ts

Comment thread framework/configstore/tables/virtualkey.go
Comment thread ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
@BearTS
BearTS force-pushed the 05-18-feat_assign_vk_to_access_profile branch from 781c9a0 to 2527351 Compare May 18, 2026 10:32
@coderabbitai
coderabbitai Bot requested a review from akshaydeo May 18, 2026 10:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/configstore/tables/virtualkey.go`:
- Around line 208-211: The model currently treats any non-nil AccessProfileID
pointer as an assignment, allowing access_profile_id: 0 to bypass exclusivity
checks; update the validation where the struct's foreign-key exclusivity is
evaluated (the logic that inspects TeamID, CustomerID, and AccessProfileID) to
only count AccessProfileID when it is non-nil AND *AccessProfileID > 0 (i.e.,
treat zero as unset), so change checks like "if v.AccessProfileID != nil" to "if
v.AccessProfileID != nil && *v.AccessProfileID > 0" wherever exclusivity or
presence is computed for virtual key attachments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9703e87-719d-4f9e-95a4-bb129e42e62a

📥 Commits

Reviewing files that changed from the base of the PR and between 781c9a0 and 2527351.

📒 Files selected for processing (10)
  • framework/configstore/migrations.go
  • framework/configstore/tables/virtualkey.go
  • transports/bifrost-http/handlers/governance.go
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/ui/combobox.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/types/governance.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • ui/lib/store/apis/baseApi.ts
  • transports/bifrost-http/handlers/governance.go
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/lib/types/governance.ts
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • framework/configstore/migrations.go
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx

Comment thread framework/configstore/tables/virtualkey.go
@BearTS
BearTS force-pushed the 05-18-feat_assign_vk_to_access_profile branch from 2527351 to 243adc1 Compare May 18, 2026 10:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/configstore/rdb.go`:
- Around line 2284-2285: The access-profile check is currently in an "else if"
so when team_id/customer_id filters are applied the access_profile_id condition
is dropped; change the logic so access_profile_id is included in the same
multi-filter OR group instead of being mutually exclusive: remove the "else if
params.AccessProfileID" exclusivity and, when building the Where group that
contains team_id and customer_id, add an "OR access_profile_id = ?" clause when
params.AccessProfileID > 0 (using the same baseQuery.Where call), referencing
params.AccessProfileID and the existing baseQuery.Where that uses
team_id/customer_id so the access-profile filter participates in the OR logic
rather than being ignored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f21dbca-843d-4fe5-9123-29e0c5b9e209

📥 Commits

Reviewing files that changed from the base of the PR and between 2527351 and 243adc1.

📒 Files selected for processing (13)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/virtualkey.go
  • transports/bifrost-http/handlers/governance.go
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/ui/combobox.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/store/apis/governanceApi.ts
  • ui/lib/types/governance.ts
✅ Files skipped from review due to trivial changes (2)
  • ui/lib/store/apis/governanceApi.ts
  • ui/lib/store/apis/baseApi.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • ui/lib/types/governance.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • ui/components/ui/combobox.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • transports/bifrost-http/handlers/governance.go
  • framework/configstore/migrations.go
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • framework/configstore/tables/virtualkey.go
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx

Comment thread framework/configstore/rdb.go
Comment thread ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx Outdated
@BearTS
BearTS force-pushed the 05-18-feat_assign_vk_to_access_profile branch from 243adc1 to 8fe228b Compare May 18, 2026 12:51
@coderabbitai
coderabbitai Bot requested a review from danpiths May 18, 2026 12:53
Comment thread transports/bifrost-http/handlers/governance.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
framework/configstore/tables/virtualkey.go (1)

208-212: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject access_profile_id = 0 in the model hook.

BeforeSave currently treats AccessProfileID == 0 as “not counted” but still valid, so non-handler write paths can persist an impossible attachment state. Reject 0 explicitly instead of silently ignoring it.

Proposed fix
-	if vk.AccessProfileID != nil && *vk.AccessProfileID > 0 {
-		entityCount++
-	}
+	if vk.AccessProfileID != nil {
+		if *vk.AccessProfileID == 0 {
+			return fmt.Errorf("access profile id must be greater than 0")
+		}
+		entityCount++
+	}

Also applies to: 251-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/configstore/tables/virtualkey.go` around lines 208 - 212, The
BeforeSave hook should explicitly reject an AccessProfileID value of 0 instead
of treating it as "not counted": in the virtualkey model's BeforeSave (and the
equivalent hook around the AccessProfileID checks later in the file) add a
validation that if AccessProfileID != nil and *AccessProfileID == 0, return a
descriptive error (e.g., fmt.Errorf("access_profile_id cannot be 0")), so writes
that try to persist a zero ID are rejected; update both places that currently
ignore zero to perform this explicit nil-and-zero check against AccessProfileID.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@transports/bifrost-http/handlers/governance.go`:
- Around line 94-97: The incoming requests currently allow access_profile_id ==
0 to pass the HTTP boundary, which breaks exclusivity logic and causes silent
filtering or server errors; update the request validation in the governance
handlers to explicitly reject access_profile_id values equal to 0 with a 400
before any business/query logic runs. Locate where the request structs (fields
TeamID, CustomerID, AccessProfileID) are parsed in the handlers for GET/POST/PUT
(e.g., the create/update/list governance handlers in governance.go) and add a
guard that treats AccessProfileID != nil && *AccessProfileID == 0 as invalid,
returning an immediate HTTP 400 with a clear message; apply the same check
wherever access_profile_id is read from input (the spots noted in the comment
ranges) so filters and persistence never receive a zero ID.

---

Duplicate comments:
In `@framework/configstore/tables/virtualkey.go`:
- Around line 208-212: The BeforeSave hook should explicitly reject an
AccessProfileID value of 0 instead of treating it as "not counted": in the
virtualkey model's BeforeSave (and the equivalent hook around the
AccessProfileID checks later in the file) add a validation that if
AccessProfileID != nil and *AccessProfileID == 0, return a descriptive error
(e.g., fmt.Errorf("access_profile_id cannot be 0")), so writes that try to
persist a zero ID are rejected; update both places that currently ignore zero to
perform this explicit nil-and-zero check against AccessProfileID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cf0caf3-025f-4878-a092-8ea3e61333d5

📥 Commits

Reviewing files that changed from the base of the PR and between 243adc1 and 8fe228b.

📒 Files selected for processing (13)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/virtualkey.go
  • transports/bifrost-http/handlers/governance.go
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/ui/combobox.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/store/apis/governanceApi.ts
  • ui/lib/types/governance.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • ui/app/_fallbacks/enterprise/lib/store/apis/accessProfileApi.ts
  • ui/lib/store/apis/governanceApi.ts
  • framework/configstore/migrations.go
  • ui/lib/types/governance.ts
  • ui/app/_fallbacks/enterprise/lib/types/accessProfile.ts
  • framework/configstore/rdb.go
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeySheet.tsx

Comment thread transports/bifrost-http/handlers/governance.go

akshaydeo commented May 19, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • May 19, 7:54 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 19, 7:55 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo merged commit a9d0192 into dev May 19, 2026
16 checks passed
@akshaydeo
akshaydeo deleted the 05-18-feat_assign_vk_to_access_profile branch May 19, 2026 07:55
akshaydeo pushed a commit that referenced this pull request May 20, 2026
…le assignment (#3560)

## Summary

This PR adds support for attaching virtual keys directly to an access profile, enabling enterprise users to associate VKs with access profile templates as an alternative to team or customer assignment. Previously, virtual keys could only be assigned to a team or a customer (mutually exclusive). This change extends that model to include access profiles as a third mutually exclusive assignment target.

## Changes

- Added `access_profile_id` column and index to `governance_virtual_keys` via a new database migration (`migrationAddVKAccessProfileIDColumn`).
- Extended `TableVirtualKey` with an `AccessProfileID *uint` field.
- Updated `CreateVirtualKeyRequest` and `UpdateVirtualKeyRequest` HTTP handler structs to accept `access_profile_id`, and replaced the binary team/customer mutual-exclusion check with a three-way entity count guard.
- In the update path, `access_profile_id` is persisted via a separate `Updates` call because the existing `UpdateVirtualKey` select list excludes it to protect config-sync paths.
- Added `useGetAccessProfilesQuery` stub to the OSS fallback API layer so the UI degrades gracefully when the enterprise backend is absent.
- Added `GetAccessProfilesParams` and `GetAccessProfilesResponse` types to the OSS fallback type definitions.
- Updated `VirtualKeySheet` to support `access_profile` as a form entity type, including a `defaultAccessProfileId` prop, a locked-assignment mode (`isAPLocked`), a reassignment confirmation dialog, and a combobox for selecting an access profile.
- Updated `VirtualKeysTable` to display an "AP: \<name\>" badge for VKs assigned to an access profile and to include access profile names in CSV exports.
- Registered `AccessProfileVirtualKeys` as a cache tag in the base RTK API.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Run the service — the migration will automatically add `access_profile_id` to `governance_virtual_keys`.
2. Create a virtual key via the API with `access_profile_id` set and verify it is persisted.
3. Attempt to create a virtual key with both `team_id` and `access_profile_id` set — expect a 400 error.
4. Update a virtual key to assign it to an access profile and confirm the previous team/customer assignment is cleared.
5. In the UI (enterprise build), open the virtual key sheet and verify the "Assign to Access Profile" option appears when access profiles exist, the combobox populates correctly, and the reassignment warning dialog triggers when changing an already-assigned profile.
6. Verify the virtual keys table shows the "AP: \<name\>" badge and that CSV export includes the access profile name.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

`access_profile_id` follows the same access-control patterns as `team_id` and `customer_id`. No new auth surfaces are introduced. The OSS fallback returns `undefined` data, ensuring no enterprise-only data is exposed in non-enterprise builds.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
@akshaydeo akshaydeo mentioned this pull request May 20, 2026
BearTS added a commit that referenced this pull request May 21, 2026
BearTS added a commit that referenced this pull request May 21, 2026
BearTS added a commit that referenced this pull request May 21, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 21, 2026
18 tasks
akshaydeo pushed a commit that referenced this pull request May 21, 2026
akshaydeo pushed a commit that referenced this pull request May 21, 2026
…ss profile assignment (#3560)" (#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akshaydeo pushed a commit that referenced this pull request May 21, 2026
…ss profile assignment (#3560)" (#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akshaydeo added a commit that referenced this pull request May 21, 2026
* [docs] : google model armor as guardrail provider docs (#3660)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (#3652)

## Summary

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

## Changes

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (#3655)

## Summary

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

## Changes

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves #3607

## Security considerations

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (#3647)

## Summary

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

## Changes

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. Only query parameters already present in the current URL are forwarded.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (#3648)

## Summary

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

## Changes

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

## Type of change

- [x] Refactor

## Affected areas

- [x] UI (React)

## How to test

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues  
#3546

## Security considerations

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (#3650)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (#3662)

## Summary

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

## Changes

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

## Breaking changes

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (#3668)

## Summary

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

## Changes

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

## Breaking changes

- [x] No

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (#3653)

## Summary

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

## Changes

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

## Type of change

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

## Affected areas

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

## How to test

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

## Screenshots/Recordings

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.



https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc




## Breaking changes

* [ ] Yes
* [x] No

## Related issues

BF-896

## Security considerations

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

## Checklist

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (#3665)

## Summary

UI updates

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (#3560)" (#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (#3670)

## Summary

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

## Changes

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (#3671)

## Summary

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

## Changes

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Covers migration test coverage for v1.5.3 schema additions.

## Security considerations

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (#3672)

## Summary

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

## Changes

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (#3674)

## Summary

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

## Changes

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

## Screenshots/Recordings

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (#3675)

## Summary

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

## Changes

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (#3676)

## Summary

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

## Changes

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (#3680)

* fix idle timeout panic (#3677)

## Summary

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

## Changes

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)

## How to test

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (#3678)

## Summary

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

## Changes

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

---------

Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Vaibhav701161 added a commit that referenced this pull request May 26, 2026
* [docs] : google model armor as guardrail provider docs (#3660)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (#3652)

## Summary

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

## Changes

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (#3655)

## Summary

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

## Changes

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves #3607

## Security considerations

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (#3647)

## Summary

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

## Changes

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. Only query parameters already present in the current URL are forwarded.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (#3648)

## Summary

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

## Changes

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

## Type of change

- [x] Refactor

## Affected areas

- [x] UI (React)

## How to test

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues  
#3546

## Security considerations

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (#3650)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (#3662)

## Summary

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

## Changes

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

## Breaking changes

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (#3668)

## Summary

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

## Changes

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

## Breaking changes

- [x] No

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (#3653)

## Summary

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

## Changes

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

## Type of change

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

## Affected areas

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

## How to test

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

## Screenshots/Recordings

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.



https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc




## Breaking changes

* [ ] Yes
* [x] No

## Related issues

BF-896

## Security considerations

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

## Checklist

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (#3665)

## Summary

UI updates

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (#3560)" (#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (#3670)

## Summary

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

## Changes

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (#3671)

## Summary

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

## Changes

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Covers migration test coverage for v1.5.3 schema additions.

## Security considerations

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (#3672)

## Summary

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

## Changes

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (#3674)

## Summary

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

## Changes

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

## Screenshots/Recordings

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (#3675)

## Summary

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

## Changes

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (#3676)

## Summary

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

## Changes

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (#3680)

* fix idle timeout panic (#3677)

## Summary

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

## Changes

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)

## How to test

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (#3678)

## Summary

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

## Changes

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

---------

Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
akshaydeo added a commit that referenced this pull request Jun 3, 2026
* [docs] : google model armor as guardrail provider docs (#3660)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (#3652)

## Summary

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

## Changes

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (#3655)

## Summary

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

## Changes

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves #3607

## Security considerations

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (#3647)

## Summary

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

## Changes

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. Only query parameters already present in the current URL are forwarded.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (#3648)

## Summary

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

## Changes

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

## Type of change

- [x] Refactor

## Affected areas

- [x] UI (React)

## How to test

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues  
#3546

## Security considerations

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (#3650)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (#3662)

## Summary

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

## Changes

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

## Breaking changes

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (#3668)

## Summary

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

## Changes

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

## Breaking changes

- [x] No

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (#3653)

## Summary

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

## Changes

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

## Type of change

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

## Affected areas

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

## How to test

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

## Screenshots/Recordings

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.



https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc




## Breaking changes

* [ ] Yes
* [x] No

## Related issues

BF-896

## Security considerations

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

## Checklist

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (#3665)

## Summary

UI updates

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (#3560)" (#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes #123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (#3670)

## Summary

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

## Changes

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (#3671)

## Summary

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

## Changes

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Covers migration test coverage for v1.5.3 schema additions.

## Security considerations

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (#3672)

## Summary

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

## Changes

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (#3674)

## Summary

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

## Changes

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

## Screenshots/Recordings

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (#3675)

## Summary

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

## Changes

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (#3676)

## Summary

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

## Changes

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (#3680)

* fix idle timeout panic (#3677)

## Summary

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

## Changes

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)

## How to test

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (#3678)

## Summary

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

## Changes

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: update e2e ui tests (#3687)

## Summary

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

## Changes

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

None.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* fix: updates mcp oauth api tests (#3693)

## Summary

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

## Changes

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. These are test coverage probes only.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
akshaydeo added a commit that referenced this pull request Jun 4, 2026
* [docs] : google model armor as guardrail provider docs (#3660)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (#3652)

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (#3655)

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

- [ ] Yes
- [x] No

- Resolves #3607

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (#3647)

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

N/A

- [ ] Yes
- [x] No

No security implications. Only query parameters already present in the current URL are forwarded.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (#3648)

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

- [x] Refactor

- [x] UI (React)

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

- [ ] Yes
- [x] No

#3546

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (#3650)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (#3662)

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (#3668)

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

- [x] Bug fix

- [x] Core (Go)
- [x] Providers/Integrations

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (#3653)

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.

https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc

* [ ] Yes
* [x] No

BF-896

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (#3665)

UI updates

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (#3560)" (#3669)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (#3670)

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

N/A

- [ ] Yes
- [x] No

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (#3671)

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

- [ ] Yes
- [x] No

Covers migration test coverage for v1.5.3 schema additions.

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (#3672)

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

N/A

- [ ] Yes
- [x] No

N/A

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (#3674)

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (#3675)

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (#3676)

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (#3680)

* fix idle timeout panic (#3677)

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

- [ ] Yes
- [x] No

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (#3678)

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

N/A

- [ ] Yes
- [x] No

N/A

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: update e2e ui tests (#3687)

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

N/A

- [ ] Yes
- [x] No

N/A

None.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* fix: updates mcp oauth api tests (#3693)

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

- [ ] Yes
- [x] No

No security implications. These are test coverage probes only.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
akshaydeo added a commit that referenced this pull request Jun 7, 2026
* [docs] : google model armor as guardrail provider docs (#3660)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (#3652)

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (#3655)

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

- [ ] Yes
- [x] No

- Resolves #3607

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (#3647)

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

N/A

- [ ] Yes
- [x] No

No security implications. Only query parameters already present in the current URL are forwarded.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (#3648)

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

- [x] Refactor

- [x] UI (React)

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

- [ ] Yes
- [x] No

#3546

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (#3650)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (#3662)

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (#3668)

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

- [x] Bug fix

- [x] Core (Go)
- [x] Providers/Integrations

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (#3653)

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.

https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc

* [ ] Yes
* [x] No

BF-896

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (#3665)

UI updates

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (#3560)" (#3669)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes #123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (#3670)

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

N/A

- [ ] Yes
- [x] No

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (#3671)

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

- [ ] Yes
- [x] No

Covers migration test coverage for v1.5.3 schema additions.

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (#3672)

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

N/A

- [ ] Yes
- [x] No

N/A

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (#3674)

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (#3675)

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (#3676)

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (#3680)

* fix idle timeout panic (#3677)

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

- [ ] Yes
- [x] No

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (#3678)

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

N/A

- [ ] Yes
- [x] No

N/A

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: update e2e ui tests (#3687)

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

N/A

- [ ] Yes
- [x] No

N/A

None.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* fix: updates mcp oauth api tests (#3693)

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

- [ ] Yes
- [x] No

No security implications. These are test coverage probes only.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
…le assignment (maximhq#3560)

## Summary

This PR adds support for attaching virtual keys directly to an access profile, enabling enterprise users to associate VKs with access profile templates as an alternative to team or customer assignment. Previously, virtual keys could only be assigned to a team or a customer (mutually exclusive). This change extends that model to include access profiles as a third mutually exclusive assignment target.

## Changes

- Added `access_profile_id` column and index to `governance_virtual_keys` via a new database migration (`migrationAddVKAccessProfileIDColumn`).
- Extended `TableVirtualKey` with an `AccessProfileID *uint` field.
- Updated `CreateVirtualKeyRequest` and `UpdateVirtualKeyRequest` HTTP handler structs to accept `access_profile_id`, and replaced the binary team/customer mutual-exclusion check with a three-way entity count guard.
- In the update path, `access_profile_id` is persisted via a separate `Updates` call because the existing `UpdateVirtualKey` select list excludes it to protect config-sync paths.
- Added `useGetAccessProfilesQuery` stub to the OSS fallback API layer so the UI degrades gracefully when the enterprise backend is absent.
- Added `GetAccessProfilesParams` and `GetAccessProfilesResponse` types to the OSS fallback type definitions.
- Updated `VirtualKeySheet` to support `access_profile` as a form entity type, including a `defaultAccessProfileId` prop, a locked-assignment mode (`isAPLocked`), a reassignment confirmation dialog, and a combobox for selecting an access profile.
- Updated `VirtualKeysTable` to display an "AP: \<name\>" badge for VKs assigned to an access profile and to include access profile names in CSV exports.
- Registered `AccessProfileVirtualKeys` as a cache tag in the base RTK API.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Run the service — the migration will automatically add `access_profile_id` to `governance_virtual_keys`.
2. Create a virtual key via the API with `access_profile_id` set and verify it is persisted.
3. Attempt to create a virtual key with both `team_id` and `access_profile_id` set — expect a 400 error.
4. Update a virtual key to assign it to an access profile and confirm the previous team/customer assignment is cleared.
5. In the UI (enterprise build), open the virtual key sheet and verify the "Assign to Access Profile" option appears when access profiles exist, the combobox populates correctly, and the reassignment warning dialog triggers when changing an already-assigned profile.
6. Verify the virtual keys table shows the "AP: \<name\>" badge and that CSV export includes the access profile name.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

`access_profile_id` follows the same access-control patterns as `team_id` and `customer_id`. No new auth surfaces are introduced. The OSS fallback returns `undefined` data, ensuring no enterprise-only data is exposed in non-enterprise builds.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
* [docs] : google model armor as guardrail provider docs (maximhq#3660)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (maximhq#3652)

## Summary

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

## Changes

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (maximhq#3655)

## Summary

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

## Changes

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves maximhq#3607

## Security considerations

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (maximhq#3647)

## Summary

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

## Changes

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. Only query parameters already present in the current URL are forwarded.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (maximhq#3648)

## Summary

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

## Changes

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

## Type of change

- [x] Refactor

## Affected areas

- [x] UI (React)

## How to test

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues  
maximhq#3546

## Security considerations

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (maximhq#3650)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (maximhq#3662)

## Summary

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

## Changes

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

## Breaking changes

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (maximhq#3668)

## Summary

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

## Changes

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

## Breaking changes

- [x] No

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (maximhq#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (maximhq#3653)

## Summary

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

## Changes

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

## Type of change

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

## Affected areas

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

## How to test

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

## Screenshots/Recordings

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.



https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc




## Breaking changes

* [ ] Yes
* [x] No

## Related issues

BF-896

## Security considerations

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

## Checklist

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (maximhq#3665)

## Summary

UI updates

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (maximhq#3560)" (maximhq#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (maximhq#3670)

## Summary

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

## Changes

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (maximhq#3671)

## Summary

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

## Changes

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Covers migration test coverage for v1.5.3 schema additions.

## Security considerations

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (maximhq#3672)

## Summary

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

## Changes

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (maximhq#3674)

## Summary

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

## Changes

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

## Screenshots/Recordings

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (maximhq#3675)

## Summary

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

## Changes

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (maximhq#3676)

## Summary

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

## Changes

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (maximhq#3680)

* fix idle timeout panic (maximhq#3677)

## Summary

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

## Changes

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)

## How to test

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (maximhq#3678)

## Summary

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

## Changes

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

---------

Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
* [docs] : google model armor as guardrail provider docs (maximhq#3660)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (maximhq#3652)

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (maximhq#3655)

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

- [ ] Yes
- [x] No

- Resolves maximhq#3607

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (maximhq#3647)

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

N/A

- [ ] Yes
- [x] No

No security implications. Only query parameters already present in the current URL are forwarded.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (maximhq#3648)

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

- [x] Refactor

- [x] UI (React)

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

- [ ] Yes
- [x] No

maximhq#3546

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (maximhq#3650)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (maximhq#3662)

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (maximhq#3668)

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

- [x] Bug fix

- [x] Core (Go)
- [x] Providers/Integrations

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (maximhq#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (maximhq#3653)

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.

https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc

* [ ] Yes
* [x] No

BF-896

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (maximhq#3665)

UI updates

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (maximhq#3560)" (maximhq#3669)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (maximhq#3670)

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

N/A

- [ ] Yes
- [x] No

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (maximhq#3671)

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

- [ ] Yes
- [x] No

Covers migration test coverage for v1.5.3 schema additions.

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (maximhq#3672)

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

N/A

- [ ] Yes
- [x] No

N/A

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (maximhq#3674)

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (maximhq#3675)

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (maximhq#3676)

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (maximhq#3680)

* fix idle timeout panic (maximhq#3677)

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

- [ ] Yes
- [x] No

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (maximhq#3678)

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

N/A

- [ ] Yes
- [x] No

N/A

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: update e2e ui tests (maximhq#3687)

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

N/A

- [ ] Yes
- [x] No

N/A

None.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* fix: updates mcp oauth api tests (maximhq#3693)

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

- [ ] Yes
- [x] No

No security implications. These are test coverage probes only.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
…le assignment (maximhq#3560)

## Summary

This PR adds support for attaching virtual keys directly to an access profile, enabling enterprise users to associate VKs with access profile templates as an alternative to team or customer assignment. Previously, virtual keys could only be assigned to a team or a customer (mutually exclusive). This change extends that model to include access profiles as a third mutually exclusive assignment target.

## Changes

- Added `access_profile_id` column and index to `governance_virtual_keys` via a new database migration (`migrationAddVKAccessProfileIDColumn`).
- Extended `TableVirtualKey` with an `AccessProfileID *uint` field.
- Updated `CreateVirtualKeyRequest` and `UpdateVirtualKeyRequest` HTTP handler structs to accept `access_profile_id`, and replaced the binary team/customer mutual-exclusion check with a three-way entity count guard.
- In the update path, `access_profile_id` is persisted via a separate `Updates` call because the existing `UpdateVirtualKey` select list excludes it to protect config-sync paths.
- Added `useGetAccessProfilesQuery` stub to the OSS fallback API layer so the UI degrades gracefully when the enterprise backend is absent.
- Added `GetAccessProfilesParams` and `GetAccessProfilesResponse` types to the OSS fallback type definitions.
- Updated `VirtualKeySheet` to support `access_profile` as a form entity type, including a `defaultAccessProfileId` prop, a locked-assignment mode (`isAPLocked`), a reassignment confirmation dialog, and a combobox for selecting an access profile.
- Updated `VirtualKeysTable` to display an "AP: \<name\>" badge for VKs assigned to an access profile and to include access profile names in CSV exports.
- Registered `AccessProfileVirtualKeys` as a cache tag in the base RTK API.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Run the service — the migration will automatically add `access_profile_id` to `governance_virtual_keys`.
2. Create a virtual key via the API with `access_profile_id` set and verify it is persisted.
3. Attempt to create a virtual key with both `team_id` and `access_profile_id` set — expect a 400 error.
4. Update a virtual key to assign it to an access profile and confirm the previous team/customer assignment is cleared.
5. In the UI (enterprise build), open the virtual key sheet and verify the "Assign to Access Profile" option appears when access profiles exist, the combobox populates correctly, and the reassignment warning dialog triggers when changing an already-assigned profile.
6. Verify the virtual keys table shows the "AP: \<name\>" badge and that CSV export includes the access profile name.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

`access_profile_id` follows the same access-control patterns as `team_id` and `customer_id`. No new auth surfaces are introduced. The OSS fallback returns `undefined` data, ensuring no enterprise-only data is exposed in non-enterprise builds.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
* [docs] : google model armor as guardrail provider docs (maximhq#3660)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (maximhq#3652)

## Summary

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

## Changes

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (maximhq#3655)

## Summary

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

## Changes

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

- Resolves maximhq#3607

## Security considerations

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (maximhq#3647)

## Summary

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

## Changes

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

No security implications. Only query parameters already present in the current URL are forwarded.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (maximhq#3648)

## Summary

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

## Changes

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

## Type of change

- [x] Refactor

## Affected areas

- [x] UI (React)

## How to test

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues  
maximhq#3546

## Security considerations

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (maximhq#3650)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (maximhq#3662)

## Summary

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

## Changes

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

## Breaking changes

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (maximhq#3668)

## Summary

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

## Changes

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

## Breaking changes

- [x] No

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (maximhq#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (maximhq#3653)

## Summary

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

## Changes

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

## Type of change

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

## Affected areas

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

## How to test

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

## Screenshots/Recordings

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.



https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc




## Breaking changes

* [ ] Yes
* [x] No

## Related issues

BF-896

## Security considerations

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

## Checklist

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (maximhq#3665)

## Summary

UI updates

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (maximhq#3560)" (maximhq#3669)

## Summary

Briefly explain the purpose of this PR and the problem it solves.

## Changes

- What was changed and why
- Any notable design decisions or trade-offs

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

## Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

## Breaking changes

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

## Related issues

Link related issues and discussions. Example: Closes maximhq#123

## Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (maximhq#3670)

## Summary

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

## Changes

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (maximhq#3671)

## Summary

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

## Changes

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

## Type of change

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

Covers migration test coverage for v1.5.3 schema additions.

## Security considerations

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (maximhq#3672)

## Summary

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

## Changes

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (maximhq#3674)

## Summary

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

## Changes

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

## Screenshots/Recordings

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (maximhq#3675)

## Summary

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

## Changes

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (maximhq#3676)

## Summary

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

## Changes

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

## Screenshots/Recordings

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

## Security considerations

None.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (maximhq#3680)

* fix idle timeout panic (maximhq#3677)

## Summary

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

## Changes

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

## Type of change

- [x] Bug fix

## Affected areas

- [x] Core (Go)

## How to test

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

## Breaking changes

- [ ] Yes
- [x] No

## Security considerations

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (maximhq#3678)

## Summary

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

## Changes

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

---------

Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
occcat pushed a commit to occcat/bifrost that referenced this pull request Sep 2, 2026
* [docs] : google model armor as guardrail provider docs (maximhq#3660)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: removes manual setting of type custom for anthropic tools (maximhq#3652)

Removes the hardcoded `type: "custom"` field that was being set by default for Anthropic tools during conversion. It is an optional field based on Anthropic docs and with this, can also support Deepseek as custom provider

- Removed the automatic assignment of `AnthropicToolTypeCustom` when initializing `AnthropicTool` in `convertBifrostToolToAnthropic`, allowing the tool type to be determined by subsequent logic rather than being overridden at construction time.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Send a request to the Anthropic provider with tools that are not of the custom type and verify they are correctly passed through without being overridden to `type: "custom"`.

```sh
go test ./core/providers/anthropic/...
```

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fix ListModels for keyless providers (maximhq#3655)

When performing keyless `ListModels` requests, provider implementations were passing an empty `schemas.Key{}`, which caused model filtering to behave incorrectly — returning no models instead of all available models. This fix ensures keyless requests use a wildcard whitelist so all models are returned as expected.

- Replaced `schemas.Key{}` with `schemas.Key{Models: schemas.WhiteList{"*"}}` in the keyless `ListModels` path for Anthropic, Cohere, Gemini, HuggingFace, and OpenAI providers.
- The wildcard `"*"` entry signals that all models should be allowed through the whitelist filter, matching the intended behavior for keyless configurations.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Call `ListModels` against a provider configured with `IsKeyLess: true` and verify that the response includes the full list of available models rather than an empty result.

```sh
go test ./...
```

- [ ] Yes
- [x] No

- Resolves maximhq#3607

No security implications. The wildcard whitelist only affects model listing behavior in explicitly keyless provider configurations.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: preserve time filter params when navigating between sidebar items (maximhq#3647)

When navigating between sidebar pages that support time filtering, the selected time range (start/end time or period) is lost. This PR preserves the active time filter parameters when clicking sidebar sub-items, so users don't have to re-select their time range after switching between time-filter-enabled pages.

- When navigating from one `TimeFilterPages` page to another via a sidebar sub-item, the current `start_time`, `end_time`, and `period` query parameters are carried over to the destination URL.
- If the current or destination page is not in `TimeFilterPages`, navigation behaves as before with no parameter forwarding.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to a page that supports time filtering (e.g., a metrics or logs page).
2. Set a custom time range or period using the time filter.
3. Click a different sidebar sub-item that also supports time filtering.
4. Verify the time range is preserved in the URL and the view reflects the same time window.
5. Navigate to a sidebar sub-item that does **not** support time filtering and verify no time parameters are appended.

```sh
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

N/A

- [ ] Yes
- [x] No

No security implications. Only query parameters already present in the current URL are forwarded.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* refactor: move auth check from login component to route loader with redirect (maximhq#3648)

Auth status checking on the login route was previously handled inside the `LoginView` component using a Redux query, causing a flash of "Checking authentication..." UI within the already-rendered login page. This moves the auth check to a TanStack Router route loader so the redirect to `/workspace` happens before the login page renders, and the pending state is handled cleanly by a dedicated `PendingComponent`.

- Moved the `is-auth-enabled` auth check from `LoginView` into the `/login` route loader. If auth is disabled or the user already has a valid token, the loader throws a redirect to `/workspace` before the component mounts.
- Removed `useIsAuthEnabledQuery`, the `isCheckingAuth` state, and the inline loading UI from `LoginView`, simplifying the component significantly.
- Added a `PendingComponent` to the `/login` route that displays the "Checking authentication..." screen while the loader is in flight, with `pendingMs: 0` to show it immediately.
- Added `providesTags: ["Sessions"]` to `useIsAuthEnabledQuery` and updated `login` to `invalidatesTags: ["Sessions"]` so session state is properly invalidated after login. Also added `"Sessions"` to the logout invalidation list.

- [x] Refactor

- [x] UI (React)

1. Navigate to `/login` while unauthenticated — the "Checking authentication..." pending screen should appear briefly, then the login form should render.
2. Navigate to `/login` while already authenticated (valid session cookie) — you should be immediately redirected to `/workspace` without seeing the login form.
3. Navigate to `/login` when auth is disabled — you should be redirected to `/workspace`.
4. Submit valid credentials on the login form — you should be redirected to `/workspace`.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

- [ ] Yes
- [x] No

maximhq#3546

The auth check now uses `credentials: "include"` in a plain `fetch` call within the route loader, ensuring the session cookie is sent. If the fetch fails, the login page is shown as a safe fallback rather than silently redirecting.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* feat: add team and bu filter support for dashboard and logs (maximhq#3650)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: map vertex trrafic type to bifrost service tier (maximhq#3662)

Adds support for Vertex AI's `trafficType` field in `usageMetadata` responses, mapping it to the Bifrost `ServiceTier` abstraction. Previously, only the `serviceTier` field was used, which is specific to the Gemini API. Vertex AI uses a separate `trafficType` field to indicate quota consumption (e.g., on-demand, provisioned throughput). This PR introduces proper handling for both fields and adds a new `provisioned` service tier value.

- Introduced a typed `TrafficType` constant set covering `ON_DEMAND`, `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, and `PROVISIONED_THROUGHPUT` values, replacing the previous untyped `string` field on `GenerateContentResponseUsageMetadata`.
- Added `mapGeminiTrafficTypeToBifrost` to convert Vertex AI `trafficType` values to `BifrostServiceTier`, returning `nil` for unrecognised or empty values.
- Added `mapBifrostServiceTierToVertexTrafficType` to convert `BifrostServiceTier` back to a Vertex AI `TrafficType` for round-trip serialisation.
- Updated `ServiceTier` resolution in chat, responses, and streaming paths to prefer `trafficType` over `serviceTier`, falling back to `serviceTier` when `trafficType` is absent or unrecognised.
- When serialising back to Gemini/Vertex format, the provider is now checked: Vertex responses use `trafficType`, while Gemini responses continue to use `serviceTier`.
- Added `BifrostServiceTierProvisioned` as a new canonical service tier value.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

- Send a request through the Vertex AI provider and verify that the returned `ServiceTier` reflects the `trafficType` in the response metadata (e.g., `ON_DEMAND` → `default`, `PROVISIONED_THROUGHPUT` → `provisioned`).
- Send a request through the standard Gemini provider and verify that `serviceTier` is still used when `trafficType` is absent.
- Verify streaming responses also populate `ServiceTier` correctly on the final chunk.

- [x] Yes
- [ ] No

`BifrostServiceTierProvisioned` is a new enum value. Consumers performing exhaustive switches over `BifrostServiceTier` should add a case for `"provisioned"`.

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: fixes the start time setting for correct ttft metric value (maximhq#3668)

`startTime` was being captured inside the goroutine that processes stream chunks, meaning it reflected the time after the HTTP connection was established rather than when the request was actually initiated. This caused time-to-first-chunk and inter-chunk latency metrics to be measured from an incorrect baseline.

- Moved `startTime := time.Now()` to just before the `client.Do(req, resp)` call in every streaming handler across all providers (Anthropic, Azure, Bedrock, Cohere, Gemini, HuggingFace, Mistral, OpenAI, Replicate, vLLM).
- `lastChunkTime` is initialized from `startTime` as before, so relative chunk-to-chunk latency calculations are unaffected — only the absolute start anchor is now correct.

- [x] Bug fix

- [x] Core (Go)
- [x] Providers/Integrations

Run the full test suite and exercise any streaming endpoint. Verify that reported time-to-first-chunk values now include network round-trip time to the upstream provider rather than starting the clock only after the connection is fully established.

```sh
go test ./...
```

- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: preserve tool call stop reason in Anthropic streaming fallback (maximhq#3640)

* Fix Anthropic tool-use stop reason in chat fallback streams

* test: assert Anthropic message stop after tool use

* feat(governance): add virtual key blocked models (maximhq#3653)

Adds blocked model support for virtual key provider configurations.

Provider keys already supported both allowed and blocked models, but virtual key provider configs only supported allowed models. This PR adds the missing blocked model flow for virtual keys, so specific models can be denied at the VK provider-config level.

* Added `blacklisted_models` to virtual key provider configs.
* Added a configstore migration for the new `blacklisted_models` column.
* Updated virtual key create/update handlers to validate, persist, and return blocked models.
* Added governance checks to reject virtual key requests when the requested model is blocked.
* Made blocked models take priority over allowed models.
* Updated virtual key model filtering to respect blocked models.
* Added `Blocked Models` UI under provider configurations in the virtual key create/edit sheet.
* Added blocked model display in the virtual key details sheet.
* Updated frontend governance types for virtual key provider configs.

This follows the existing provider-key blocked model behavior instead of introducing a separate flow.

Behavior:

* Empty `blacklisted_models` means no models are blocked.
* `["*"]` blocks all models for that VK provider config.
* If the same model exists in both allowed and blocked models, the blocked list wins.

* [ ] Bug fix
* [x] Feature
* [ ] Refactor
* [ ] Documentation
* [ ] Chore/CI

* [x] Core (Go)
* [x] Transports (HTTP)
* [ ] Providers/Integrations
* [x] Plugins
* [x] UI (React)
* [ ] Docs

Local UI flow:

Start Bifrost locally with embedded UI on port `9090`, then open:

`http://localhost:9090/workspace/governance/virtual-keys`

Steps tested:

1. Open Virtual Keys.
2. Create or edit a virtual key.
3. Expand a provider config.
4. Confirm `Blocked Models` appears below `Allowed Models`.
5. Select a blocked model and save.
6. Reopen the virtual key and confirm the blocked model is still shown.
7. Refresh the page and confirm the value persists.

Runtime validation:

1. Configure a VK provider config with `allowed_models: ["*"]` and `blacklisted_models: ["<model-to-block>"]`.
2. Send a request through that virtual key using the blocked model.

Expected: request is rejected.

3. Send a request through the same virtual key using a model not present in `blacklisted_models`.

Expected: request succeeds.

4. Configure the same model in both `allowed_models` and `blacklisted_models`.

Expected: request is rejected because blocked models take priority.

5. Configure `blacklisted_models: []`.

Expected: existing virtual key behavior remains unchanged.

Sanity checks:

`go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/handlers/...`

UI check:

`cd ui && pnpm build`

Added a recording showing the new `Blocked Models` field in the virtual key provider configuration flow.

https://github.com/user-attachments/assets/64efca01-0366-491c-b9e7-95dfa08eb0bc

* [ ] Yes
* [x] No

BF-896

This change improves virtual-key governance by allowing specific models to be denied for a VK provider config.

No provider secrets, customer keys, auth tokens, or PII are exposed or stored by this change. Existing provider-key behavior is unchanged.

* [x] I read `docs/contributing/README.md` and followed the guidelines
* [ ] I added/updated tests where appropriate
* [ ] I updated documentation where needed
* [x] I verified builds succeed (Go and UI)
* [x] I verified the CI pipeline passes locally if applicable.

* table updates (maximhq#3665)

UI updates

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* Revert "feat: add `access_profile_id` to virtual keys for direct access profile assignment (maximhq#3560)" (maximhq#3669)

Briefly explain the purpose of this PR and the problem it solves.

- What was changed and why
- Any notable design decisions or trade-offs

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Describe the steps to validate this change. Include commands and expected outcomes.

```sh
go version
go test ./...

cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build
```

If adding new configs or environment variables, document them here.

If UI changes, add before/after screenshots or short clips.

- [ ] Yes
- [ ] No

If yes, describe impact and migration instructions.

Link related issues and discussions. Example: Closes maximhq#123

Note any security implications (auth, secrets, PII, sandboxing, etc.).

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* chore: drop `access_profile_id` column from `governance_virtual_keys` (maximhq#3670)

This PR removes the `access_profile_id` column and its associated index from the `governance_virtual_keys` table, reverting the previously applied `migrationAddVKAccessProfileIDColumn` migration.

- Added a new migration `migrationDropVKAccessProfileIDColumn` that drops the `idx_governance_virtual_keys_access_profile_id` index and the `access_profile_id` column from `governance_virtual_keys`, if they exist.
- Registered the new migration in `triggerMigrations` immediately after the migration that originally added the column, ensuring correct ordering.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./...
```

Verify that after running migrations, the `governance_virtual_keys` table no longer contains the `access_profile_id` column or its index. Confirm that the migration runs cleanly on both fresh and existing databases where the column may or may not already be present.

N/A

- [ ] Yes
- [x] No

None. This change removes an unused column and index with no impact on authentication, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* test: add v1.5.3 migration test coverage for feature flags, temp tokens, OAuth schema refactor, and new columns (maximhq#3671)

Extends the migration test script to cover v1.5.3 schema changes, including new tables (`feature_flags`, `temp_tokens`) and new columns added across config store and log store tables. Also refactors the per-user OAuth insert generation to handle both the legacy v1.5.0-prerelease4 schema and the v1.5.3 refactored schema, where `oauth_per_user_*` tables were dropped and `oauth_user_sessions`/`oauth_user_tokens` were restructured.

- Added `generate_feature_flags_insert_postgres` and `generate_feature_flags_insert_sqlite` functions to seed the `feature_flags` table introduced in v1.5.3 via `migrationAddFeatureFlagsTable`.
- Added `generate_temp_tokens_insert_postgres` and `generate_temp_tokens_insert_sqlite` functions to seed the `temp_tokens` table introduced in v1.5.3 via `migrationAddTempTokensTable`.
- Both new insert generators are wired into `append_dynamic_mcp_clients_insert` for both PostgreSQL and SQLite paths.
- Added v1.5.3 dynamic column UPDATE blocks for both PostgreSQL and SQLite covering:
  - `config_client.metadata_json`
  - `framework_configs.model_parameters_url` and `config_hash`
  - `governance_teams.source_id` and `calendar_aligned`
  - `governance_virtual_keys.access_profile_id`
  - `logs.cluster_node_id`, `budget_ids`, and `rate_limit_ids`
  - `mcp_tool_logs.user_id`, `team_id`, `customer_id`, and `business_unit_id`
- Refactored `generate_per_user_oauth_tables_insert_postgres` and extracted a new `generate_per_user_oauth_tables_insert_sqlite` function. Both now branch on schema version: if `oauth_per_user_clients` exists, the prerelease4 schema is used; if `oauth_user_sessions.session_id` exists, the v1.5.3 refactored schema (using `session_id` + `flow_mode` instead of `session_token`/`gateway_session_id`) is used instead.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the migration test workflow against a deployment that includes v1.5.3 migrations and verify that the test script seeds all new tables and columns without errors. Also run against a pre-v1.5.3 schema to confirm the conditional guards correctly skip missing tables and columns.

```sh
bash .github/workflows/scripts/run-migration-tests.sh
```

- [ ] Yes
- [x] No

Covers migration test coverage for v1.5.3 schema additions.

No new secrets or auth flows are introduced. Test data uses placeholder tokens and hashes that are not used in production.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* adds created_by for virtual keys (maximhq#3672)

This PR addresses two independent improvements: preventing reads on already-closed streaming connections, and tracking which user created a virtual key.

- Added a `ctx` field to `idleTimeoutReader` so that it can check the `BifrostContextKeyConnectionClosed` flag before attempting a `Read()`. If the connection is already marked as closed, the read returns immediately with `(0, nil)` instead of blocking or erroring.
- Added a `CreatedBy` field (`*string`) to `TableVirtualKey` with a database index (`idx_virtual_key_created_by`) to record the creator of each virtual key.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go version
go test ./...
```

- To validate the idle timeout reader fix: establish a streaming connection, close it, and confirm no further reads are attempted on the closed stream.
- To validate the `CreatedBy` field: create a virtual key and confirm the `created_by` column is populated and indexed in the database.

N/A

- [ ] Yes
- [x] No

N/A

The `CreatedBy` field stores a user identifier on virtual keys. Ensure that this value is not populated with sensitive PII beyond what is already stored in the system, and that access controls on virtual key records remain enforced.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: ensure toasts remain clickable above modal overlays (maximhq#3674)

Fixes an issue where toast notifications were unclickable when a modal was open. Radix UI's `react-remove-scroll` sets `pointer-events: none` on elements outside the modal, which inadvertently blocked interaction with Sonner toasts.

- Added a CSS rule to force `pointer-events: auto` on `[data-sonner-toaster]`, ensuring toasts remain interactive even when a modal overlay is active.

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open any modal dialog in the UI.
2. Trigger a toast notification while the modal is open.
3. Verify the toast is visible and can be clicked/dismissed without closing the modal first.

Before: Toasts displayed behind/blocked by modal overlay and could not be clicked.
After: Toasts remain fully interactive while a modal is open.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: remove save/cancel icons and fix sheet layout growth in routing rule and virtual key sheets (maximhq#3675)

Cleans up the routing rule sheet UI by removing icon decorations from action buttons and fixing layout issues where the form content doesn't grow to fill available space in both the routing rule and virtual key sheets.

- Removed the `X` and `Save` icons from the Cancel and Save/Update buttons in the routing rule sheet, leaving text-only labels
- Added `grow` and `flex flex-col` classes to the routing rule sheet form and its inner container so the form expands to fill the sheet height correctly
- Added `grow` to the virtual key sheet's inner content div for consistent layout behavior
- Moved the `RbacOperation`, `RbacResource`, and `useRbac` import to be grouped with other non-local imports

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Open the routing rules sheet (create or edit a rule) and verify the form content fills the full height of the sheet without collapsing.
2. Confirm the Cancel and Save/Update buttons display text only, without icons.
3. Open the virtual key sheet and verify the form content similarly fills the available height.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the button label changes and corrected sheet layout are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: make virtual keys table fill available height with sticky header and scrollable body (maximhq#3676)

Fixes the Virtual Keys table layout so it fills the available viewport height and scrolls internally, rather than causing the entire page to scroll. The table header remains sticky at the top while the body scrolls, and the pinned action column z-indices are corrected to prevent overlap issues.

- Converted the outer container to a flex column layout with `grow` and `overflow-hidden` so the table section expands to fill remaining space without overflowing the page.
- Added `shrink-0` to the header/toolbar rows so they don't compress when space is constrained.
- Made the table container use `min-h-0 grow overflow-hidden` and passed `containerClassName="h-full overflow-auto"` so scrolling is scoped to the table body.
- Made `TableHeader` sticky (`sticky top-0 z-20`) with a background so column headers remain visible during vertical scroll.
- Adjusted z-index on the pinned right-side `TableHead` to `z-30` (above the sticky header row) and the pinned `TableCell` to `z-20` to maintain correct stacking order.
- Reduced pagination text to `text-xs` for visual consistency.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

1. Navigate to the Virtual Keys page with enough keys to require scrolling.
2. Verify the page itself does not scroll — only the table body scrolls.
3. Verify the column headers remain visible (sticky) as you scroll down.
4. Verify the pinned actions column on the right does not disappear behind the sticky header.
5. Verify row hover states on the pinned actions cell render correctly.

```sh
cd ui
pnpm i || npm i
pnpm build || npm run build
```

Before/after screenshots showing the table scrolling within its container rather than the full page scrolling are recommended.

- [ ] Yes
- [x] No

None.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [ ] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* v1.5.4 cut (maximhq#3680)

* fix idle timeout panic (maximhq#3677)

`idleTimeoutReader` had several correctness issues: the cleanup function could return before an in-flight timer callback finished closing the body stream, panics from the underlying reader during a timeout-triggered close were unhandled, a nil context caused a panic, and a closed connection returned `nil` instead of a meaningful error.

- Added a `timerDone` channel and `timerDoneOnce`/`cleanupOnce` guards so that `cleanup()` blocks until any concurrently running timer callback has fully completed, preventing races between cleanup and the idle timeout close path.
- Added a `recover()` deferred in `Read()` that catches panics from the underlying reader (e.g. reads on a closed pipe after timeout) and converts them into `ErrStreamIdleTimeout` or `ErrStreamClosed` rather than crashing.
- Extracted `connectionClosed()` and `closedReadError()` helpers to centralise nil-context safety and consistent error selection logic.
- Changed the connection-closed early-return in `Read()` to return `ErrStreamClosed` instead of `(0, nil)`, giving callers a clear signal.
- Introduced `ErrStreamClosed` as a named sentinel error for streams closed by cancellation or cleanup before a read begins.
- Added four new tests covering: nil context safety, closed-context returning `ErrStreamClosed`, panic recovery after timeout, and cleanup blocking until the timer callback finishes.

- [x] Bug fix

- [x] Core (Go)

```sh
go test ./core/providers/utils/... -v -race
```

All four new tests should pass, including `TestIdleTimeoutReader_CleanupWaitsForRunningTimerCallback` which validates the synchronisation behaviour under the race detector.

- [ ] Yes
- [x] No

None. The changes are scoped to internal stream lifecycle management with no impact on auth, secrets, or PII handling.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* mcp log fingerprinting (maximhq#3678)

MCP tool log entries were not being stamped with DAC (Data Access Control) governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) from the request context. This meant MCP logs could not be attributed to the correct organizational entities for governance and auditing purposes.

- Introduced `applyMCPGovernanceFieldsToEntry`, a helper that reads governance identity fields from the `BifrostContext` and stamps them onto an `MCPToolLog` entry.
- Called this helper in both `PreMCPHook` and `PostMCPHook` so that governance fields are applied regardless of whether the log entry originates from a normal pre/post flow or the post-hook fallback path (where no pending pre-hook entry exists).
- Added `assertMCPLogGovernanceFields` as a shared test helper to validate all four governance fields on a log entry.
- Extended `TestMCPHooksDeferDBWriteUntilPostHookBatch` to set governance context values and assert they are persisted correctly.
- Added `TestPostMCPHookFallbackStampsGovernanceFields` to verify that fallback-created MCP log entries (post-hook only, no prior pre-hook) also carry the correct governance fields.

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./plugins/logging/... -run TestMCPHooksDeferDBWriteUntilPostHookBatch
go test ./plugins/logging/... -run TestPostMCPHookFallbackStampsGovernanceFields
go test ./plugins/logging/...
```

Both tests should pass. Verify that after a `PreMCPHook` or `PostMCPHook` call with governance context values set, the resulting `MCPToolLog` entry in the store has non-nil `UserID`, `TeamID`, `CustomerID`, and `BusinessUnitID` matching the values placed in the context.

N/A

- [ ] Yes
- [x] No

N/A

Governance ownership fields (`user_id`, `team_id`, `customer_id`, `business_unit_id`) are sourced exclusively from the authenticated request context and are only written when non-empty, ensuring no unintended data leakage or field overwriting occurs.

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [ ] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

* fix: update e2e ui tests (maximhq#3687)

Improves E2E test stability for virtual key editing by handling a budget reset dialog that can appear after saving, and marks a known flaky bulk-rotate test as `fixme` until the underlying UI bug is resolved.

- Added `preserveBudgetUsageIfPrompted()` helper that detects the `vk-budget-reset-dialog` and clicks the preserve button if it appears after saving a virtual key. This prevents test failures caused by an unexpected dialog interrupting the save flow.
- Marked `should bulk rotate selected virtual keys only` as `test.fixme` due to a UI bug where the checkbox selection state resets when the search input filters out a previously selected row. When `bulkRotateVirtualKeys` searches by name to select each key, the first key becomes deselected as the search narrows to the second, resulting in only the last key being rotated.

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the virtual keys E2E suite and confirm no failures occur on the save flow due to the budget reset dialog:

```sh
cd tests/e2e
npx playwright test features/virtual-keys/virtual-keys.spec.ts
```

The bulk rotate test will be skipped (`fixme`) and should not cause CI failures.

N/A

- [ ] Yes
- [x] No

N/A

None.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* fix: updates mcp oauth api tests (maximhq#3693)

Consolidates the Per User OAuth Postman coverage probes to align with the updated API surface, replacing the old register/authorize/token/consent/upstream endpoints with the new flow-based endpoints (`/api/oauth/per-user/flows/:flowId` and `/api/oauth/per-user/flows/:flowId/start`).

- Removed coverage probe requests for `Per User OAuth Register`, `Per User OAuth Authorize`, `Per User OAuth Token`, `Per User OAuth Upstream Authorize`, `Per User Consent VK`, `Per User Consent User ID`, `Per User Consent Skip`, and `Per User Consent Submit`
- Added coverage probe requests for `Per User OAuth Flow Detail` (`GET /api/oauth/per-user/flows/coverage-probe-flow`) and `Per User OAuth Flow Start` (`GET /api/oauth/per-user/flows/coverage-probe-flow/start`)
- Added `OAuth Callback (Coverage Probe)` to the raw text shapes validation map, replacing the three previously tracked OAuth probe entries

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

Run the updated Postman collection against a running Bifrost instance and verify that the new flow-based coverage probe requests return expected responses and that the response structure validation passes.

- [ ] Yes
- [x] No

No security implications. These are test coverage probes only.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable

* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Madhu Shantan <madhushantangot@gmail.com>
Co-authored-by: roroghost17 <roroghost17@gmail.com>
Co-authored-by: Suresh Chaudhary <83772622+impoiler@users.noreply.github.com>
Co-authored-by: Tejas Ghatte <64637256+TejasGhatte@users.noreply.github.com>
Co-authored-by: Nicholas Dunzelman <139033898+dicnunz@users.noreply.github.com>
Co-authored-by: Vaibhav Mittal <mittal.shaluatul@gmail.com>
Co-authored-by: Akshay Deo <akshay@akshaydeo.com>
Co-authored-by: Anuj Parihar <anujparihar@yahoo.com>
Co-authored-by: Samyabrata Maji <samyabratamaji334@gmail.com>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
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.

2 participants