Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b7d6813
feat: adds team budget and rl collection exporters
roroghost17 Jun 3, 2026
75288aa
feat: adds support for multiple team and bu units in logstore
roroghost17 Jun 3, 2026
fe3db39
fix: fixes cascade deletes for model configs
roroghost17 Jun 4, 2026
85f9bf1
fix: fixes virtual key model configs UI
roroghost17 Jun 4, 2026
c2e4366
fet: supports multiple customers logs and usage tracking
roroghost17 Jun 4, 2026
3fb0215
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
f120472
feat: governance views fixes
impoiler Jun 3, 2026
acb03f7
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
24e649c
fix: dashboard ranking charts label and hover state of all charts too…
impoiler Jun 4, 2026
4d4802e
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
ffefed9
fix: governance pages layout
impoiler Jun 4, 2026
9c5b601
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
748d230
chore: Make the filterable entity in log detail sheets clickable for …
impoiler Jun 4, 2026
901263b
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
880609d
chore: uniform view of budget and limits for virtual keys table
impoiler Jun 4, 2026
49b4755
fix: replicates vk hierarchy flow for ghost nodes reconcilation
roroghost17 Jun 5, 2026
186956b
feat: allow viewing filtered logs for Governance entities
impoiler Jun 5, 2026
107ef5b
fix: [BUG] add alias filter support for matview queries
impoiler Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 41 additions & 35 deletions core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,29 +203,35 @@ const (
MCPContextKeyIncludeClients BifrostContextKey = "mcp-include-clients" // Context key for whitelist client filtering
MCPContextKeyIncludeTools BifrostContextKey = "mcp-include-tools" // Context key for whitelist tool filtering (Note: toolName should be in "clientName-toolName" format for individual tools, or "clientName-*" for wildcard)

BifrostContextKeySelectedKeyID BifrostContextKey = "bifrost-selected-key-id" // string (to store the selected key ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedKeyName BifrostContextKey = "bifrost-selected-key-name" // string (to store the selected key name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceVirtualKeyID BifrostContextKey = "bifrost-governance-virtual-key-id" // string (to store the virtual key ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceVirtualKeyName BifrostContextKey = "bifrost-governance-virtual-key-name" // string (to store the virtual key name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceTeamID BifrostContextKey = "bifrost-governance-team-id" // string (to store the team ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceTeamName BifrostContextKey = "bifrost-governance-team-name" // string (to store the team name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerID BifrostContextKey = "bifrost-governance-customer-id" // string (to store the customer ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerName BifrostContextKey = "bifrost-governance-customer-name" // string (to store the customer name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitID BifrostContextKey = "bifrost-governance-business-unit-id" // string (to store the business unit ID (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitName BifrostContextKey = "bifrost-governance-business-unit-name" // string (to store the business unit name (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleID BifrostContextKey = "bifrost-governance-routing-rule-id" // string (to store the routing rule ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleName BifrostContextKey = "bifrost-governance-routing-rule-name" // string (to store the routing rule name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptName BifrostContextKey = "bifrost-selected-prompt-name" // string (display name of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptVersion BifrostContextKey = "bifrost-selected-prompt-version" // string (numeric version as string, e.g. "3" (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptID BifrostContextKey = "bifrost-selected-prompt-id" // string (id of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceIncludeOnlyKeys BifrostContextKey = "bf-governance-include-only-keys" // []string (to store the include-only key IDs for provider config routing (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyNumberOfRetries BifrostContextKey = "bifrost-number-of-retries" // int (to store the number of retries (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyFallbackIndex BifrostContextKey = "bifrost-fallback-index" // int (to store the fallback index (set by bifrost - DO NOT SET THIS MANUALLY)) 0 for primary, 1 for first fallback, etc.
BifrostContextKeyStreamEndIndicator BifrostContextKey = "bifrost-stream-end-indicator" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyStreamIdleTimeout BifrostContextKey = "bifrost-stream-idle-timeout" // time.Duration (per-chunk idle timeout for streaming)
BifrostContextKeySkipKeySelection BifrostContextKey = "bifrost-skip-key-selection" // bool (will pass an empty key to the provider)
BifrostContextKeyExtraHeaders BifrostContextKey = "bifrost-extra-headers" // map[string][]string
BifrostContextKeyURLPath BifrostContextKey = "bifrost-extra-url-path" // string
BifrostContextKeySelectedKeyID BifrostContextKey = "bifrost-selected-key-id" // string (to store the selected key ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedKeyName BifrostContextKey = "bifrost-selected-key-name" // string (to store the selected key name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceVirtualKeyID BifrostContextKey = "bifrost-governance-virtual-key-id" // string (to store the virtual key ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceVirtualKeyName BifrostContextKey = "bifrost-governance-virtual-key-name" // string (to store the virtual key name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceTeamID BifrostContextKey = "bifrost-governance-team-id" // string (to store the team ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceTeamName BifrostContextKey = "bifrost-governance-team-name" // string (to store the team name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerID BifrostContextKey = "bifrost-governance-customer-id" // string (to store the customer ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceCustomerName BifrostContextKey = "bifrost-governance-customer-name" // string (to store the customer name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitID BifrostContextKey = "bifrost-governance-business-unit-id" // string (to store the business unit ID (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceBusinessUnitName BifrostContextKey = "bifrost-governance-business-unit-name" // string (to store the business unit name (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceTeamIDs BifrostContextKey = "bifrost-governance-team-ids" // []string (all teams a user/AP request belongs to; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceTeamNames BifrostContextKey = "bifrost-governance-team-names" // []string (display names, aligned with team-ids; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceBusinessUnitIDs BifrostContextKey = "bifrost-governance-business-unit-ids" // []string (distinct BUs across the user's teams; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceBusinessUnitNames BifrostContextKey = "bifrost-governance-business-unit-names" // []string (display names, aligned with business-unit-ids; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceCustomerIDs BifrostContextKey = "bifrost-governance-customer-ids" // []string (distinct customers a user/team request belongs to; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceCustomerNames BifrostContextKey = "bifrost-governance-customer-names" // []string (display names, aligned with customer-ids; set by enterprise governance plugin - DO NOT SET THIS MANUALLY)
BifrostContextKeyGovernanceRoutingRuleID BifrostContextKey = "bifrost-governance-routing-rule-id" // string (to store the routing rule ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleName BifrostContextKey = "bifrost-governance-routing-rule-name" // string (to store the routing rule name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptName BifrostContextKey = "bifrost-selected-prompt-name" // string (display name of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptVersion BifrostContextKey = "bifrost-selected-prompt-version" // string (numeric version as string, e.g. "3" (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptID BifrostContextKey = "bifrost-selected-prompt-id" // string (id of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceIncludeOnlyKeys BifrostContextKey = "bf-governance-include-only-keys" // []string (to store the include-only key IDs for provider config routing (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyNumberOfRetries BifrostContextKey = "bifrost-number-of-retries" // int (to store the number of retries (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyFallbackIndex BifrostContextKey = "bifrost-fallback-index" // int (to store the fallback index (set by bifrost - DO NOT SET THIS MANUALLY)) 0 for primary, 1 for first fallback, etc.
BifrostContextKeyStreamEndIndicator BifrostContextKey = "bifrost-stream-end-indicator" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyStreamIdleTimeout BifrostContextKey = "bifrost-stream-idle-timeout" // time.Duration (per-chunk idle timeout for streaming)
BifrostContextKeySkipKeySelection BifrostContextKey = "bifrost-skip-key-selection" // bool (will pass an empty key to the provider)
BifrostContextKeyExtraHeaders BifrostContextKey = "bifrost-extra-headers" // map[string][]string
BifrostContextKeyURLPath BifrostContextKey = "bifrost-extra-url-path" // string
BifrostContextKeyUseRawRequestBody BifrostContextKey = "bifrost-use-raw-request-body"
BifrostContextKeyChangeRequestType BifrostContextKey = "bifrost-change-request-type" // RequestType (set by plugins to trigger request type conversion in core, e.g. text->chat or chat->responses)
BifrostContextKeySendBackRawRequest BifrostContextKey = "bifrost-send-back-raw-request" // bool (per-request override — read by bifrost.go, never overwritten)
Expand Down Expand Up @@ -1433,7 +1439,7 @@ type BifrostResponseExtraFields struct {
ConvertedRequestType RequestType `json:"converted_request_type,omitempty"`
DroppedCompatPluginParams []string `json:"dropped_compat_plugin_params,omitempty"` // params dropped by the compat plugin based on model catalog
ProviderResponseHeaders map[string]string `json:"provider_response_headers,omitempty"` // HTTP response headers from the provider (filtered to exclude transport-level headers)
PassthroughPath string `json:"passthrough_path,omitempty"` // Stripped provider path for passthrough requests, e.g. "/v1/chat/completions"
PassthroughPath string `json:"passthrough_path,omitempty"` // Stripped provider path for passthrough requests, e.g. "/v1/chat/completions"
}

type BifrostMCPResponseExtraFields struct {
Expand Down Expand Up @@ -1670,15 +1676,15 @@ func (e *ErrorField) UnmarshalJSON(data []byte) error {

// BifrostErrorExtraFields contains additional fields in an error response.
type BifrostErrorExtraFields struct {
Provider ModelProvider `json:"provider,omitempty"`
OriginalModelRequested string `json:"original_model_requested,omitempty"`
ResolvedModelUsed string `json:"resolved_model_used,omitempty"`
RequestType RequestType `json:"request_type,omitempty"`
MCPRequestType MCPRequestType `json:"mcp_request_type,omitempty"`
RawRequest interface{} `json:"raw_request,omitempty"`
RawResponse interface{} `json:"raw_response,omitempty"`
ConvertedRequestType RequestType `json:"converted_request_type,omitempty"`
DroppedCompatPluginParams []string `json:"dropped_compat_plugin_params,omitempty"`
KeyStatuses []KeyStatus `json:"key_statuses,omitempty"`
MCPAuthRequired *MCPAuthRequiredError `json:"mcp_auth_required,omitempty"` // Set when a per-user MCP tool requires the caller to complete an inline auth flow (OAuth or headers)
Provider ModelProvider `json:"provider,omitempty"`
OriginalModelRequested string `json:"original_model_requested,omitempty"`
ResolvedModelUsed string `json:"resolved_model_used,omitempty"`
RequestType RequestType `json:"request_type,omitempty"`
MCPRequestType MCPRequestType `json:"mcp_request_type,omitempty"`
RawRequest interface{} `json:"raw_request,omitempty"`
RawResponse interface{} `json:"raw_response,omitempty"`
ConvertedRequestType RequestType `json:"converted_request_type,omitempty"`
DroppedCompatPluginParams []string `json:"dropped_compat_plugin_params,omitempty"`
KeyStatuses []KeyStatus `json:"key_statuses,omitempty"`
MCPAuthRequired *MCPAuthRequiredError `json:"mcp_auth_required,omitempty"` // Set when a per-user MCP tool requires the caller to complete an inline auth flow (OAuth or headers)
}
96 changes: 96 additions & 0 deletions framework/configstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
if err := migrationAddCustomerBudgetsToBudgetsTable(ctx, db); err != nil {
return err
}
if err := migrationAddModelConfigBudgetsFKConstraint(ctx, db); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -7382,6 +7385,99 @@ func migrationAddTeamBudgetsToBudgetsTable(ctx context.Context, db *gorm.DB) err
return nil
}

// migrationAddModelConfigBudgetsFKConstraint adds the missing
// governance_budgets.model_config_id -> governance_model_configs(id)
// ON DELETE CASCADE foreign key (defined on TableModelConfig.Budgets via
// foreignKey:ModelConfigID;constraint:OnDelete:CASCADE).
//
// migrationAddMultiBudgetTables created the equivalent cascade FKs for VK- and
// ProviderConfig-owned budgets but never the model-config edge, and
// migrationAddBudgetModelConfigIDColumn added the column without a constraint.
// As a result deleting a model config never cascaded to its multi-budget rows,
// so they leaked (orphaned governance_budgets whose model_config_id points at a
// since-deleted config). This makes that cleanup structurally sound at the DB
// level, underneath the existing application-level cleanup in
// DeleteModelConfigsForScope/DeleteModelConfig. (The single owned rate-limit is
// intentionally left to application cleanup — rate-limits use the opposite
// owner.rate_limit_id convention, so reversing it just for model configs would
// introduce a one-off ownership split for a one-row-per-config leak surface.)
func migrationAddModelConfigBudgetsFKConstraint(ctx context.Context, db *gorm.DB) error {
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
ID: "add_model_config_budgets_fk_constraint",
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
mg := tx.Migrator()

// Pre-clean: budgets whose model_config_id already references a
// missing config would violate the FK we're about to add and block
// its creation. They are exactly the rows the cascade would have
// removed, so delete them — but only when nothing live still
// references them via the legacy governance_model_configs.budget_id
// (a NO ACTION FK), so this DELETE can't trip that constraint.
if err := tx.Exec(`
DELETE FROM governance_budgets
WHERE model_config_id IS NOT NULL
AND model_config_id NOT IN (SELECT id FROM governance_model_configs)
AND id NOT IN (
SELECT budget_id FROM governance_model_configs WHERE budget_id IS NOT NULL
)
`).Error; err != nil {
return fmt.Errorf("failed to pre-clean orphaned model-config budgets: %w", err)
}

// Create the cascade FK (no-op if a prior fresh-DB migrate already made it).
if !mg.HasConstraint(&tables.TableModelConfig{}, "Budgets") {
if err := mg.CreateConstraint(&tables.TableModelConfig{}, "Budgets"); err != nil {
return fmt.Errorf("failed to create FK constraint for ModelConfig -> Budgets: %w", err)
}
}
return nil
},
// Partially non-rollbackable: dropping the FK restores the previous
// schema, but the orphaned governance_budgets rows removed by the
// pre-clean are gone permanently. That loss is intentional — they were
// exactly the dead rows the missing cascade had leaked — so the schema
// rollback below is still provided rather than hard-failing.
Rollback: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
mg := tx.Migrator()
if mg.HasConstraint(&tables.TableModelConfig{}, "Budgets") {
if err := mg.DropConstraint(&tables.TableModelConfig{}, "Budgets"); err != nil {
return err
}
}
return nil
},
}})
// SQLite workaround — same reasoning as migrationAddMultiBudgetTables:
// CreateConstraint rebuilds governance_budgets via DROP+RENAME inside a
// transaction, which fails while other tables hold FKs into it and
// foreign_keys is ON. PRAGMA foreign_keys can't change inside a transaction,
// so disable it (pinned to one connection) before the migrator opens its tx.
// Postgres supports ALTER TABLE ADD CONSTRAINT natively and needs none of this.
if db.Dialector.Name() == "sqlite" {
sqlDB, err := db.DB()
if err != nil {
return fmt.Errorf("failed to get underlying sql.DB: %w", err)
}
sqlDB.SetMaxOpenConns(1)
defer sqlDB.SetMaxOpenConns(0)

if err := db.Exec("PRAGMA foreign_keys = OFF").Error; err != nil {
return fmt.Errorf("failed to disable SQLite foreign keys: %w", err)
}
defer func() {
if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
log.Fatalf("[Migration] FATAL: failed to re-enable SQLite foreign keys: %v", err)
}
}()
}
if err := m.Migrate(); err != nil {
return fmt.Errorf("error running add_model_config_budgets_fk_constraint migration: %s", err.Error())
}
return nil
}

// migrationAddPerUserOAuthTables adds the oauth_user_sessions and oauth_user_tokens tables
func migrationAddPerUserOAuthTables(ctx context.Context, db *gorm.DB) error {
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
Expand Down
Loading
Loading