diff --git a/framework/logstore/rdb.go b/framework/logstore/rdb.go
index d6b0cc87597..1a962ef52a4 100644
--- a/framework/logstore/rdb.go
+++ b/framework/logstore/rdb.go
@@ -908,6 +908,7 @@ func stripNonBillingPayloadBytes(l *Log) {
l.ImageGenerationOutput = ""
}
+// searchLogs runs scoped log searches with the requested projection.
func (s *RDBLogStore) searchLogs(ctx context.Context, filters SearchFilters, pagination PaginationOptions, selectColumns string) (*SearchResult, error) {
// Build order clause up front (needed by the data goroutine).
direction := "DESC"
@@ -1184,6 +1185,7 @@ func (s *RDBLogStore) GetSessionSummary(ctx context.Context, sessionID string) (
}, nil
}
+// normalizeAggregateTimestamp normalizes driver-specific timestamp values for aggregate responses.
func normalizeAggregateTimestamp(value any) string {
switch v := value.(type) {
case nil:
@@ -2460,6 +2462,7 @@ type latencyHistogramBucketData struct {
overheads []float64
}
+// toBucket converts accumulated latency values into the response bucket.
func (bd *latencyHistogramBucketData) toBucket(ts int64) LatencyHistogramBucket {
b := LatencyHistogramBucket{
Timestamp: time.Unix(ts, 0).UTC(),
@@ -4591,6 +4594,25 @@ func (s *RDBLogStore) DeleteLogs(ctx context.Context, ids []string) error {
// applyMCPFilters applies search filters to a GORM query for MCP tool logs
func (s *RDBLogStore) applyMCPFilters(baseQuery *gorm.DB, filters MCPToolLogSearchFilters) *gorm.DB {
+ if len(filters.UserIDs) > 0 {
+ baseQuery = baseQuery.Where("user_id IN ?", filters.UserIDs)
+ }
+ if len(filters.TeamIDs) > 0 {
+ baseQuery = baseQuery.Where("team_id IN ?", filters.TeamIDs)
+ }
+ if len(filters.CustomerIDs) > 0 {
+ baseQuery = baseQuery.Where("customer_id IN ?", filters.CustomerIDs)
+ }
+ if len(filters.BusinessUnitIDs) > 0 {
+ baseQuery = baseQuery.Where("business_unit_id IN ?", filters.BusinessUnitIDs)
+ }
+ if len(filters.ProjectIDs) > 0 {
+ baseQuery = baseQuery.Where("project_id IN ?", filters.ProjectIDs)
+ }
+ if len(filters.DeviceIDs) > 0 {
+ baseQuery = baseQuery.Where("device_id IN ?", filters.DeviceIDs)
+ }
+
if len(filters.ToolNames) > 0 {
baseQuery = baseQuery.Where("tool_name IN ?", filters.ToolNames)
}
@@ -4895,6 +4917,7 @@ func (s *RDBLogStore) GetAvailableToolNames(ctx context.Context, limit int, quer
return toolNames, nil
}
+// GetAvailableServerLabels lists MCP server labels matching the filter search.
func (s *RDBLogStore) GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) {
cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays)
var serverLabels []string
@@ -4943,6 +4966,7 @@ func (s *RDBLogStore) GetAvailableMCPApps(ctx context.Context, limit int, query
return apps, nil
}
+// GetAvailableMCPVirtualKeys lists virtual keys represented in visible MCP logs.
func (s *RDBLogStore) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) ([]MCPToolLog, error) {
cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays)
var logs []MCPToolLog
diff --git a/framework/logstore/rdb_perf_test.go b/framework/logstore/rdbperf_test.go
similarity index 100%
rename from framework/logstore/rdb_perf_test.go
rename to framework/logstore/rdbperf_test.go
diff --git a/framework/logstore/rdb_postgres_perf_test.go b/framework/logstore/rdbpostgres_perf_test.go
similarity index 100%
rename from framework/logstore/rdb_postgres_perf_test.go
rename to framework/logstore/rdbpostgres_perf_test.go
diff --git a/framework/logstore/rdb_ranking_limit_test.go b/framework/logstore/rdbranking_limit_test.go
similarity index 100%
rename from framework/logstore/rdb_ranking_limit_test.go
rename to framework/logstore/rdbranking_limit_test.go
diff --git a/framework/logstore/rdb_requestid_test.go b/framework/logstore/rdbrequestid_test.go
similarity index 100%
rename from framework/logstore/rdb_requestid_test.go
rename to framework/logstore/rdbrequestid_test.go
diff --git a/framework/logstore/rdb_rootsonly_test.go b/framework/logstore/rdbrootsonly_test.go
similarity index 100%
rename from framework/logstore/rdb_rootsonly_test.go
rename to framework/logstore/rdbrootsonly_test.go
diff --git a/framework/logstore/rdb_stats_test.go b/framework/logstore/rdbstats_test.go
similarity index 54%
rename from framework/logstore/rdb_stats_test.go
rename to framework/logstore/rdbstats_test.go
index d68f84d7583..79d8a7f28ed 100644
--- a/framework/logstore/rdb_stats_test.go
+++ b/framework/logstore/rdbstats_test.go
@@ -56,3 +56,26 @@ func TestGetStatsTokenSplit(t *testing.T) {
require.Equal(t, int64(70), stats.CompletionTokens, "completion = 10+20+40")
require.Equal(t, stats.TotalTokens, stats.PromptTokens+stats.CompletionTokens, "split sums to total")
}
+
+// TestMCPAttributionFiltersApplyToRowsAndStats checks each stored scope filters both records and aggregates.
+func TestMCPAttributionFiltersApplyToRowsAndStats(t *testing.T) {
+ db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
+ require.NoError(t, err)
+ require.NoError(t, db.AutoMigrate(&MCPToolLog{}))
+ store := &RDBLogStore{db: db, logger: bifrost.NewDefaultLogger(schemas.LogLevelInfo)}
+ ctx := context.Background()
+ a, b := "a", "b"
+ now := time.Now()
+ require.NoError(t, db.Create(&MCPToolLog{ID: a, ToolName: "Read", Timestamp: now, Status: "success", UserID: &a, TeamID: &a, CustomerID: &a, BusinessUnitID: &a, ProjectID: &a, DeviceID: &a}).Error)
+ require.NoError(t, db.Create(&MCPToolLog{ID: b, ToolName: "Read", Timestamp: now, Status: "error", UserID: &b, TeamID: &b, CustomerID: &b, BusinessUnitID: &b, ProjectID: &b, DeviceID: &b}).Error)
+ for _, filters := range []MCPToolLogSearchFilters{{UserIDs: []string{a}}, {TeamIDs: []string{a}}, {CustomerIDs: []string{a}}, {BusinessUnitIDs: []string{a}}, {ProjectIDs: []string{a}}, {DeviceIDs: []string{a}}} {
+ result, err := store.SearchMCPToolLogs(ctx, filters, PaginationOptions{Limit: 10, SortBy: "timestamp", Order: "desc"})
+ require.NoError(t, err)
+ require.Len(t, result.Logs, 1)
+ require.Equal(t, a, result.Logs[0].ID)
+ stats, err := store.GetMCPToolLogStats(ctx, filters)
+ require.NoError(t, err)
+ require.EqualValues(t, 1, stats.TotalExecutions)
+ require.EqualValues(t, 100, stats.SuccessRate)
+ }
+}
diff --git a/framework/logstore/rdb_toolcallnames_test.go b/framework/logstore/rdbtoolcallnames_test.go
similarity index 100%
rename from framework/logstore/rdb_toolcallnames_test.go
rename to framework/logstore/rdbtoolcallnames_test.go
diff --git a/framework/logstore/safe_jsonb_test.go b/framework/logstore/safejsonb_test.go
similarity index 100%
rename from framework/logstore/safe_jsonb_test.go
rename to framework/logstore/safejsonb_test.go
diff --git a/framework/logstore/tables.go b/framework/logstore/tables.go
index 9b225e1469f..af398cb7731 100644
--- a/framework/logstore/tables.go
+++ b/framework/logstore/tables.go
@@ -1354,6 +1354,12 @@ func costsReconcile(a, b float64) bool {
// MCPToolLog represents a log entry for MCP tool executions
// This is separate from the main Log table since MCP tool calls have different fields
type MCPToolLog struct {
+ // Display names are resolved after scoped reads; IDs remain the recorded attribution.
+ UserName *string `gorm:"-" json:"user_name,omitempty"`
+ TeamName *string `gorm:"-" json:"team_name,omitempty"`
+ CustomerName *string `gorm:"-" json:"customer_name,omitempty"`
+ BusinessUnitName *string `gorm:"-" json:"business_unit_name,omitempty"`
+
ID string `gorm:"primaryKey;type:varchar(255)" json:"id"`
RequestID string `gorm:"type:varchar(255);column:request_id;index:idx_mcp_logs_request_id" json:"request_id,omitempty"` // The original request ID from context
LLMRequestID *string `gorm:"type:varchar(255);column:llm_request_id;index:idx_mcp_logs_llm_request_id" json:"llm_request_id,omitempty"` // Links to the LLM request that triggered this tool call
@@ -1388,7 +1394,7 @@ type MCPToolLog struct {
// Endpoint-agent context. These are populated for tool calls observed on a
// developer machine by the Bifrost Edge agent (rather than proxied by the
// gateway). Source distinguishes the origin: empty/null for gateway-proxied
- // calls, "endpoint" for agent-observed calls.
+ // calls, "endpoint" for agent-observed MCP calls, "native" for harness tools.
DeviceID *string `gorm:"type:varchar(255);index:idx_mcp_logs_device_id" json:"device_id,omitempty"`
AppKey *string `gorm:"type:varchar(64)" json:"app_key,omitempty"` // Canonical policy key of the detected client app (schemas.AppKeyFromName), e.g. "claude-code"; a slug like App, not a secret or credential
Decision *string `gorm:"type:varchar(16)" json:"decision,omitempty"`
@@ -1694,6 +1700,13 @@ type WebhookDeliverySearchFilters struct {
// MCPToolLogSearchFilters represents the available filters for MCP tool log searches
type MCPToolLogSearchFilters struct {
+ UserIDs []string `json:"user_ids,omitempty"`
+ TeamIDs []string `json:"team_ids,omitempty"`
+ CustomerIDs []string `json:"customer_ids,omitempty"`
+ BusinessUnitIDs []string `json:"business_unit_ids,omitempty"`
+ ProjectIDs []string `json:"project_ids,omitempty"`
+ DeviceIDs []string `json:"device_ids,omitempty"`
+
ToolNames []string `json:"tool_names,omitempty"`
ServerLabels []string `json:"server_labels,omitempty"`
Status []string `json:"status,omitempty"`
@@ -2315,6 +2328,7 @@ var dimensionColumns = map[RankingDimension]dimensionColumnDef{
RankingDimensionUserAgent: {IDCol: "user_agent", NameCol: "user_agent"},
}
+// DimensionColumnDef returns the column pair for a supported ranking dimension.
func DimensionColumnDef(d RankingDimension) (idCol, nameCol string, ok bool) {
def, exists := dimensionColumns[d]
return def.IDCol, def.NameCol, exists
diff --git a/plugins/governance/main.go b/plugins/governance/main.go
index 39eeed5f40a..3d429b60913 100644
--- a/plugins/governance/main.go
+++ b/plugins/governance/main.go
@@ -1322,7 +1322,7 @@ func (p *GovernancePlugin) PreMCPHook(ctx *schemas.BifrostContext, req *schemas.
// disagree. What is left is a question about the tool, which the access answers whatever granted it.
// A request carrying no access is unrestricted and may execute any tool, as it always could.
access := ctx.Grant().Access()
- if access != nil && !access.IsMCPToolAllowed(toolName) {
+ if access != nil && !access.IsMCPToolAllowed(toolName) && !hasMCPExecutionAuthorization(ctx, req) {
ctx.SetValue(governanceRejectedContextKey, true)
return req, &schemas.MCPPluginShortCircuit{Error: &schemas.BifrostError{
Type: bifrost.Ptr(string(DecisionMCPToolBlocked)),
diff --git a/plugins/governance/mcpauthorization.go b/plugins/governance/mcpauthorization.go
new file mode 100644
index 00000000000..ecf83d44482
--- /dev/null
+++ b/plugins/governance/mcpauthorization.go
@@ -0,0 +1,21 @@
+package governance
+
+import "github.com/maximhq/bifrost/core/schemas"
+
+const mcpAuthorizationContextKey schemas.BifrostContextKey = "bf-governance-mcp-authorization"
+
+// mcpAuthorization binds trusted transport approval to one exact execution target.
+type mcpAuthorization struct{ clientName, toolName string }
+
+// SetMCPExecutionAuthorization records a transport-verified approval for one MCP call.
+// Only trusted handlers may call this after checking their own authorization policy;
+// it replaces the MCP tool permit check, never identity, headers or usage limits.
+func SetMCPExecutionAuthorization(ctx *schemas.BifrostContext, clientName, toolName string) {
+ ctx.SetValue(mcpAuthorizationContextKey, mcpAuthorization{clientName, toolName})
+}
+
+// hasMCPExecutionAuthorization prevents approval from following a retargeted request.
+func hasMCPExecutionAuthorization(ctx *schemas.BifrostContext, req *schemas.BifrostMCPRequest) bool {
+ approval, ok := ctx.Value(mcpAuthorizationContextKey).(mcpAuthorization)
+ return ok && approval.clientName != "" && approval.toolName != "" && req.ClientName == approval.clientName && req.GetToolName() == approval.toolName
+}
diff --git a/plugins/governance/mcpauthorization_test.go b/plugins/governance/mcpauthorization_test.go
new file mode 100644
index 00000000000..5810a4a2682
--- /dev/null
+++ b/plugins/governance/mcpauthorization_test.go
@@ -0,0 +1,53 @@
+package governance
+
+import (
+ "testing"
+
+ "github.com/maximhq/bifrost/core/schemas"
+ "github.com/stretchr/testify/require"
+)
+
+// TestMCPExecutionAuthorization preserves governance while replacing only the exact tool permit.
+func TestMCPExecutionAuthorization(t *testing.T) {
+ p := newPluginForMCPStamping(t, buildVKForMCPStamping(nil), false)
+ for _, tc := range []struct {
+ name, client, tool, key string
+ approved, allowed bool
+ }{
+ {"gateway", "local", "local-read", mcpTestVKValue, false, false},
+ {"approved", "local", "local-read", mcpTestVKValue, true, true},
+ {"other tool", "local", "local-write", mcpTestVKValue, true, false},
+ {"other client", "other", "local-read", mcpTestVKValue, true, false},
+ {"invalid identity", "local", "local-read", "invalid", true, false},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := presentCtx(tc.key)
+ if tc.approved {
+ SetMCPExecutionAuthorization(ctx, "local", "local-read")
+ }
+ req := &schemas.BifrostMCPRequest{RequestType: schemas.MCPRequestTypeChatToolCall, ClientName: tc.client, ChatAssistantMessageToolCall: &schemas.ChatAssistantMessageToolCall{Function: schemas.ChatAssistantMessageToolCallFunction{Name: &tc.tool, Arguments: "{}"}}}
+ _, short, err := p.PreMCPHook(ctx, req)
+ require.NoError(t, err)
+ if tc.allowed {
+ require.Nil(t, short)
+ } else {
+ require.NotNil(t, short)
+ require.NotNil(t, short.Error)
+ }
+ })
+ }
+}
+
+// TestMCPExecutionAuthorizationRequiresHeaders keeps transport requirements ahead of tool approval.
+func TestMCPExecutionAuthorizationRequiresHeaders(t *testing.T) {
+ p := newPluginForMCPStamping(t, buildVKForMCPStamping(nil), false)
+ p.requiredHeaders = &[]string{"x-required"}
+ ctx := presentCtx(mcpTestVKValue)
+ SetMCPExecutionAuthorization(ctx, "local", "local-read")
+ name := "local-read"
+ req := &schemas.BifrostMCPRequest{RequestType: schemas.MCPRequestTypeChatToolCall, ClientName: "local", ChatAssistantMessageToolCall: &schemas.ChatAssistantMessageToolCall{Function: schemas.ChatAssistantMessageToolCallFunction{Name: &name, Arguments: "{}"}}}
+ _, short, err := p.PreMCPHook(ctx, req)
+ require.NoError(t, err)
+ require.NotNil(t, short)
+ require.Equal(t, "missing_required_headers", *short.Error.Type)
+}
diff --git a/plugins/logging/writer.go b/plugins/logging/writer.go
index 7cd5605d8a4..8a6797e54fc 100644
--- a/plugins/logging/writer.go
+++ b/plugins/logging/writer.go
@@ -312,6 +312,14 @@ func (p *LoggerPlugin) EnqueueLogEntry(entry *logstore.Log) {
p.enqueueLogEntry(entry, p.makePostWriteCallback(nil))
}
+// EnqueueMCPToolLogEntry pushes a completed MCP log through the normal async write queue.
+func (p *LoggerPlugin) EnqueueMCPToolLogEntry(entry *logstore.MCPToolLog) {
+ p.mu.Lock()
+ callback := p.mcpToolLogCallback
+ p.mu.Unlock()
+ p.enqueueMCPToolLogEntry(entry, callback)
+}
+
// enqueueMCPToolLogEntry pushes a complete MCP tool log entry to the write queue.
// If the queue is full, the entry is dropped to prevent store slowness from
// cascading into request handling goroutines.
diff --git a/transports/bifrost-http/handlers/logging.go b/transports/bifrost-http/handlers/logging.go
index ccc014a68f9..47e37379f82 100644
--- a/transports/bifrost-http/handlers/logging.go
+++ b/transports/bifrost-http/handlers/logging.go
@@ -301,16 +301,19 @@ func (c *filterDataCache) load(key string) (*filterDataCacheEntry, map[string]in
return entry, nil, false
}
+// store publishes a completed filter-data cache result.
func (c *filterDataCache) store(entry *filterDataCacheEntry, payload map[string]interface{}) {
entry.payload = payload
entry.expiresAt = time.Now().Add(filterDataCacheTTL)
entry.mu.Unlock()
}
+// release releases a filter-data cache entry after a fetch attempt.
func (c *filterDataCache) release(entry *filterDataCacheEntry) {
entry.mu.Unlock()
}
+// parseParentRequestIDFilter reads the parent request ID filter from the request.
func parseParentRequestIDFilter(ctx *fasthttp.RequestCtx) string {
if parentRequestID := string(ctx.QueryArgs().Peek("parent_request_id")); strings.TrimSpace(parentRequestID) != "" {
return parentRequestID
@@ -359,6 +362,7 @@ func (h *LoggingHandler) SetMCPLogRedactionMappingResolver(resolver MCPLogRedact
h.mcpLogRedactionMappingResolver = resolver
}
+// shouldHideDeletedVirtualKeysInFilters reads the configured deleted-key visibility policy.
func (h *LoggingHandler) shouldHideDeletedVirtualKeysInFilters() bool {
if h == nil || h.config == nil {
return false
@@ -413,6 +417,7 @@ func (h *LoggingHandler) RegisterRoutes(r *router.Router, middlewares ...schemas
r.DELETE("/api/mcp-logs", lib.ChainMiddlewares(h.deleteMCPLogs, middlewares...))
}
+// listUserAgentMappings returns configured client identification mappings.
func (h *LoggingHandler) listUserAgentMappings(ctx *fasthttp.RequestCtx) {
mappings, err := h.logManager.ListUserAgentMappings(ctx)
if err != nil {
@@ -422,6 +427,7 @@ func (h *LoggingHandler) listUserAgentMappings(ctx *fasthttp.RequestCtx) {
SendJSON(ctx, map[string]any{"mappings": mappings})
}
+// createUserAgentMapping validates and creates a client identification mapping.
func (h *LoggingHandler) createUserAgentMapping(ctx *fasthttp.RequestCtx) {
var mapping logstore.UserAgentMapping
if err := sonic.Unmarshal(ctx.PostBody(), &mapping); err != nil {
@@ -440,6 +446,7 @@ func (h *LoggingHandler) createUserAgentMapping(ctx *fasthttp.RequestCtx) {
SendJSON(ctx, created)
}
+// updateUserAgentMapping updates an identified client mapping after request validation.
func (h *LoggingHandler) updateUserAgentMapping(ctx *fasthttp.RequestCtx) {
id, ok := ctx.UserValue("id").(string)
if !ok || strings.TrimSpace(id) == "" {
@@ -467,6 +474,7 @@ func (h *LoggingHandler) updateUserAgentMapping(ctx *fasthttp.RequestCtx) {
SendJSON(ctx, updated)
}
+// deleteUserAgentMapping deletes an identified client mapping and reports missing records.
func (h *LoggingHandler) deleteUserAgentMapping(ctx *fasthttp.RequestCtx) {
id, ok := ctx.UserValue("id").(string)
if !ok || strings.TrimSpace(id) == "" {
@@ -1538,6 +1546,7 @@ func (h *LoggingHandler) getModelRankings(ctx *fasthttp.RequestCtx) {
SendJSON(ctx, result)
}
+// getDimensionRankings validates and serves rankings for the requested attribution dimension.
func (h *LoggingHandler) getDimensionRankings(ctx *fasthttp.RequestCtx) {
dim := logstore.RankingDimension(string(ctx.QueryArgs().Peek("dimension")))
if dim == "" {
@@ -2420,6 +2429,7 @@ func recalcJobStatusFromRow(job *tables.TableSidekiqJob) recalcJobStatus {
// Helper functions
+// findRedactedKey matches a redacted provider key or returns a deleted-key placeholder.
func findRedactedKey(redactedKeys []schemas.Key, id string, name string) *schemas.Key {
if len(redactedKeys) == 0 {
return &schemas.Key{
@@ -2450,6 +2460,7 @@ func findRedactedKey(redactedKeys []schemas.Key, id string, name string) *schema
}
}
+// findRedactedVirtualKey matches a redacted virtual key or returns a deleted-key placeholder.
func findRedactedVirtualKey(redactedVirtualKeys []tables.TableVirtualKey, id string, name string) *tables.TableVirtualKey {
if len(redactedVirtualKeys) == 0 {
return &tables.TableVirtualKey{
@@ -2480,6 +2491,7 @@ func findRedactedVirtualKey(redactedVirtualKeys []tables.TableVirtualKey, id str
}
}
+// findRedactedRoutingRule matches a redacted routing rule or returns a deleted-rule placeholder.
func findRedactedRoutingRule(redactedRoutingRules []tables.TableRoutingRule, id string, name string) *tables.TableRoutingRule {
if len(redactedRoutingRules) == 0 {
return &tables.TableRoutingRule{
@@ -2585,6 +2597,13 @@ type recalculateCostFilters struct {
// Returns an error if any required parsing fails (e.g., invalid time format, invalid number format).
func parseMCPFiltersAndPagination(ctx *fasthttp.RequestCtx) (*logstore.MCPToolLogSearchFilters, *logstore.PaginationOptions, error) {
filters := &logstore.MCPToolLogSearchFilters{}
+ filters.UserIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("user_ids")))
+ filters.TeamIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("team_ids")))
+ filters.CustomerIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("customer_ids")))
+ filters.BusinessUnitIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("business_unit_ids")))
+ filters.ProjectIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("project_ids")))
+ filters.DeviceIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("device_ids")))
+
pagination := &logstore.PaginationOptions{}
// Extract filters from query parameters
@@ -2712,6 +2731,12 @@ func parseMCPFiltersAndPagination(ctx *fasthttp.RequestCtx) (*logstore.MCPToolLo
// Returns an error if any required parsing fails.
func parseMCPFilters(ctx *fasthttp.RequestCtx) (*logstore.MCPToolLogSearchFilters, error) {
filters := &logstore.MCPToolLogSearchFilters{}
+ filters.UserIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("user_ids")))
+ filters.TeamIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("team_ids")))
+ filters.CustomerIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("customer_ids")))
+ filters.BusinessUnitIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("business_unit_ids")))
+ filters.ProjectIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("project_ids")))
+ filters.DeviceIDs = parseCommaSeparated(string(ctx.QueryArgs().Peek("device_ids")))
// Extract filters from query parameters
if toolNames := string(ctx.QueryArgs().Peek("tool_names")); toolNames != "" {
diff --git a/transports/bifrost-http/handlers/logging_test.go b/transports/bifrost-http/handlers/logging_test.go
index 0e14b4b6bf8..8d758d99d03 100644
--- a/transports/bifrost-http/handlers/logging_test.go
+++ b/transports/bifrost-http/handlers/logging_test.go
@@ -274,6 +274,7 @@ func TestFilterDataCacheIdentity_PartitionsPerCaller(t *testing.T) {
}
}
+// TestGetDashboard verifies get dashboard.
func TestGetDashboard(t *testing.T) {
tests := []struct {
name string
@@ -378,6 +379,7 @@ func TestGetDashboard(t *testing.T) {
}
}
+// TestRecalculateLogCostsResolvesPeriodFilter verifies recalculate log costs resolves period filter.
func TestRecalculateLogCostsResolvesPeriodFilter(t *testing.T) {
SetLogger(&mockLogger{})
@@ -415,6 +417,7 @@ func TestRecalculateLogCostsResolvesPeriodFilter(t *testing.T) {
}
}
+// TestRecalculateLogCostsRejectsDuplicateJob verifies recalculate log costs rejects duplicate job.
func TestRecalculateLogCostsRejectsDuplicateJob(t *testing.T) {
SetLogger(&mockLogger{})
@@ -450,6 +453,7 @@ func TestRecalculateLogCostsRejectsDuplicateJob(t *testing.T) {
}
}
+// TestCancelRecalculateCost verifies cancel recalculate cost.
func TestCancelRecalculateCost(t *testing.T) {
SetLogger(&mockLogger{})
@@ -577,16 +581,19 @@ type fakeSidekiqStore struct {
inFlight *tables.TableSidekiqJob
}
+// newFakeSidekiqStore verifies new fake sidekiq store.
func newFakeSidekiqStore() *fakeSidekiqStore {
return &fakeSidekiqStore{jobs: make(map[string]*tables.TableSidekiqJob)}
}
+// createdCount implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) createdCount() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.created
}
+// CreateSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) CreateSidekiqJob(ctx context.Context, job *tables.TableSidekiqJob) error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -596,6 +603,7 @@ func (s *fakeSidekiqStore) CreateSidekiqJob(ctx context.Context, job *tables.Tab
return nil
}
+// GetSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) GetSidekiqJob(ctx context.Context, id string) (*tables.TableSidekiqJob, error) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -606,6 +614,7 @@ func (s *fakeSidekiqStore) GetSidekiqJob(ctx context.Context, id string) (*table
return nil, nil
}
+// GetInFlightSidekiqJobByKind implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) GetInFlightSidekiqJobByKind(ctx context.Context, kind string) (*tables.TableSidekiqJob, error) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -616,27 +625,42 @@ func (s *fakeSidekiqStore) GetInFlightSidekiqJobByKind(ctx context.Context, kind
return nil, nil
}
+// ClaimSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) ClaimSidekiqJob(ctx context.Context, id, runnerID string, staleBefore time.Time) (bool, error) {
return true, nil
}
+
+// ClaimPartitionedSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) ClaimPartitionedSidekiqJob(ctx context.Context, id, runnerID string, staleBefore time.Time, partitioningKey string, createdAt time.Time) (bool, error) {
return true, nil
}
+
+// HeartbeatSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) HeartbeatSidekiqJob(ctx context.Context, id, runnerID string) (bool, error) {
return true, nil
}
+
+// UpdateSidekiqJobProgress implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) UpdateSidekiqJobProgress(ctx context.Context, id, runnerID, metadata string) error {
return nil
}
+
+// CompleteSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) CompleteSidekiqJob(ctx context.Context, id, runnerID, metadata string) error {
return nil
}
+
+// FailSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) FailSidekiqJob(ctx context.Context, id, runnerID, metadata, lastErr string) error {
return nil
}
+
+// ListClaimableSidekiqJobs implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) ListClaimableSidekiqJobs(ctx context.Context, staleBefore time.Time) ([]tables.TableSidekiqJob, error) {
return nil, nil
}
+
+// CancelSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) CancelSidekiqJob(ctx context.Context, id string) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -650,6 +674,8 @@ func (s *fakeSidekiqStore) CancelSidekiqJob(ctx context.Context, id string) (boo
}
return true, nil
}
+
+// FinalizeCancelledSidekiqJob implements the test double used by logging handler tests.
func (s *fakeSidekiqStore) FinalizeCancelledSidekiqJob(ctx context.Context, id, runnerID, metadata string) error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -676,19 +702,28 @@ type dashboardLogManager struct {
lastRecalculateContext chan context.Context
}
+// GetLog implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetLog(ctx context.Context, id string) (*logstore.Log, error) {
return nil, nil
}
+
+// Search implements the test double used by logging handler tests.
func (m *dashboardLogManager) Search(ctx context.Context, filters *logstore.SearchFilters, pagination *logstore.PaginationOptions) (*logstore.SearchResult, error) {
m.lastLLMFilters = *filters
return &logstore.SearchResult{}, nil
}
+
+// GetSessionLogs implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetSessionLogs(ctx context.Context, sessionID string, pagination *logstore.PaginationOptions) (*logstore.SessionDetailResult, error) {
return nil, nil
}
+
+// GetSessionSummary implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetSessionSummary(ctx context.Context, sessionID string) (*logstore.SessionSummaryResult, error) {
return nil, nil
}
+
+// GetStats implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetStats(ctx context.Context, filters *logstore.SearchFilters) (*logstore.SearchStats, error) {
m.lastLLMFilters = *filters
m.statsCalls = append(m.statsCalls, *filters)
@@ -700,100 +735,168 @@ func (m *dashboardLogManager) GetStats(ctx context.Context, filters *logstore.Se
}
return &logstore.SearchStats{}, nil
}
+
+// GetHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.HistogramResult, error) {
return &logstore.HistogramResult{}, nil
}
+
+// GetTokenHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.TokenHistogramResult, error) {
return &logstore.TokenHistogramResult{}, nil
}
+
+// GetCostHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.CostHistogramResult, error) {
return &logstore.CostHistogramResult{}, nil
}
+
+// GetModelHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetModelHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ModelHistogramResult, error) {
return &logstore.ModelHistogramResult{}, nil
}
+
+// GetLatencyHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.LatencyHistogramResult, error) {
return &logstore.LatencyHistogramResult{}, nil
}
+
+// GetProviderCostHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetProviderCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderCostHistogramResult, error) {
return &logstore.ProviderCostHistogramResult{}, nil
}
+
+// GetProviderTokenHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetProviderTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderTokenHistogramResult, error) {
return &logstore.ProviderTokenHistogramResult{}, nil
}
+
+// GetProviderLatencyHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetProviderLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderLatencyHistogramResult, error) {
return &logstore.ProviderLatencyHistogramResult{}, nil
}
+
+// GetThroughputHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetThroughputHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ThroughputHistogramResult, error) {
return &logstore.ThroughputHistogramResult{}, nil
}
+
+// GetProviderThroughputHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetProviderThroughputHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderThroughputHistogramResult, error) {
return &logstore.ProviderThroughputHistogramResult{}, nil
}
+
+// GetModelRankings implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetModelRankings(ctx context.Context, filters *logstore.SearchFilters) (*logstore.ModelRankingResult, error) {
return &logstore.ModelRankingResult{}, nil
}
+
+// GetDimensionRankings implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetDimensionRankings(ctx context.Context, filters *logstore.SearchFilters, dimension logstore.RankingDimension) (*logstore.DimensionRankingResult, error) {
return &logstore.DimensionRankingResult{Dimension: dimension}, nil
}
+
+// GetDroppedRequests implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetDroppedRequests(ctx context.Context) int64 { return 0 }
+
+// GetAvailableModels implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableModels(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableAliases implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableAliases(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableSelectedKeys implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableSelectedKeys(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableVirtualKeys implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableVirtualKeys(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableRoutingRules implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableRoutingRules(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableRoutingEngines implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableRoutingEngines(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableStopReasons implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableStopReasons(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableToolCallNames implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableToolCallNames(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableTeams implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableTeams(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableCustomers implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableCustomers(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableUsers implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableUsers(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableBusinessUnits implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableBusinessUnits(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetAvailableProjects implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableProjects(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return m.projects, nil
}
+
+// GetAvailableMetadataKeys implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) {
return nil, nil
}
+
+// GetDimensionCostHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetDimensionCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionCostHistogramResult, error) {
return nil, nil
}
+
+// GetDimensionTokenHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetDimensionTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionTokenHistogramResult, error) {
return nil, nil
}
+
+// GetDimensionLatencyHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetDimensionLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionLatencyHistogramResult, error) {
return nil, nil
}
-func (m *dashboardLogManager) DeleteLog(ctx context.Context, id string) error { return nil }
+
+// DeleteLog implements the test double used by logging handler tests.
+func (m *dashboardLogManager) DeleteLog(ctx context.Context, id string) error { return nil }
+
+// DeleteLogs implements the test double used by logging handler tests.
func (m *dashboardLogManager) DeleteLogs(ctx context.Context, ids []string) error { return nil }
+
+// RecalculateCosts implements the test double used by logging handler tests.
func (m *dashboardLogManager) RecalculateCosts(ctx context.Context, filters *logstore.SearchFilters, limit int) (*loggingplugin.RecalculateCostResult, error) {
m.lastRecalculateFilters = *filters
return &loggingplugin.RecalculateCostResult{}, nil
}
+
+// RecalculateCostsWithProgress implements the test double used by logging handler tests.
func (m *dashboardLogManager) RecalculateCostsWithProgress(ctx context.Context, filters *logstore.SearchFilters, limit int, progress func(loggingplugin.RecalculateCostProgress)) (*loggingplugin.RecalculateCostResult, error) {
m.lastRecalculateFilters = *filters
if m.lastRecalculateContext != nil {
@@ -801,13 +904,19 @@ func (m *dashboardLogManager) RecalculateCostsWithProgress(ctx context.Context,
}
return nil, nil
}
+
+// BuildCostRecalcJobMeta implements the test double used by logging handler tests.
func (m *dashboardLogManager) BuildCostRecalcJobMeta(ctx context.Context, filters logstore.SearchFilters, missingCostOnly bool) (string, error) {
m.lastRecalculateFilters = filters
return "{}", nil
}
+
+// RunCostRecalcJob implements the test double used by logging handler tests.
func (m *dashboardLogManager) RunCostRecalcJob(ctx context.Context, metaJSON string, checkpoint func(string) error) (string, error) {
return metaJSON, nil
}
+
+// GetMCPToolLog implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetMCPToolLog(ctx context.Context, id string) (*logstore.MCPToolLog, error) {
if m.mcpLog == nil {
return nil, nil
@@ -815,32 +924,49 @@ func (m *dashboardLogManager) GetMCPToolLog(ctx context.Context, id string) (*lo
entry := *m.mcpLog
return &entry, nil
}
+
+// SearchMCPToolLogs implements the test double used by logging handler tests.
func (m *dashboardLogManager) SearchMCPToolLogs(ctx context.Context, filters *logstore.MCPToolLogSearchFilters, pagination *logstore.PaginationOptions) (*logstore.MCPToolLogSearchResult, error) {
return nil, nil
}
+
+// GetMCPToolLogStats implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetMCPToolLogStats(ctx context.Context, filters *logstore.MCPToolLogSearchFilters) (*logstore.MCPToolLogStats, error) {
return nil, nil
}
+
+// GetAvailableToolNames implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableServerLabels implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) {
return nil, nil
}
+
+// GetAvailableMCPVirtualKeys implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) ([]loggingplugin.KeyPair, error) {
return nil, nil
}
+
+// GetMCPHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetMCPHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPHistogramResult, error) {
m.lastMCPFilters = filters
return &logstore.MCPHistogramResult{}, nil
}
+
+// GetMCPCostHistogram implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetMCPCostHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPCostHistogramResult, error) {
return &logstore.MCPCostHistogramResult{}, nil
}
+
+// GetMCPTopTools implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetMCPTopTools(ctx context.Context, filters logstore.MCPToolLogSearchFilters, limit int) (*logstore.MCPTopToolsResult, error) {
return &logstore.MCPTopToolsResult{}, nil
}
+// DeleteMCPToolLogs implements the test double used by logging handler tests.
func (m *dashboardLogManager) DeleteMCPToolLogs(ctx context.Context, ids []string) error { return nil }
// staticMCPLogRedactionResolver records calls and returns a configured reveal result.
@@ -856,34 +982,42 @@ func (r *staticMCPLogRedactionResolver) ResolveMCPLogRedactionMapping(_ *fasthtt
return r.mapping, r.err
}
+// CreateUserAgentMapping implements the test double used by logging handler tests.
func (m *dashboardLogManager) CreateUserAgentMapping(ctx context.Context, mapping *logstore.UserAgentMapping) (*logstore.UserAgentMapping, error) {
return nil, nil
}
+// DeleteUserAgentMapping implements the test double used by logging handler tests.
func (m *dashboardLogManager) DeleteUserAgentMapping(ctx context.Context, id string) error {
return nil
}
+// UpdateUserAgentMapping implements the test double used by logging handler tests.
func (m *dashboardLogManager) UpdateUserAgentMapping(ctx context.Context, id string, mapping *logstore.UserAgentMapping) (*logstore.UserAgentMapping, error) {
return nil, nil
}
+// ListUserAgentMappings implements the test double used by logging handler tests.
func (m *dashboardLogManager) ListUserAgentMappings(ctx context.Context) ([]logstore.UserAgentMapping, error) {
return nil, nil
}
+// GetAvailableUserAgents implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableUserAgents(ctx context.Context, _ int, _ string) ([]string, error) {
return nil, nil
}
+// GetAvailableApps implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableApps(ctx context.Context, _ int, _ string) ([]string, error) {
return nil, nil
}
+// GetAvailableMCPApps implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableMCPApps(ctx context.Context, _ int, _ string) ([]string, error) {
return nil, nil
}
+// GetAvailableMCPUserAgents implements the test double used by logging handler tests.
func (m *dashboardLogManager) GetAvailableMCPUserAgents(ctx context.Context, _ int, _ string) ([]string, error) {
return nil, nil
}
@@ -892,10 +1026,15 @@ func (m *dashboardLogManager) GetAvailableMCPUserAgents(ctx context.Context, _ i
// search result produces.
type noRedactedKeys struct{}
+// GetAllRedactedKeys implements the test double used by logging handler tests.
func (noRedactedKeys) GetAllRedactedKeys(ctx context.Context, ids []string) []schemas.Key { return nil }
+
+// GetAllRedactedVirtualKeys implements the test double used by logging handler tests.
func (noRedactedKeys) GetAllRedactedVirtualKeys(ctx context.Context, ids []string) []tables.TableVirtualKey {
return nil
}
+
+// GetAllRedactedRoutingRules implements the test double used by logging handler tests.
func (noRedactedKeys) GetAllRedactedRoutingRules(ctx context.Context, ids []string) []tables.TableRoutingRule {
return nil
}
@@ -962,3 +1101,26 @@ func TestFilterDataListsProjects(t *testing.T) {
t.Fatalf("expected the project pair under \"projects\", got %s", ctx.Response.Body())
}
}
+
+// TestMCPAttributionFilterParsing keeps detail-link filters identical across list and analytics endpoints.
+func TestMCPAttributionFilterParsing(t *testing.T) {
+ var ctx fasthttp.RequestCtx
+ ctx.Request.SetRequestURI("/api/mcp-logs?user_ids=u1,u2&team_ids=t1&customer_ids=c1&business_unit_ids=b1&project_ids=p1&device_ids=d1")
+ list, _, err := parseMCPFiltersAndPagination(&ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ stats, err := parseMCPFilters(&ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ histogram, err := parseMCPHistogramFilters(&ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, filters := range []*logstore.MCPToolLogSearchFilters{list, stats, histogram} {
+ if !reflect.DeepEqual(filters.UserIDs, []string{"u1", "u2"}) || !reflect.DeepEqual(filters.TeamIDs, []string{"t1"}) || !reflect.DeepEqual(filters.CustomerIDs, []string{"c1"}) || !reflect.DeepEqual(filters.BusinessUnitIDs, []string{"b1"}) || !reflect.DeepEqual(filters.ProjectIDs, []string{"p1"}) || !reflect.DeepEqual(filters.DeviceIDs, []string{"d1"}) {
+ t.Fatalf("lost attribution filters: %+v", filters)
+ }
+ }
+}
diff --git a/ui/app/_fallbacks/enterprise/lib/constants/edgeApps.tsx b/ui/app/_fallbacks/enterprise/lib/constants/edgeApps.tsx
new file mode 100644
index 00000000000..03294312838
--- /dev/null
+++ b/ui/app/_fallbacks/enterprise/lib/constants/edgeApps.tsx
@@ -0,0 +1,7 @@
+import { MonitorSmartphone } from "lucide-react";
+
+// OSS stub for the Edge OS icon - there are no platform icons in OSS, so
+// always render the generic device glyph.
+export function OsIcon({ className }: { platform?: string; className?: string }) {
+ return
Request ID: {displayLog.id}
} -