linter#3555
Conversation
…filter inaccessible sidebar items (#3295) This PR improves RBAC granularity in the sidebar by introducing dedicated resource types for `APIKeys`, `Inference`, and `Metrics`, and fixes sidebar visibility logic so that items and groups are hidden when the user lacks access rather than relying on broader, less specific permissions. - Added three new `RbacResource` enum values: `APIKeys`, `Inference`, and `Metrics` to the fallback RBAC context. - The API Keys sidebar item now gates access via the new `hasAPIKeyAccess` (`RbacResource.APIKeys`) check instead of the generic `hasSettingsAccess`. - The MCP Logs sidebar item now correctly gates access via `hasMCPGatewayAccess` instead of the unrelated `hasLogsAccess`. - Introduced an `accessibleItems` memoized computation that filters out sidebar items and entire groups whose sub-items are all inaccessible, ensuring users never see empty navigation sections. Previously, access filtering only happened during search. - Removed unused imports (`PanelLeft`, `PanelRight`, `cn`). - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs 1. Log in as a user with restricted RBAC permissions that exclude `APIKeys` and/or `Settings`. 2. Verify the API Keys entry under the Config section is hidden for users without `APIKeys` view permission. 3. Verify the MCP Logs entry is hidden for users without `MCPGateway` view permission. 4. Verify that sidebar groups with no accessible sub-items are hidden entirely rather than showing an empty group. 5. Verify that users with full access see no change in sidebar behavior. ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` _Add before/after screenshots showing sidebar items hidden for restricted users._ - [ ] Yes - [x] No _Link related issues here._ Access control checks for API Keys management are now scoped to a dedicated `APIKeys` RBAC resource rather than the broader `Settings` resource, reducing the risk of unintended access to key management for users who have settings visibility but should not manage API keys. - [ ] 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
…elete access (#3314) The delete button in log tables was always rendered (just disabled) for users without delete access. This PR hides the actions column entirely when the user lacks delete permissions, and fixes the RBAC resource check for MCP logs to use the correct `MCPGateway` resource instead of `Logs`. - The actions column in both the workspace logs and MCP logs tables is now conditionally included in the column definitions only when `hasDeleteAccess` is `true`, rather than always rendering a disabled button. - The delete button styling was updated to use more visible destructive colors (`text-destructive/60 border-destructive/60`) instead of the previous muted secondary foreground styles. - The RBAC resource used to gate delete access on the MCP logs page was corrected from `RbacResource.Logs` to `RbacResource.MCPGateway`. - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs 1. Log in as a user **without** delete access on Logs or MCPGateway resources. 2. Navigate to the workspace logs page and the MCP logs page. 3. Verify the delete button/column is not visible. 4. Log in as a user **with** delete access. 5. Verify the delete button appears and is functional. ```sh cd ui pnpm i pnpm test pnpm build ``` Before: Delete button rendered but disabled for users without access. After: Delete column is hidden entirely for users without delete access. - [ ] Yes - [x] No The RBAC fix ensures MCP log deletion is gated on the correct `MCPGateway` resource permission, preventing users with only `Logs` delete access from incorrectly being granted delete access to MCP logs. - [ ] 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
…ogs route and sidebar (#3316) Introduces a dedicated `MCPLogs` RBAC resource, decoupling MCP log access control from the `MCPGateway` resource. This allows permissions for viewing and deleting MCP logs to be managed independently from gateway-level permissions. - Added `MCPLogs` as a new `RbacResource` enum value in the fallback RBAC context. - The MCP Logs route now checks `MCPLogs` view permission and renders a `NoPermissionView` when access is denied, rather than rendering the page unconditionally. - Delete access on the MCP Logs page now checks `RbacResource.MCPLogs` instead of `RbacResource.MCPGateway`. - The sidebar MCP Logs entry now uses `hasMCPLogsAccess` (derived from `RbacResource.MCPLogs`) to control visibility, rather than reusing `hasMCPGatewayAccess`. - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs 1. Configure a role that has `MCPGateway` access but **no** `MCPLogs` access. 2. Log in as a user with that role and navigate to the MCP Logs page — the `NoPermissionView` should be displayed and the sidebar entry should be hidden. 3. Grant the role `MCPLogs` view access and confirm the page and sidebar entry become accessible. 4. Verify that delete functionality on the MCP Logs page is gated by `MCPLogs` delete permission independently of `MCPGateway` delete permission. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` N/A - [x] Yes - [ ] No Any role configuration that previously relied on `MCPGateway` permissions to grant access to MCP Logs will need to be updated to explicitly grant `MCPLogs` permissions. N/A Access to MCP log data (which may contain sensitive tool execution details) is now enforced by a dedicated RBAC resource, reducing the risk of unintended access through overly broad `MCPGateway` permissions. - [ ] 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
…SQL helper to prevent malformed JSON from aborting list queries (#3407) ## Summary The `/api/logs` list query was aborting entirely when a single row contained malformed JSON in `input_history` or `responses_input_history`. The previous inline guard only checked the first character before casting to `jsonb`, so rows that appeared array-shaped but contained malformed JSON (unterminated structures, trailing commas, unpaired UTF-16 surrogates, `\u0000` escapes, etc.) would trigger a `22P02`/`22P05` error and kill the entire response. This PR fixes that by introducing a PL/pgSQL helper function (`bifrost_safe_jsonb`) that wraps the cast in an `EXCEPTION` block and falls back to returning the raw text on any parse failure. ## Changes - Added a new migration `migrationAddSafeJsonbFunction` that installs the `bifrost_safe_jsonb(text)` PL/pgSQL function on Postgres. The function validates the input, attempts the `jsonb` cast inside an `EXCEPTION` block, and returns the last array element on success or the raw text on any failure. - Replaced the multi-condition inline `CASE` guards in `listSelectColumns` for Postgres with calls to `bifrost_safe_jsonb`, simplifying the SQL and correctly handling all malformed-JSON edge cases that the previous character-check approach missed. - For SQLite, added `json_valid()`, `json_type()`, and `json_array_length()` guards to the `CASE` expressions to prevent extraction attempts on invalid or empty arrays. - Added `safe_jsonb_test.go` covering both the SQLite and Postgres dialect branches of `listSelectColumns`, as well as direct invocation of `bifrost_safe_jsonb` across all relevant edge cases (malformed structures, surrogate pairs, `\u0000` escapes, non-array values, SQL `NULL`). ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh cd framework && docker compose up -d postgres go test ./framework/logstore/ -run 'MalformedInputHistory|BifrostSafeJsonb' -count=1 -v ``` Insert a row into the logs table with a malformed JSON value in `input_history` (e.g., `[{"key": "val"` — unterminated) and verify that a call to the list endpoint returns successfully without a 500 error, with the malformed row's `input_history` returned as raw text rather than aborting the query. ## Test Coverage ### `TestSearchLogs_MalformedInputHistory_{SQLite,Postgres}` — end-to-end list query | # | Case | Column | Payload shape | Pre-fix behavior | Path exercised | | --- | --- | --- | --- | --- | --- | | 1 | `unterminated_object_in_array` | `input_history` | `[{"role":"user","content":"hi"` | 22P02 aborts query | EXCEPTION fallback | | 2 | `garbage_after_bracket` | `input_history` | `[abc, not json]` | 22P02 aborts query | EXCEPTION fallback | | 3 | `trailing_comma` | `input_history` | `[{"role":"user","content":"hi"},]` | 22P02 aborts query | EXCEPTION fallback | | 4 | `unclosed_array_only` | `input_history` | `[` | 22P02 aborts query | EXCEPTION fallback | | 5 | `open_bracket_then_brace_unclosed` | `input_history` | `[{` | 22P02 aborts query | EXCEPTION fallback | | 6 | `nan_value_not_valid_json` | `input_history` | `[NaN]` | 22P02 aborts query | EXCEPTION fallback | | 7 | `infinity_value_not_valid_json` | `input_history` | `[Infinity]` | 22P02 aborts query | EXCEPTION fallback | | 8 | `unpaired_high_surrogate` | `input_history` | `[{"...":"bad \uD800 surrogate"}]` | 22P05 aborts query | EXCEPTION fallback | | 9 | `unpaired_low_surrogate` | `input_history` | `[{"...":"bad \uDC00 low"}]` | 22P05 aborts query | EXCEPTION fallback | | 10 | `bad_surrogate_pair_high_then_ascii` | `input_history` | `[{"c":"\uD800A"}]` | 22P05 aborts query | EXCEPTION fallback | | 11 | `u0000_escape_inside_string` | `input_history` | `[{"...":"null byte � here"}]` | 22P05 aborts query | EXCEPTION fallback | | 12 | `literal_backslash_u0000_valid_jsonb` | `input_history` | `[{"...":"... \\u0000 literal"}]` | OK (degraded to raw by old guard) | Fast path, last-element extraction | | 13 | `single_element_array` | `input_history` | `[{"role":"user","content":"only one"}]` | OK | Fast path | | 14 | `array_of_primitives` | `input_history` | `[1,2,3]` | OK | Fast path | | 15 | `array_with_null_last_element` | `input_history` | `[{...}, null]` | OK | Fast path | | 16 | `deeply_nested_valid` | `input_history` | `[{"role":"user","content":{"nested":{"deep":{"value":42}}}}]` | OK | Fast path | | 17 | `unicode_emoji_content` | `input_history` | `[{"...":"hello 🎉 world ✨"}]` | OK | Fast path | | 18 | `large_valid_array` | `input_history` | 1001-element array | OK | Fast path at scale | | 19 | `leading_whitespace_then_array` | `input_history` | ` [\t{...}]` | OK | `btrim` + fast path | | 20 | `top_level_object_not_array` | `input_history` | `{"not":"an array"}` | OK | Non-array fall-through | | 21 | `null_literal` | `input_history` | `null` | OK | Non-array fall-through | | 22 | `whitespace_only` | `input_history` | `" \t "` | OK | Empty-after-btrim fall-through | | 23 | `realtime_turn_malformed_passthrough` | `input_history` (object_type=`realtime.turn`) | `[{"role":"user"` | OK (outer CASE bypassed safe fn) | Realtime-turn bypass branch | | 24 | `malformed_responses_input_history` | `responses_input_history` | `[{"role":"user"` | 22P02 aborts query | Mirror column, EXCEPTION fallback | | 25 | `valid_responses_input_history` | `responses_input_history` | `[{...},{...}]` | OK | Mirror column, fast path | ## Breaking changes - [ ] Yes - [x] No ## Related issues [https://github.com/maximhq/bifrost/issues/3255](https://github.com/maximhq/bifrost/issues/3255#issuecomment-4427506449) ## Security considerations None. The function is `IMMUTABLE` and operates only on text values already stored in the database. No new inputs are exposed. ## 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
…3412) ## Summary Adds support for server-configured required request headers in the prompt playground. When the server specifies `required_headers` in its client config, users can now provide values for those headers directly in the settings panel, and they are forwarded with every chat completion request. ## Changes - Added `customHeaders` state and `requiredHeaders` derived from the core config's `client_config.required_headers` to the `PromptContext`, keeping header keys in sync with the server config while preserving user-entered values. - Exposed a "Required Headers" section in the settings panel that renders an input field for each required header name when any are configured. - Extended `ExecutionConfig` in the executor to accept `customHeaders`, which are merged into the fetch request headers (skipping any entries with empty names or values). - Passed `customHeaders` through both `handleSubmit` and `handleSubmitToolResult` execution paths and included it in their respective `useCallback` dependency arrays. ## 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. Configure `required_headers` in the server's client config (e.g., `["X-My-Custom-Header"]`). 2. Open the prompt playground and navigate to the settings panel. 3. Verify a "Required Headers" section appears with an input for each configured header name. 4. Enter a value for each header and send a chat completion request. 5. Confirm the header is present in the outgoing request. 6. Remove a header from the server config and verify it disappears from the UI without affecting other header values. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings _Add before/after screenshots of the settings panel showing the new Required Headers section._ ## Breaking changes - [x] No ## Related issues ## Security considerations Header values are entered by the user and sent only to the configured backend endpoint. Empty header names or values are explicitly skipped before being added to the request, preventing accidental forwarding of blank headers. Users should be cautious not to enter sensitive credentials unless the connection to the server is secured. ## 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
## Summary When exporting virtual keys, pagination clamping was being applied unnecessarily, which could interfere with retrieving the full dataset. This PR skips the pagination limit/offset clamping when the export flag is set, while still ensuring the offset is non-negative. ## Changes - Pagination clamping via `ClampPaginationParams` is now bypassed when `params.Export` is `true`, allowing exports to retrieve data without artificially constrained limits - A minimal guard ensures `params.Offset` is still set to `0` if negative during an export request ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Trigger a virtual keys export request and verify that all keys are returned without being truncated by pagination limits. Compare the export result count against the total number of virtual keys in the system. ```sh go test ./... ``` ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues #3414 ## Security considerations No additional security implications. Export access is still gated by existing authentication and authorization checks. ## 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
## Summary Bumps the `@maximhq/bifrost` NPX package version to `1.6.3` to align the `package.json` and `package-lock.json` version fields, which were previously out of sync. ## Changes - Updated `package.json` version from `1.6.2` to `1.6.3` - Corrected `package-lock.json` to reflect `1.6.3` consistently across both the lockfile root and the package entry (previously mismatched at `1.0.6` and `1.0.4`) ## 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 ```sh cd npx/bifrost npm install npm pack --dry-run # Verify the reported version is 1.6.3 ``` ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations No security implications. ## 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
… bar click suppression (#3431) Adds a log volume histogram chart to the MCP Logs page, matching the existing chart behavior on the main Logs page. Also fixes a bug where clicking a bar immediately after a drag-select would overwrite the dragged time range with a single-bucket zoom. - Added `useGetMCPHistogramQuery` to the MCP Logs page to fetch histogram data with optional polling, and rendered the `LogsVolumeChart` component in the MCP Logs view. - Added `handleTimeRangeChange`, `handleResetZoom`, and `isZoomed` logic to the MCP Logs page, mirroring the behavior already present on the main Logs page. - Fixed `isZoomed` on the main Logs page to return `false` when a named `period` (e.g. `"1h"`) is active, so resetting zoom correctly clears the zoomed state. - When resetting zoom, `period: "1h"` and `polling: true` are now explicitly set in URL state to ensure the page returns to a live-polling relative range. - Fixed a race condition in `LogsVolumeChart` where Recharts fires a Bar `onClick` event immediately after a drag-select `mouseUp`, which was overwriting the dragged range with a single-bucket zoom. A `suppressNextBarClickRef` ref is set during drag completion and cleared on the next bar click to suppress the spurious event. - [x] Bug fix - [x] Feature - [x] UI (React) 1. Navigate to the MCP Logs page and confirm the log volume histogram chart renders and updates with polling. 2. Click a bar in the histogram and confirm the time range zooms into that bucket. 3. Drag-select a range on the histogram and confirm the time range updates to the dragged selection without immediately snapping to a single bucket. 4. Click "Reset Zoom" and confirm the chart returns to the default 1-hour live-polling view. ```sh cd ui pnpm i pnpm build ``` Before: MCP Logs page had no histogram chart. After: MCP Logs page displays the log volume histogram with zoom, drag-select, and reset zoom functionality identical to the main Logs page. - [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
## Summary
This PR refactors the semantic cache plugin to simplify its internal state management, improves cache lookup correctness, and adds a new `cache_hit_types` filter to the logs API and UI. The direct cache lookup path is now a single deterministic point-fetch by a UUIDv5 `directCacheID` (replacing the previous dual-path of chunk lookup + legacy metadata scan), and several context keys are consolidated. The UI gains a "Local Caching" filter sidebar section and cache hit type badges in the log detail view.
## Changes
- **Semantic cache plugin refactor:**
- Replaced the dual direct-search path (`performDirectChunkLookup` + `performLegacyDirectSearch`) with a single `performDirectSearch` that does an O(1) `GetChunk` by deterministic `directCacheID` (UUIDv5 derived from provider, model, cacheKey, requestHash, paramsHash).
- `generateDirectCacheID` now returns an error instead of silently falling back to a string concatenation, making failures explicit.
- `request_hash` is no longer stored as a top-level metadata field; it is encoded into the `directCacheID` instead.
- Reduced context keys from ~10 to 4 (`directCacheIDKey`, `paramsHashKey`, `embeddingsKey`, `embeddingsInputTokensKey`), removing stale keys like `requestIDKey`, `requestHashKey`, `isCacheHitKey`, and `cacheHitTypeKey`.
- `shouldSkipCaching` is extracted into its own method; cache-hit detection now reads `CacheDebug.CacheHit` from the response rather than a context flag.
- `buildUnifiedMetadata` no longer accepts `requestHash` as a parameter.
- `addSingleResponse` renamed to `addNonStreamingResponse`.
- `StreamAccumulator` fields `HasError`, `FinalTimestamp`, and `FinishReason` on `StreamChunk` are removed; error streams are handled by early return in `PostLLMHook`.
- Streaming replay goroutine now guards every send with `ctx.Done()` to prevent goroutine leaks on dropped consumers.
- A background `runStreamCleanupLoop` goroutine (started by `Init`, stopped by `Cleanup` via `stopCh`) replaces the one-shot cleanup call, periodically reaping stale stream accumulators.
- `buildResponseFromResult` now accepts `threshold`, `similarity`, and `inputTokens` as pointers, and `attachCacheDebug` is extracted as a shared helper for both streaming and non-streaming paths.
- `isExpiredEntry` is extracted as a standalone function.
- `chunkSortKey` replaces the large inline sort comparator in `processAccumulatedStream`.
- Tools, stop sequences, modalities, include lists, and other order-insensitive set fields are now hashed with `hashSortedSet` / `sortedStringSet` to prevent MCP's randomized map iteration from perturbing the request hash.
- `extractAttachmentsForCaching` is extracted so attachment URLs are included in the cache key metadata rather than the embedding text.
- `extractTextForEmbedding` no longer returns a `paramsHash`; callers compute it once via `buildRequestMetadataForCaching` + `hashMap`.
- `generateEmbedding` moved from `utils.go` to `search.go`.
- `generateRequestHash` now accepts prebuilt metadata to avoid recomputing it.
- `removeField` no longer mutates the input slice's backing array.
- Added `PronunciationDictionaryLocators`, `TimestampGranularities`, `Include`, `AdditionalFormats`, and `InputImages` to their respective parameter metadata extractors.
- Public context key names changed from `semantic_cache_*` to `semantic_cache-*` (underscore → hyphen separator after the plugin prefix).
- `SelectFields` no longer includes `request_hash`.
- `VectorStoreProperties` no longer includes a `request_hash` entry.
- `CacheByModel` and `CacheByProvider` default-value log messages added.
- **Log filtering — `cache_hit_types`:**
- Added `CacheHitTypes []string` to `SearchFilters` in `framework/logstore/tables.go`.
- `applyFilters` in `rdb.go` applies a JSON path filter on `cache_debug` for both SQLite (`json_extract`) and PostgreSQL (`substring` regex) dialects, restricted to the allowlist `["direct", "semantic"]`.
- `canUseMatViewFilters` excludes queries with `CacheHitTypes` set from the materialized-view fast path.
- HTTP handlers (`getLogs`, `getLogsStats`, `parseHistogramFilters`) parse a `cache_hit_types` comma-separated query parameter.
- **UI:**
- Added a "Local Caching" filter section to `LogsFilterSidebar` with checkboxes for "Direct cache" and "Semantic cache".
- `cache_hit_types` is added to URL state, filter state, and the `buildFilterParams` API helper.
- Log detail view shows "Direct Cache" (indigo) and "Semantic Cache" (rose) badges based on `cache_debug.hit_type`.
- Plugins form now filters the provider dropdown to embedding-capable providers only (`EmbeddingSupportedProviders` for built-ins; `custom_provider_config.allowed_requests.embedding` for custom providers), shows an error message when no embedding provider is configured, and disables the toggle accordingly.
- Embedding model input replaced with `ModelMultiselect` (single-select mode) scoped to the selected provider.
- Provider dropdown clears the embedding model when the provider changes.
- Provider icons rendered in the provider dropdown.
- `EmbeddingSupportedProviders` constant added to `ui/lib/constants/logs.ts`.
- **Misc:**
- HTTP request logging in `CorsMiddleware` and an auth debug log are commented out.
- `transports/bifrost-http/v1.5.x` added to `.gitignore`.
- Minor formatting fixes in `core/schemas/bifrost.go` and `framework/modelcatalog/sync.go`.
- Missing newline at end of `sync.go` added.
## Type of change
- [ ] Bug fix
- [x] Feature
- [x] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [x] UI (React)
- [ ] Docs
## How to test
```sh
# Core/Transports
go test ./plugins/semanticcache/...
go test ./framework/logstore/...
go test ./transports/bifrost-http/...
# UI
cd ui
pnpm i
pnpm build
```
- Configure the semantic cache plugin with a direct and/or semantic cache type and verify that cache hits are recorded with the correct `hit_type` in `cache_debug`.
- Query `/logs?cache_hit_types=direct` and `/logs?cache_hit_types=semantic` and confirm only matching entries are returned.
- In the UI, open the logs filter sidebar and verify the "Local Caching" section appears with "Direct cache" and "Semantic cache" checkboxes that correctly filter the log list.
- Open a log detail for a cache hit and confirm the appropriate badge ("Direct Cache" or "Semantic Cache") is displayed.
- In the plugins form, verify that only embedding-capable providers appear in the provider dropdown and that the embedding model field uses the model multiselect.
## Breaking changes
- [x] Yes
The public semantic cache context key names have changed from `semantic_cache_*` to `semantic_cache-*`. Any caller setting `CacheKey`, `CacheTTLKey`, `CacheThresholdKey`, `CacheTypeKey`, or `CacheNoStoreKey` via the old string values will no longer be recognized by the plugin. Update all call sites to use the exported constants from the plugin package rather than raw string literals.
`request_hash` is no longer stored as a top-level metadata field in the vector store. Existing cache entries written by prior versions will not be found by the new direct-search path (they will be treated as misses and re-populated).
`ClearCacheForRequestID` is documented as currently broken for entries written by the new direct-search path; callers should not rely on it until the TODO is resolved.
## Related issues
N/A
## Security considerations
The `CacheHitTypes` filter allowlists values to `"direct"` and `"semantic"` before interpolating them into SQL, preventing arbitrary input from reaching the JSON path expression.
## 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
…3330) ## Summary Removes the `cleanup_on_shutdown` option from the semantic cache plugin. Cache data now always persists between Bifrost restarts. The previous behavior of deleting all cache entries and the vector store namespace on shutdown is no longer supported. ## Changes - Removed `CleanUpOnShutdown` field from `Config` struct in `plugins/semanticcache/main.go` and stripped the corresponding shutdown deletion logic from `Cleanup()` - Removed `cleanup_on_shutdown` from the JSON config schema (`transports/config.schema.json`), Helm values schema (`helm-charts/bifrost/values.schema.json`), Helm template helper (`_helpers.tpl`), and default `values.yaml` - Removed `cleanup_on_shutdown` from all example Kubernetes values files and documentation code samples - Added migration guide entry (Breaking Change 16) in `docs/migration-guides/v1.5.0.mdx` describing the removal, how to clear cache data using the existing invalidation endpoints, and how to handle dimension/provider/model rotation without the old escape hatch - Updated the semantic caching feature docs to remove references to `cleanup_on_shutdown` and the associated warning block - Removed `TestCleanup_DeletesEntriesAndNamespaceWhenEnabled` test and simplified `newTestPlugin` helper to drop the `cleanupOnShutdown` parameter across all test files ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [x] Docs ## How to test ```sh go test ./plugins/semanticcache/... ``` Verify that passing `cleanup_on_shutdown` in a semantic cache plugin config is rejected by schema validation. Confirm that restarting Bifrost with a semantic cache configured leaves existing vector store entries intact. ## Breaking changes - [x] Yes - [ ] No The `cleanup_on_shutdown` field is removed from the semantic cache plugin config schema and will be rejected by validation. Remove it from `config.json`, Helm values, and any `PUT /api/config` payloads. To clear cache data, use `DELETE /api/cache/clear/{cacheId}`, `DELETE /api/cache/clear-by-key/{cacheKey}`, or rotate `vector_store_namespace` to a fresh name. ## Related issues See Breaking Change 16 in the v1.5.0 migration guide. ## Security considerations None. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary Replaces the separate `PluginsForm` component with a fully self-contained `CachingView` that introduces a first-class **Direct / Direct + Semantic** mode toggle for the local cache plugin. Previously, the UI only exposed provider-backed semantic cache settings and had no concept of direct-only (hash-based) caching as a distinct, supported mode. This rewrite makes direct-only mode the default and gates semantic configuration behind an explicit mode selection. ## Changes - Deleted `pluginsForm.tsx` and consolidated all local cache configuration logic directly into `cachingView.tsx`. - Introduced a `CacheMode` type (`"direct"` | `"semantic"`) with a tab-based picker. Direct-only mode requires no embedding provider; semantic mode adds vector similarity on top and requires a provider, model, and dimension. - The enable/disable toggle now immediately calls `updatePlugin` or `createPlugin` (for first-time setup) rather than deferring the enabled-state change to the Save button, decoupling the plugin lifecycle from config edits. - Added `inferMode` to derive the active mode from a saved config, `isEmptyConfig` to detect zero-value configs from the API, `buildPayload` to strip semantic-only fields when persisting a direct-only config, and `validateForSave` for inline validation surfaced before the user clicks Save. - Structural change warnings (provider/model/dimension drift vs. server state) are now shown only when the user has actually modified those fields, rather than permanently in semantic mode. - Removed the Zod `cacheConfigSchema` validation path in favor of the new `validateForSave` function. - Removed the effect that auto-seeded a default provider/model/dimension on first load, since direct-only mode no longer requires those fields. - Per-request override documentation expanded to include `x-bf-cache-key` and `x-bf-cache-no-store` with clearer descriptions. ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` 1. Navigate to the Workspace → Config → Caching view. 2. Verify the page loads with **Direct only** selected by default and no provider/model/dimension fields visible. 3. Switch to **Direct + Semantic** and confirm provider, model, and dimension fields appear with inline validation. 4. Toggle caching on without a vector store configured and confirm the toggle is disabled. 5. Save a direct-only config and confirm the plugin is created/updated with `dimension: 1` and no provider fields. 6. Save a semantic config with a valid provider, model, and dimension and confirm the full payload is persisted. 7. Reload the page and confirm the saved mode and config are correctly hydrated. ## Screenshots/Recordings Before/after screenshots recommended showing the mode tab picker, the conditional semantic fields, and the structural change warning banner. ## Breaking changes - [x] Yes - [ ] No The `PluginsForm` component is removed. Any code importing it directly will need to be updated. The enable/disable toggle now persists immediately rather than requiring a Save click, which changes the interaction model for existing users. ## Related issues N/A ## Security considerations No new auth, secrets, or PII handling introduced. API keys for embedding providers continue to be inherited from the provider's existing configuration and are not re-entered or stored in the cache config. ## 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
…and plugin reloads (#3423) ## Summary The `CacheHandler` previously captured a reference to the `semantic_cache` plugin at boot time. This caused two bugs: (1) if the plugin was not present in `config.json` at startup, cache-clear routes were never registered, resulting in HTTP 405 for the entire process lifetime; (2) if the plugin was loaded or reloaded via `/api/plugins` after boot, the handler held a stale (or nil) pointer and would silently misbehave. Additionally, `GET /api/plugins/:name` was returning the raw plugin config without runtime status, causing the UI to see an empty status when refetching a single plugin. ## Changes - `CacheHandler` now accepts a `CacheClearerResolver` function instead of a concrete plugin pointer. The resolver is called on every cache-clear request, so plugin lifecycle changes via `/api/plugins` are always honored. - `CacheClearer` and `CacheClearerResolver` are exported so server wiring can supply the resolver without importing the plugin's concrete type. - Cache routes are registered unconditionally at startup. When no plugin is loaded, requests return HTTP 400 with a descriptive message instead of HTTP 405. - The server wiring in `RegisterAPIRoutes` uses a closure over `lib.FindPluginAs` to resolve the plugin per request, replacing the boot-time capture. - `getPlugin` now returns the same response shape as list/create/update (with runtime status merged in), fixing the empty status seen by `useGetPluginQuery` in the UI. - Tests cover the new "plugin not loaded" path for both `clearCache` and `clearCacheByKey`, and existing tests are updated to use the resolver-based constructor. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./transports/bifrost-http/handlers/... go test ./transports/bifrost-http/... ``` 1. Start the server **without** `semantic_cache` in `config.json`. Issue `DELETE /api/cache/clear/{cacheId}` — expect HTTP 400 with `"semantic_cache plugin is not loaded"` (previously HTTP 405). 2. Load the `semantic_cache` plugin via `POST /api/plugins`. Repeat the request — expect the cache-clear to succeed. 3. Reload or remove the plugin via `PUT`/`DELETE /api/plugins`. Verify the handler reflects the new state on the next request without a server restart. 4. Issue `GET /api/plugins/{name}` for a loaded plugin and confirm the response includes runtime status fields, matching the shape returned by the list endpoint. ## Breaking changes - [x] Yes - [ ] No `NewCacheHandler` now accepts a `CacheClearerResolver` function instead of a `schemas.LLMPlugin`. Any caller constructing a `CacheHandler` directly must be updated to pass a resolver. ## Related issues ## Security considerations None. The change does not affect authentication, 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
…rch paths in semantic cache (#3424) ## Summary Fixes several correctness issues in the semantic cache plugin's `PostLLMHook` and related helpers: cache telemetry (`cache_debug`) was previously invisible to callers using `no-store`, cache-hit replay detection was fragile, non-positive per-request TTL overrides could silently kill cache writes, and requests with a `cache_type` header narrowed to a path the plugin cannot serve would still produce orphan cache entries. ## Changes - **Early exit for unsupported search paths in `PreLLMHook`**: When `resolveCacheTypes` resolves to a path the plugin cannot actually serve (e.g. `x-bf-cache-type=semantic` against a direct-only plugin, or an unknown header value), the hook now clears cache state and returns early instead of proceeding to generate an embedding or write an orphan entry under a random request UUID that no future read can match. - **Separated cache-hit replay handling from write-skip logic**: The `shouldSkipCaching` method (which conflated cache-hit detection with write-skip conditions) is replaced by `shouldSkipCacheWrite`. Cache-hit replay is now handled as a dedicated early return in `PostLLMHook` before any telemetry stamping, while `shouldSkipCacheWrite` gates only the write decision after telemetry is already stamped. This ensures `cache_debug` is always populated for callers using `no-store` or large-payload modes. - **Telemetry stamped before write decision**: `stampCacheDebugForMiss` is now called before `shouldSkipCacheWrite` is consulted, so observability is not conditional on whether the entry is ultimately written. - **Non-positive TTL overrides fall back to plugin default**: `resolveTTL` now treats a zero or negative per-request TTL override as "use default" rather than applying it, which would have set `expires_at=now` and silently discarded the cache write. - **Cleaned up stale comments**: Removed an outdated ordering constraint comment in `PostLLMHook` that no longer applies after the restructuring. - **Tests updated**: Test cases for `shouldSkipCaching` are renamed and updated to reflect the new `shouldSkipCacheWrite` contract. The cache-hit replay test case is removed from this suite (it is now an early return in `PostLLMHook`, not a condition inside the helper). A new default-is-false test is added. ## Type of change - [x] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./plugins/semanticcache/... ``` Validate the following scenarios: - A request with `x-bf-cache-type=semantic` against a plugin configured with `Provider=""` or `Dimension=1` should log a warning and skip caching entirely — no orphan entry should appear in the store. - A request with `Cache-Control: no-store` should still produce a populated `cache_debug` field in the response with `cache_hit=false`. - A per-request TTL override of `0s` should fall back to the plugin's configured default TTL and not silently discard the cache write. ## Breaking changes - [x] No ## Security considerations None. ## 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
## Summary Adds a standalone end-to-end test suite for the `semantic_cache` plugin under `tests/semanticcache`. The suite validates the full caching lifecycle against a live Bifrost instance — plugin creation/teardown, cache miss/hit assertions, cross-provider behavior, streaming, and log cross-checking — without provisioning any infrastructure itself. ## Changes - **`e2e_test.go`** — `TestMain` entry point: loads config, initializes the report directory, checks Bifrost reachability, enforces plugin-absent precondition (with `RUN_FORCE=1` auto-delete), runs all phases, and performs best-effort teardown on exit. - **`preconditions_test.go`** — Phase 0 checks: Bifrost reachable, OpenAI configured, optional providers (Gemini, Anthropic) present with warnings if absent. - **`http_test.go`** — HTTP helpers for all request types: chat completions (streaming and non-streaming), text completions, embeddings, image generation, and the Responses API. Each helper dumps full request/response bodies to the report directory for forensics. - **`plugin_test.go`** — Plugin lifecycle helpers (`pluginCreate`, `pluginUpdate`, `pluginDelete`, `pluginGet`) mirroring the exact wire format the UI sends to `/api/plugins`. - **`assert_test.go`** — Assertion helpers (`assertMiss`, `assertHit`, `assertNoCacheDebug`, `assertSameCacheID`, `assertDifferentCacheID`) plus a configurable async write-settle wait (`SC_WRITE_SETTLE_MS`) to account for the plugin's async PostLLMHook store write. - **`cache_test.go`** — Cache management helpers (`clearByCacheID`, `clearByCacheKey`) wrapping the `/api/cache/clear/*` endpoints. - **`logs_crosscheck_test.go`** — Cross-checks the persisted log row's `cache_debug` against the in-flight response stamp, with polling to handle Bifrost's async logging pipeline and float epsilon tolerance for JSON encoder differences. - **`fixtures_test.go`** — Hand-curated paraphrase pairs for Phase 2 semantic cases, designed to land well above (canonical→paraphrase) or well below (canonical→unrelated) the default 0.8 similarity threshold. - **`log_test.go`** — Structured per-run logging to `reports/<UTC-timestamp>/run.log` with optional `TRAIL_SESSION_ID` stamping for trail integration. - **`go.mod`** — Standalone module (`github.com/maximhq/bifrost/tests/semanticcache`), consistent with the `tests/governance` pattern, excluded from the repo's `go.work`. - **`README.md`** — Documents prerequisites, env vars, run commands, trail integration, and report output format. - **`.gitignore`** — Excludes `reports/` and `*.log` from version control. Notable design decisions: the suite is intentionally verify-only (no infrastructure provisioning), uses a dedicated vector store namespace (`BifrostSemanticCachePluginE2E`) to isolate test data, and writes full wire-level request/response artifacts per step to support post-mortem debugging without re-running. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test Requires a running Bifrost instance with Weaviate configured, OpenAI (required), and optionally Gemini and Anthropic providers. ```sh cd tests/semanticcache # All phases GOWORK=off go test -v ./... # Single phase GOWORK=off go test -v -run TestPhase1_DirectOnly ./... # Auto-delete any pre-existing plugin row before run RUN_FORCE=1 GOWORK=off go test -v ./... # Keep plugin after run for post-mortem inspection RUN_KEEP_PLUGIN=1 GOWORK=off go test -v ./... ``` Environment variables: | Variable | Default | Purpose | |---|---|---| | `BIFROST_URL` | `http://localhost:8080` | Bifrost base URL | | `SC_CHAT_MODEL_OPENAI` | `openai/gpt-4o-mini` | OpenAI chat model | | `SC_CHAT_MODEL_OPENAI_ALT` | `openai/gpt-4o` | Alternate OpenAI model for cache-by-model cases | | `SC_EMBED_MODEL_OPENAI` | `text-embedding-3-small` | Embedding model for Phase 2 | | `SC_CHAT_MODEL_GEMINI` | `gemini/gemini-2.5-flash` | Gemini chat model | | `SC_CHAT_MODEL_ANTHROPIC` | `anthropic/claude-haiku-4-5` | Anthropic chat model | | `SC_NAMESPACE` | `BifrostSemanticCachePluginE2E` | Vector store namespace | | `SC_WRITE_SETTLE_MS` | `500` | Async write settle wait in ms | | `RUN_FORCE` | unset | `1` to delete pre-existing plugin before run | | `RUN_KEEP_PLUGIN` | unset | `1` to skip teardown on exit | | `TRAIL_SESSION_ID` | unset | Stamped onto every log line for trail integration | ## Screenshots/Recordings N/A ## Breaking changes - [x] No ## Related issues N/A ## Security considerations No secrets are stored in the test suite. API keys are consumed from the existing Bifrost provider configuration and never passed directly through the test harness. ## Checklist - [ ] 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
## Summary Adds a comprehensive end-to-end test suite (`TestDirect`) for the semantic cache plugin operating in direct-only mode. The suite covers 55 test cases (plan §1.1–1.55) validating cache hit/miss behavior, key isolation, TTL handling, config flag mutations, normalization, streaming, multi-endpoint support, parameter hashing, tool definitions, and cache management operations. ## Changes - Introduces `tests/semanticcache/direct_test.go` with `TestDirect`, covering: - **Basic hit/miss and key isolation** (1.1, 1.2, 1.3, 1.4) - **`cache_by_model` and `cache_by_provider` flag behavior** (1.5–1.8), including serial config-mutation cases that restore baseline via `t.Cleanup` - **`exclude_system_prompt` flag** (1.9, 1.10) - **Conversation threshold boundary conditions** (1.11, 1.12) - **TTL expiry, per-request TTL override, invalid TTL fallback, and zero/negative TTL fallback** (1.13, 1.14, 1.15, 1.54) - **`no-store` header semantics**, including case-sensitivity and explicit `false` value (1.16, 1.17, 1.45, 1.46) - **`cache-type` header behavior** in direct-only mode, including the `semantic` header bug case (1.18, 1.19) - **Streaming SSE**: hit/miss, chunk replay order, and non-final chunk cache_debug absence (1.24, 1.25, 1.47) - **Multi-endpoint coverage**: text completions, responses API, embeddings, and image generation (1.20–1.23) - **Input normalization**: case folding, whitespace trimming, Unicode, and large prompts (1.26–1.29) - **Image attachment hashing**: same URL hits, different URL misses (1.30, 1.31) - **Edge cases**: nil content messages, empty messages array, unknown cache ID deletion (1.42, 1.43, 1.40) - **Parameter hash isolation**: temperature, top_p, seed, max_tokens, top_logprobs, tools (order-independent and name-change), prompt_cache_key, service_tier, store flag (1.32–1.37, 1.48–1.52) - **Cache management**: clear by cache ID, clear by key (1.38, 1.39) - **Plugin status round-trip** via GET (1.44) - **`/api/logs` cross-check**: verifies persisted `cache_debug` matches in-flight response stamp (1.55) - **`responses` API `previous_response_id` isolation** (1.53) - **Threshold header no-op in direct-only mode** (1.41) - Adds helper functions: `simpleChat`, `chatWithSystem`, `chatWithImage`, `restoreDirectBaseline`, `assertHitAndReturnCacheDebug` - Establishes a parallelism contract: cases that mutate plugin config run serially (no `t.Parallel()`); all others run concurrently with unique cache keys to prevent collisions ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh # Run the full direct-mode suite go test ./tests/semanticcache/... -run TestDirect -v -timeout 300s # Skip the expensive image generation case SC_SKIP_IMAGE_GEN=1 go test ./tests/semanticcache/... -run TestDirect -v -timeout 300s ``` Required environment variables (same as the broader semantic cache e2e suite): - `OPENAI_MODEL` — primary OpenAI-compatible model (e.g. `openai/gpt-4o-mini`) - `OPENAI_MODEL_ALT` — secondary model for cross-model isolation cases - `OPENAI_EMBED` — embedding model name (e.g. `text-embedding-3-small`) - `ANTHRO_MODEL` — (optional) Anthropic model; cases 1.7 and 1.8 skip if unset - `SC_SKIP_IMAGE_GEN=1` — (optional) skip case 1.23 to avoid DALL-E costs ## Screenshots/Recordings N/A — test-only change. ## Breaking changes - [x] No ## Related issues ## Security considerations No new auth, secrets, or PII surface. Test prompts are benign and do not contain sensitive data. ## 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
## Summary Adds a comprehensive integration test suite for the semantic cache mode (Phase 2), covering the full lifecycle of semantic similarity-based cache hits and misses using Weaviate as the vector store and OpenAI's `text-embedding-3-small` as the embedding model. This suite validates that the semantic cache behaves correctly across a wide range of real-world scenarios, complementing the existing direct-mode (Phase 1) tests. ## Changes - Added `TestParaphraseFixtures` to pre-flight all paraphrase pairs against the live embedding model, asserting cosine similarity thresholds before any semantic cache cases run. This prevents flaky downstream failures caused by borderline fixture pairs. - Added `TestSemantic` containing 44 sub-cases (2.1–2.44) covering: - Semantic hit on paraphrase, miss on unrelated content - Per-request threshold overrides (relax, tighten, clamp above/below valid range) - `x-bf-cache-type` header forcing direct-only or semantic-only lookup paths - Cache key and model/provider isolation in semantic mode - `cache_by_model=false` and `cache_by_provider=false` cross-model/cross-provider hits - Streaming replay of semantic hits, including tool call preservation - TTL expiry, per-request TTL, TTL=0 fallback, and `no-store` header semantics - Namespace isolation and dimension-change silent miss behavior - Embedding endpoint bypass (semantic search skipped for `/v1/embeddings`) - Image generation and Responses API semantic hits - Text completion semantic hits - Gemini provider with OpenAI embedding provider - `params_hash` isolation (temperature, service tier, store flag, prompt cache key, previous response ID) - `exclude_system_prompt` flag effect on semantic matching - Conversation message threshold skipping semantic search - Attachment URL changes causing misses - `cache_debug` field presence and correctness on hits and misses, including log endpoint cross-check - Streaming chunk-level `cache_debug` placement (final chunk only) - Serial (non-parallel) cases that mutate plugin config restore baseline via `t.Cleanup` to avoid test pollution. - A dedicated Weaviate namespace (`cfg.Namespace + "Semantic"`) is used to avoid dimension conflicts with the Phase 1 direct-mode namespace. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh # Run fixture pre-flight (requires OpenAI embedding access) go test ./tests/semanticcache/... -run TestParaphraseFixtures -v # Run full semantic suite go test ./tests/semanticcache/... -run TestSemantic -v -timeout 10m # Skip fixture verification if embedding access is unavailable SC_SKIP_FIXTURE_VERIFY=1 go test ./tests/semanticcache/... -run TestSemantic -v -timeout 10m # Skip image generation cases if DALL-E is unavailable SC_SKIP_IMAGE_GEN=1 go test ./tests/semanticcache/... -run TestSemantic -v -timeout 10m ``` Required environment/config: - `cfg.OpenAIEmbed` — embedding model name (e.g. `text-embedding-3-small`) - `cfg.OpenAIModel` / `cfg.OpenAIModelAlt` — chat models for isolation tests - `cfg.AnthroModel` — optional; skipped if empty (case 2.13) - `cfg.GeminiModel` — optional; skipped if empty (case 2.28) - `cfg.Namespace` — base Weaviate namespace; suite appends `Semantic` suffix - `SC_SKIP_FIXTURE_VERIFY=1` — skip embedding pre-flight - `SC_SKIP_IMAGE_GEN=1` — skip DALL-E case ## Screenshots/Recordings N/A ## Breaking changes - [x] No ## Related issues N/A ## Security considerations No new auth, secrets, or PII handling introduced. Tests call live external APIs (OpenAI, optionally Anthropic/Gemini) and require valid credentials in the test environment; no credentials are hardcoded. ## 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
## Summary
Adds an end-to-end lifecycle test for the semantic cache plugin, covering the full disable → re-enable → delete → recreate flow and asserting that namespace data persists across each state transition.
## Changes
- Introduces `TestLifecycle` in `tests/semanticcache/lifecycle_test.go`, which runs 10 serial subtests (3.1–3.10) exercising the plugin's lifecycle state machine:
- **3.1** – Disabling the plugin via PUT sets `enabled=false` and `status=disabled`
- **3.2** – Requests while disabled bypass the cache pipeline entirely (no `cache_debug` header)
- **3.3 / 3.4** – Cache-clear endpoints (`/api/cache/clear/{id}` and `/api/cache/clear-by-key/{k}`) return HTTP 400 when the plugin is not loaded
- **3.5** – Re-enabling via PUT restores `enabled=true` and `status=active`
- **3.6** – Entries written before disable are still queryable after re-enable
- **3.7** – DELETE removes both the DB row and the in-memory plugin instance
- **3.8** – Requests after delete bypass the cache pipeline (no `cache_debug` header)
- **3.9** – Recreating the plugin with the same config succeeds and surfaces `status=active`
- **3.10** – Entries written before delete are still queryable after recreate, validating the namespace-persistence contract introduced by the removal of `CleanUpOnShutdown`
- Tests are intentionally serial (no `t.Parallel()`) because each subtest mutates globally shared plugin lifecycle state
- A `t.Cleanup` handler performs best-effort key clearing regardless of which lifecycle state the plugin is left in at teardown
## Type of change
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Chore/CI
## Affected areas
- [ ] Core (Go)
- [ ] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
go test ./tests/semanticcache/... -run TestLifecycle -v
```
Expected outcome: all 10 subtests (3.1–3.10) pass, with structured log output at each step confirming correct status transitions and cache hit/miss behaviour.
## Breaking changes
- [x] No
## Related issues
## Security considerations
None. Tests run against a local Bifrost instance and do not introduce new auth paths, secrets handling, or PII exposure.
## 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
…kefile targets (#3429) ## Summary Adds Makefile targets for running `semantic_cache` plugin unit tests and end-to-end tests, with optional integration of the `trail` CLI for capture-based debugging sessions. ## Changes - Added `test-semantic-cache` target that runs e2e tests from `tests/semanticcache`, supporting a `CACHE_TYPE` variable (`direct` or `semantic`) to filter which test phases are executed. Automatically wraps the run in `trail run` if the `trail` binary is available on `PATH`. - Added `test-semantic-cache-complete` target that runs both the plugin unit tests (`plugins/semanticcache`) and the e2e tests in sequence, optionally wrapping the entire session in a single `trail run` invocation. - Added `_test-semantic-cache-complete-inner` as an internal helper target that performs the actual sequential execution of unit and e2e tests with formatted output banners. - Registered all three new targets in the `.PHONY` declaration. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [x] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh # Run all semantic_cache e2e tests make test-semantic-cache # Run only direct cache tests CACHE_TYPE=direct make test-semantic-cache # Run only semantic cache tests CACHE_TYPE=semantic make test-semantic-cache # Run both unit and e2e tests together make test-semantic-cache-complete # Force e2e run regardless of preconditions RUN_FORCE=1 make test-semantic-cache-complete ``` If `trail` is installed and on `PATH`, all commands will automatically wrap execution in a `trail run` session for capture-based debugging. ## 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
## 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
…tions column (#3480) ## Summary Replaces the direct delete button in the logs and MCP logs action columns with a dropdown menu triggered by a `MoreHorizontal` icon. This improves the UI by providing a more scalable actions pattern while keeping the delete functionality accessible. The actions column is also now properly pinned to the right side of the table when the user has delete access. ## Changes - Replaced the inline destructive `Trash2` button with a `DropdownMenu` containing a "Delete" item for both logs and MCP logs tables - The actions column trigger is now a ghost `MoreHorizontal` icon button, reducing visual noise in the table - The actions column is pinned to the right only when `hasDeleteAccess` is true; otherwise no fixed columns are configured - Fixed `fixedColumnIds` to include `"actions"` so the column receives correct sticky positioning behavior - Removed `overflow-hidden` from pinned cells in the MCP logs table to prevent the dropdown from being clipped - Reduced the actions column size from 72 to 56px ## 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 Logs page as a user with delete access. 2. Confirm the actions column is pinned to the right of the table. 3. Click the `⋯` icon on any row and verify the dropdown appears with a "Delete" option. 4. Click "Delete" and confirm the log is deleted without the row click handler firing. 5. Repeat on the MCP Logs page. 6. Log in as a user without delete access and confirm the actions column is not present. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots showing the old delete button vs. the new dropdown._ ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new security implications. Delete access gating remains unchanged. ## 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
…wing text (#3481) ## Summary Fixes layout overflow issues in the Model Catalog table where long provider names and model badge text would break out of their columns or cause uneven column sizing. ## Changes - Added `table-fixed` layout with explicit `<colgroup>` column widths (26% / 44% / 16% / 14%) to enforce stable column proportions - Added `overflow-hidden` and `truncate` to the Provider name cell so long names are clipped cleanly instead of overflowing - Added `shrink-0` to the "CUSTOM" badge so it doesn't compress when the provider name is long - Added `max-w-[220px] truncate` to model name badges in `ModelsUsedCell` to prevent individual badges from stretching too wide ## 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 Navigate to the Model Catalog page and verify: 1. Provider names that are long truncate cleanly within their column 2. The "CUSTOM" badge remains visible and does not shrink when next to a long provider name 3. Model name badges in the models column truncate at a reasonable width 4. Column widths remain stable regardless of content length ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings If UI changes, add before/after screenshots or short clips. ## 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
…y names (#3482) ## Summary Fixes layout issues in the model provider keys table where long key/model/server names would overflow their cells and cause the table to render incorrectly. ## Changes - Applied `table-fixed` layout to the keys table and defined explicit column widths via `<colgroup>` (64% for the name column, 12% each for the remaining three columns) to enforce stable column sizing regardless of content length - Added `overflow-hidden` to the name cell and `min-w-0` + `truncate` to the name text span so long strings are clipped with an ellipsis instead of breaking the layout - Added a trailing newline at end of file ## 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 the Workspace → Providers page and open a provider that has keys with long names (e.g. a vLLM model name or a long API key label). 2. Verify that the name column truncates with an ellipsis rather than overflowing into adjacent columns. 3. Verify that the three action columns (weight, status, actions) maintain consistent widths. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings Add before/after screenshots showing the table with a long key name to confirm truncation behavior. ## 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
…limits table (#3483) ## Summary Replaces the individual inline Edit and Delete action buttons in the model limits table with a consolidated `DropdownMenu` (three-dot menu). The delete confirmation dialog is also lifted out of the per-row render loop and rendered once at the table level, driven by a `deleteModelConfigId` state value. ## Changes - Replaced per-row Edit and Delete buttons with a single `MoreHorizontal` icon button that opens a `DropdownMenu` containing Edit and Delete items. - Moved the `AlertDialog` for delete confirmation out of the table row loop into a single top-level instance, controlled by `deleteModelConfigId` state. This prevents multiple dialog instances from being mounted simultaneously. - Added `deletingModelConfig` derived from `deleteModelConfigId` via `useMemo`, keeping it in sync with the RTK cache similarly to `editingModelConfig`. - Cleared `deleteModelConfigId` on successful deletion to close the dialog automatically. - Removed the hover/focus-dependent opacity animation on the action cell since the dropdown replaces that pattern. ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Navigate to the Model Limits table in the workspace. 2. Hover over any model limit row and click the `...` (MoreHorizontal) button. 3. Verify the dropdown shows **Edit** and **Delete** options. 4. Click **Edit** and confirm the model limit sheet opens with the correct config pre-populated. 5. Click **Delete** and confirm the confirmation dialog appears with the correct model name (truncated if over 30 characters). 6. Confirm deletion succeeds, the dialog closes, and a success toast is shown. 7. Confirm that users without update/delete RBAC access see the respective menu items disabled. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings _Add before/after screenshots showing the old separate Edit/Delete buttons vs. the new dropdown menu._ ## Breaking changes - [x] No ## Related issues ## Security considerations RBAC checks for `Governance` update and delete operations are preserved on the new dropdown menu items. ## 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
## Summary Replaces the individual Edit and Delete action buttons in the routing rules table with a consolidated `DropdownMenu` triggered by a `MoreHorizontal` icon. This reduces visual clutter in the actions column and provides a more consistent UX pattern for row-level actions. ## Changes - Replaced separate Edit and Delete ghost buttons with a single `MoreHorizontal` icon button that opens a dropdown menu containing both actions - Edit and Delete items within the dropdown remain gated by `canUpdate` and `canDelete` permissions respectively, now using the `disabled` prop instead of conditional rendering - The Delete item uses the `destructive` variant to visually distinguish it from the Edit action - Changed `catch (error: any)` to `catch (error: unknown)` for improved type safety - Added a trailing newline to the end of the file ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test Navigate to the routing rules table and verify: 1. Each row displays a `MoreHorizontal` icon button in the actions column 2. Clicking the icon opens a dropdown with Edit and Delete options 3. Edit and Delete options are disabled when the user lacks the respective permissions 4. Selecting Edit opens the edit flow for the correct rule 5. Selecting Delete triggers the delete confirmation dialog for the correct rule 6. Row click propagation is not triggered when interacting with the dropdown ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings Before: Two separate icon buttons (pencil and trash) visible inline on each row. After: A single `⋯` icon button per row that reveals Edit and Delete options in a dropdown, with Delete styled in red. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No security implications. Permission checks (`canUpdate`, `canDelete`) are preserved. ## 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
…g overrides table (#3485) ## Summary Replaces the individual Edit and Delete action buttons in the pricing overrides table with a consolidated `DropdownMenu` triggered by a `MoreHorizontal` icon. Also fixes a sidebar active state bug where sub-items were incorrectly matching routes, and adds `hasAPIKeyAccess` to the sidebar's memoization dependencies. ## Changes - Replaced separate Edit and Delete icon buttons in the pricing overrides table rows with a single `MoreHorizontal` actions dropdown containing labeled Edit and Delete menu items. The Delete item uses the destructive variant for visual clarity. - Fixed sidebar sub-item active state detection to use `isRouteMatch` instead of `pathname.startsWith`, preventing incorrect active highlighting on partial path matches. - Added `hasAPIKeyAccess` to the sidebar's `useMemo` dependency array, which was previously missing and could cause stale renders. ## Type of change - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Navigate to the custom pricing overrides table. 2. Hover over a row and click the `⋯` (MoreHorizontal) button — a dropdown should appear with **Edit** and **Delete** options. 3. Clicking **Edit** should open the edit drawer without triggering row selection. 4. Clicking **Delete** should open the delete confirmation dialog without triggering row selection. 5. Verify sidebar sub-item active states are correct when navigating between nested routes — only the exact matching route should appear active. ```sh cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` ## Screenshots/Recordings Before: Two separate ghost icon buttons (pencil and trash) visible inline on each row. After: A single `⋯` button per row that reveals a dropdown with labeled **Edit** and **Delete** actions. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No security implications. ## 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
…s column in MCP clients table (#3486) ## Summary Replaces the per-row inline action buttons (reconnect + delete) in the MCP clients table with a consolidated `MoreHorizontal` dropdown menu, and moves the actions column to a sticky right-pinned position so it remains visible when the table scrolls horizontally. ## Changes - Replaced the individual `Reconnect` (with tooltip) and `Delete` (with inline `AlertDialog`) buttons with a single `DropdownMenu` triggered by a `MoreHorizontal` icon button. - The delete confirmation `AlertDialog` is now lifted out of the table row and controlled via a `clientToDelete` state variable, preventing multiple dialog instances from being mounted inside the DOM simultaneously. - The actions column header and cell are now `sticky right-0` with `PIN_SHADOW_RIGHT` applied, keeping the actions visible during horizontal scroll. - The table container changed from `overflow-hidden` to `overflow-auto` to enable horizontal scrolling. - Reconnect and delete menu items are conditionally rendered based on RBAC access, rather than being rendered-but-disabled. - The `MoreHorizontal` button shows a `Loader2` spinner while a reconnect is in progress for that row. - Added `group` class to table rows to allow the sticky actions cell to mirror the row hover background. ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Navigate to the MCP Registry page in the workspace UI. 2. Verify each MCP client row shows a `⋯` (MoreHorizontal) button in the rightmost column. 3. Click the button and confirm the dropdown shows **Reconnect** and **Delete** options (subject to RBAC permissions). 4. Select **Reconnect** and confirm the spinner appears on the button while reconnecting. 5. Select **Delete** and confirm the confirmation dialog appears with the correct server name, and that confirming removes the client. 6. Resize the browser window to trigger horizontal scrolling and confirm the actions column remains pinned to the right. ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings Before/after screenshots recommended showing the old inline icon buttons vs. the new dropdown menu. ## Breaking changes - [x] No ## Related issues ## Security considerations RBAC checks are preserved — reconnect and delete menu items are only rendered when the user has the corresponding `Update` or `Delete` permission on `MCPGateway`. ## 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
…d active toggle switch in teams and virtual keys tables (#3487) ## Summary Replaces the inline edit/delete action buttons in the Teams and Virtual Keys tables with a consolidated `MoreHorizontal` dropdown menu per row. The actions column is now sticky-pinned to the right edge of the table so it remains visible when the table scrolls horizontally. The Virtual Keys table also replaces the status badge with an inline active/inactive toggle switch. ## Changes - Extracted `TeamActionsMenu` and `VKActionsMenu` components that render a `DropdownMenu` containing Edit and Delete items, with the delete confirmation `AlertDialog` controlled via local state rather than being triggered directly from an `AlertDialogTrigger`. - Removed the hover-only opacity animation on action buttons in favor of always-visible dropdown triggers. - The actions `TableHead` and `TableCell` are now sticky (`sticky right-0 z-10`) with `PIN_SHADOW_RIGHT` applied and background colors that match the row hover state, keeping the pinned column visually consistent. - Tables are given a `min-w` value and their container uses `overflow-auto` to support horizontal scrolling without breaking the sticky column. - `VKStatusBadge` is replaced by `VKActiveSwitch`, which renders a `Switch` component and calls `useUpdateVirtualKeyMutation` to toggle `is_active` inline. Managed-by-profile keys disable the switch and show a tooltip title. - The managed-by-profile delete tooltip/disabled-button pattern is replaced by a disabled destructive `DropdownMenuItem` with a `title` attribute. - `handleEditVirtualKey` no longer requires a `MouseEvent` argument since click propagation is handled at the cell level. ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Navigate to the **Governance → Teams** page. - Verify the actions column stays pinned to the right when scrolling horizontally. - Click the `MoreHorizontal` button on a row and confirm Edit and Delete items appear. - Confirm the delete confirmation dialog opens from the dropdown and completes successfully. 2. Navigate to the **Virtual Keys** page. - Verify the active toggle switch reflects the current `is_active` state and toggling it updates the key immediately with a success toast. - Confirm keys managed by an access profile show a disabled switch and a disabled Delete item in the dropdown. - Verify the sticky actions column behaves correctly on horizontal scroll. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots showing the dropdown menu and sticky column behavior._ ## Breaking changes - [x] No ## Related issues ## Security considerations No new auth surfaces introduced. RBAC checks (`hasUpdateAccess`, `hasDeleteAccess`) are preserved on all action items. ## 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
## Summary Calendar alignment for teams was previously tracked per-budget row, which meant the setting was scattered across individual budgets and did not apply to the team's rate limit. This PR promotes calendar alignment to a team-level property, persisted in a new `calendar_aligned` column on `governance_teams`, so a single toggle governs all team budgets and the team rate limit simultaneously. ## Changes - Added a database migration (`migrationAddTeamCalendarAlignedColumn`) to add `calendar_aligned` to `governance_teams`, and changed `TableTeam.CalendarAligned` from a transient (`gorm:"-"`) field to a persisted one. - Removed `calendar_aligned` from `CreateBudgetRequest`, `UpdateBudgetRequest`, and the `Budget` type. Calendar alignment is now owned by the entity (VK or Team), not the budget row. - Added an `AfterFind` hook on `TableBudget` that subqueries the owning VK or Team to populate a transient `IsCalendarAligned` field, so budget reset logic can read alignment without joining at the call site. - `ResetExpiredBudgetsInMemory` now reads `budget.IsCalendarAligned` directly instead of looking up the owning VK. - `ResetExpiredRateLimitsInMemory` now includes team rate limits in the `rateLimitCalendarAligned` reverse map, enabling calendar-aligned resets for team-attached rate limits. - `CreateTeamRequest` and `UpdateTeamRequest` now accept a top-level `calendar_aligned` field. On enable, existing team budgets and the team rate limit are immediately snapped to the current calendar period start and their usage counters are zeroed. - The team dialog UI replaces per-budget calendar-align toggles with a single team-wide toggle. The confirmation warning dialog now describes the reset of both budget usage and rate-limit counters. The toggle is only rendered when at least one alignable budget or rate limit exists. - The VK details sheet and VKs table now guard the `(calendar)` label behind `supportsCalendarAlignment(duration)`, so short durations (e.g. hours) never show the label even when the VK flag is set. - `RateLimitDisplay` accepts a new `calendarAligned` prop and appends `(calendar)` to alignable durations in all display contexts (bar, tooltip, limit-only). - Removed the now-redundant `ProviderConfigBudget`, `ProviderConfigBudgetRequest`, and `VirtualKeyBudgetRequest` type aliases; all budget request sites use `CreateBudgetRequest` directly. - VK sheet now always sends `calendar_aligned` at the top level of the update/create payload rather than conditionally inside the budgets block. ## Type of change - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Create a team with budgets and/or a rate limit using alignable durations (1d, 1w, 1M, 1Y). Enable `calendar_aligned: true` on creation and verify `last_reset` is snapped to the current period start. 2. Update an existing team to set `calendar_aligned: true`. Confirm existing budget `current_usage` is zeroed and `last_reset` is snapped, and the team rate limit counters are also zeroed and snapped. 3. Verify that toggling `calendar_aligned` off does not reset usage and that subsequent resets roll from the last reset time rather than calendar boundaries. 4. Confirm the team dialog shows a single calendar-align toggle (not per-budget toggles), and that the confirmation dialog appears only when enabling on an existing team. 5. Confirm the `(calendar)` label does not appear on budgets or rate limits with sub-day durations even when the VK or team has `calendar_aligned: true`. ```sh go test ./framework/configstore/... ./plugins/governance/... ./transports/bifrost-http/... cd ui pnpm i pnpm test pnpm build ``` ## Breaking changes - [x] Yes - [ ] No `calendar_aligned` has been removed from `CreateBudgetRequest`, `UpdateBudgetRequest`, and the `Budget` response type. Any API clients sending `calendar_aligned` on individual budget objects will have the field silently ignored. Clients reading `budget.calendar_aligned` from responses will no longer receive it; they should read `calendar_aligned` from the owning team or VK instead. ## Related issues ## Security considerations No new auth surfaces or secrets handling. The migration is additive (new nullable column with a default). No PII is involved. ## 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
## Summary Adds support for Amazon Nova system tools (`nova_grounding` and `nova_code_interpreter`) through the Bedrock Converse and Converse-Stream paths. These are AWS-managed tools that the model invokes and executes automatically within a single turn — no client-side tool loop is required. The changes wire up bidirectional translation between Bedrock's native system tool format and Bifrost's neutral schema (`web_search` and `code_interpreter`), covering both non-streaming and streaming response handling. ## Changes - **`nova_grounding` support**: Enables `WebSearch: true` for the Bedrock provider feature map. Translates `nova_grounding` ↔ `web_search` in tool config conversion. Handles `citation` deltas in the streaming path, emitting them as `url_citation` annotations. Maps `citationsContent` blocks in non-streaming responses to `url_citation` annotations on text content blocks. - **`nova_code_interpreter` support**: Translates `nova_code_interpreter` ↔ `code_interpreter` in tool config conversion. Introduces `CodeInterpreterIndices` tracking in `BedrockResponsesStreamState` to distinguish code interpreter tool calls from regular function calls during streaming. Emits `code_interpreter_call.in_progress`, `code_interpreter_call.code_delta`, `code_interpreter_call.code.done`, and `code_interpreter_call.completed` stream events instead of the function-call event sequence. In non-streaming responses, merges the `toolUse` (code) and paired `nova_code_interpreter_result` `toolResult` blocks into a single `code_interpreter_call` output item with execution logs. - **Stop reason fix**: Server-managed tools resolve their own `toolResult` in the same turn. The stop reason derivation now pre-scans for matched `toolUse`/`toolResult` pairs so that `hasToolUse` stays false for self-resolving tools, preserving the original `end_turn` stop reason rather than incorrectly emitting `tool_use`. - **New Bedrock types**: Added `BedrockSystemTool`, `BedrockSystemToolType`, `BedrockCitation`, `BedrockCitationLocation`, `BedrockWebCitationLocation`, `BedrockCitationsContent`, and a `Type` field on `BedrockToolResult` to support `nova_code_interpreter_result` identification. - **Integration tests**: Added `TestNovaSystemTools` with four test cases (50–53) covering `nova_grounding` and `nova_code_interpreter` in both non-streaming and streaming modes via the Bedrock Converse API. ## 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 ./core/providers/bedrock/... go test ./core/providers/anthropic/... ``` For integration tests against a live Bedrock endpoint with Nova model access: ```sh cd tests/integrations/python pip install -r requirements.txt pytest tests/test_bedrock.py::TestNovaSystemTools -v ``` Test cases require AWS credentials with access to `us.amazon.nova-2-lite-v1:0` and the `nova_grounding` / `nova_code_interpreter` system tools enabled in your account. Tests will be skipped automatically if the tools are unavailable or the API key is not configured. ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations Nova system tools are fully AWS-managed and execute in AWS infrastructure. No user-supplied code or credentials are passed through Bifrost beyond the standard Bedrock API call. Citation URLs returned by `nova_grounding` are surfaced as annotations and are not fetched or processed by Bifrost. ## 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
… chat completions fallback (#3519) * [fix]: openai provider - add usage to completed event in responses to chat completions fallback When a Responses streaming request falls back to chat completions, Bifrost streams from the upstream provider (OpenAI, Groq, Mistral, etc.) and translates each chunk into a Responses event for the caller. The provider emits finish_reason first and usage one chunk later, but Bifrost was forwarding response.completed/response.incomplete as soon as finish_reason arrived — so the caller's terminal event had usage unset. Upstream chunk order: ...content deltas... { ..., "finish_reason": "stop", "usage": null } <- we sent terminal here (bug) { ..., "usage": {...} } <- arrives too late Fix: accumulate usage from every chunk, hold the terminal event until the upstream stream ends, then attach usage before sending. Native chat-completion and Responses streaming paths are unchanged. Modified files: - core/providers/openai/openai.go: attach usage information to completed event Update changelogs: - core/changelog.md: [fix]: openai provider - add usage to completed event in responses to chat completions fallback [@kevinpdev](https://github.com/kevinpdev) * [fix]: openai provider - preserve nil usage when fallback stream omits usage chunks Follow-up to 9581cb6. If a provider in the fallback path never sends usage data, the previous fix would still attach a zero-valued usage object to the final event instead of leaving it null. Only attach usage when we actually saw a usage chunk from upstream. Affected packages: - core/providers/openai: gate fallback terminal usage attach on observed usage chunk Update changelogs: - core/changelog.md: [fix]: openai provider - preserve nil usage when fallback stream omits usage chunks [@kevinpdev](https://github.com/kevinpdev) * [fix]: openai provider - always use merged usage on terminal event in fallback Drop the nil guard so the merged usage accumulator always overwrites the terminal event's usage, even if a partial usage struct was already attached. Modified files: - core/providers/openai/openai.go: drop nil guard on terminal usage attach --------- Co-authored-by: Dominic Elm <elmdominic@gmx.net>
## 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
…e-close panic (#3532) ## Summary The `Cleanup` method on the semantic cache plugin could panic if called more than once, because `close(stopCh)` would be invoked on an already-closed channel. This happens when the plugin is registered against multiple interface caches, causing the harness to invoke `Cleanup` multiple times. ## Changes - Introduced a `sync.Once` field (`cleanupOnce`) on the `Plugin` struct to guard the body of `Cleanup`, ensuring `close(stopCh)` and the subsequent wait/sweep logic execute exactly once regardless of how many times `Cleanup` is called. ## Type of change - [x] Bug fix ## Affected areas - [x] Plugins ## How to test Register the semantic cache plugin against multiple interface caches and trigger shutdown. Verify that `Cleanup` is called more than once without panicking. ```sh go test ./plugins/semanticcache/... ``` ## 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
## Summary Several fields on the Responses streaming schema (`Phase`, `SummaryIndex`, `Obfuscation`, and the latent leaks `Status` and `Signature`) were either undocumented or silently dropped by the deep-copy helper used during stream processing. This PR fixes the documentation, ensures the deep-copy preserves all of these fields with proper pointer independence, and adds test coverage to prevent regressions. The `Phase` field is particularly important: it is required when replaying history to `gpt-5.3-codex+` models, and omitting it causes significant performance degradation. ## Changes - Expanded the inline doc comment on `ResponsesMessage.Phase` to explain its role (`"commentary"` vs `"final_answer"`), its requirement on `gpt-5.3-codex+` history replay, and the performance impact of dropping it. - Expanded the inline doc comment on `BifrostResponsesStreamResponse.SummaryIndex` to clarify which streaming event types emit it. - Expanded the inline doc comment on `BifrostResponsesStreamResponse.Obfuscation` to explain it is random padding used as a side-channel mitigation, toggled via `StreamOptions.IncludeObfuscation`. - Added `TestDeepCopyResponsesStreamResponsePreservesAllFields` to guard the deep-copy helper against silently dropping `Phase`, `SummaryIndex`, `Obfuscation`, `Status`, and `Signature`, and to assert pointer independence after copying. - Added four new E2E harness cases covering: reasoning `summary_index` and `obfuscation` surviving a stream, `phase` appearing on assistant message items in a stream, and `phase` round-tripping correctly as an input field. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./framework/streaming/... -run TestDeepCopyResponsesStreamResponsePreservesAllFields -v ``` For E2E validation, run the updated provider harness collection against a live environment with a valid `openaiKey` and confirm: - `summary_index` and `obfuscation` appear in the SSE body for the `o3-mini` streaming request. - `phase` (`"final_answer"` or `"commentary"`) appears on assistant message items for the `gpt-5.3-codex` streaming request. - The `phase` input round-trip request returns output items without an `unknown field "phase"` error. ## Breaking changes - [ ] Yes - [x] No ## Related issues Closes #3528 ## Security considerations The `Obfuscation` field is explicitly documented as random padding added to normalize delta event payload sizes as a side-channel mitigation. No new secrets, PII, or auth surfaces are introduced. ## 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
## Summary Fixes invalid SQL table alias usage in the `migrationAddTeamCalendarAlignedColumn` migration and adds build artifacts from e2e test demo servers to `.gitignore`. ## Changes - Removed table aliases (`t`) from `UPDATE` statements in the calendar-aligned backfill migration, replacing them with direct table name references. Some databases (e.g., PostgreSQL) do not support aliases in `UPDATE` statements in this form, which would cause the migration to fail. - Added compiled binaries for `auth-demo-server` and `oauth-demo-server` under `examples/mcps/` to `.gitignore` to prevent e2e test build artifacts from being accidentally committed. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test Run the migration against a fresh or existing database and verify it completes without error: ```sh go test ./framework/configstore/... ``` Confirm that building the e2e demo servers does not produce tracked files in git: ```sh cd examples/mcps/auth-demo-server && go build . cd examples/mcps/oauth-demo-server && go build . git status # binaries should not appear as untracked files ``` ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations No security implications. The SQL fix removes table aliases that could cause migration failures; no auth, secrets, or PII are involved. ## 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
## 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
## Summary Reformats `sidebar.tsx` to use 2-space indentation consistently throughout the file, and fixes external links rendering in the collapsed sidebar state. ## Changes - Converted all tab-indented code to 2-space indentation across the entire sidebar component - External links in the bottom toolbar are now conditionally rendered only when the sidebar is expanded (`sidebarState !== "collapsed"`), preventing them from appearing in the collapsed icon-only view where they have no visible labels and cause layout issues ## Type of change - [x] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Open the sidebar in expanded state and verify all external links (Discord, GitHub, bug report, docs) are visible in the bottom toolbar 2. Collapse the sidebar using the collapse button and verify the external links are no longer rendered 3. Expand the sidebar again and verify the external links reappear ```sh cd ui pnpm i || npm i pnpm build || npm run build ``` ## Screenshots/Recordings Verify the collapsed sidebar no longer shows external link icons crowding the bottom icon row. ## 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
## Summary Consolidates context cancellation in `createHandler` into a single `defer` at the top of the handler lambda, eliminating scattered and inconsistent `cancel()` / `defer cancel()` calls throughout the function. A `streamingOwnsCancel` flag is introduced to transfer cancel ownership to the streaming path, whose producer goroutine outlives the handler lambda and is responsible for calling `cancel` itself. ## Changes - Replaced all individual `cancel()` and `defer cancel()` calls across every early-return branch with a single `defer` guarded by a `streamingOwnsCancel` boolean. - When the request is determined to be streaming, `streamingOwnsCancel` is set to `true` before calling `handleStreamingRequest`, so the deferred cleanup is skipped and ownership is passed to the streaming goroutine. - All non-streaming and error paths continue to have `cancel` called correctly via the centralized defer without any change in behavior. ## Type of change - [ ] Bug fix - [ ] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./transports/bifrost-http/... ``` Verify that streaming responses complete without context cancellation errors, and that non-streaming and error paths do not leak contexts. ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations No security implications. This change only affects context lifecycle management within the HTTP handler. ## 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
## Summary
Extends SCIM/SSO support in the Bifrost Helm chart (v2.1.17) to add two new providers — **Zitadel** and **Google Workspace** — and fixes a schema bug where `attributeRoleMappings`, `attributeTeamMappings`, and `attributeBusinessUnitMappings` were silently rejected for Okta and Entra due to `additionalProperties: false`. Additionally tightens the previously loose Keycloak mapping item schemas from bare `{type: object}` to strictly typed shapes so typos are caught at `helm template` time.
## Changes
- Added `zitadel` and `google` to the `provider` enum in `values.schema.json`, with full `if/then` conditional config blocks for each.
- Added `attributeRoleMappings`, `attributeTeamMappings`, and `attributeBusinessUnitMappings` to the Okta and Entra provider config schemas — these fields were previously blocked by `additionalProperties: false` even though the enterprise runtime writes them into `config.json`.
- Tightened all mapping array item schemas (Keycloak, Okta, Entra, Zitadel, Google) from the placeholder `{type: object}` to strict shapes with `required` fields and `additionalProperties: false`, so invalid mapping entries fail at `helm template` time rather than silently at runtime.
- Added `helm template`-time validation in `_helpers.tpl` for Zitadel (`domain`, `clientId` required) and Google Workspace (`domain`, `clientId` required).
- Documented all new fields as commented examples in `values.yaml` for Zitadel (`domain`, `clientId`, optional `clientSecret`/`projectId`/`audience`, service-account fields) and Google Workspace (`domain`, `clientId`, `credentialMode`, service-account sources, `adminEmail`, `impersonateServiceAccount`).
## Type of change
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [ ] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
# Validate schema enforcement for new providers
helm template bifrost ./helm-charts/bifrost \
--set bifrost.scim.enabled=true \
--set bifrost.scim.provider=zitadel \
--set bifrost.scim.config.domain=my-instance.zitadel.cloud \
--set bifrost.scim.config.clientId=my-client-id
# Confirm validation failure when required fields are missing
helm template bifrost ./helm-charts/bifrost \
--set bifrost.scim.enabled=true \
--set bifrost.scim.provider=zitadel
# Expected: ERROR: bifrost.scim.config.domain is required when SCIM provider is Zitadel.
# Validate Google Workspace provider
helm template bifrost ./helm-charts/bifrost \
--set bifrost.scim.enabled=true \
--set bifrost.scim.provider=google \
--set bifrost.scim.config.domain=company.com \
--set bifrost.scim.config.clientId=my-client-id
# Validate attributeRoleMappings accepted for Okta (previously rejected)
helm template bifrost ./helm-charts/bifrost \
--set bifrost.scim.enabled=true \
--set bifrost.scim.provider=okta \
--set bifrost.scim.config.issuer=https://my-org.okta.com \
--set bifrost.scim.config.clientId=my-client-id \
--set 'bifrost.scim.config.attributeRoleMappings[0].attribute=groups' \
--set 'bifrost.scim.config.attributeRoleMappings[0].value=bifrost-admins' \
--set 'bifrost.scim.config.attributeRoleMappings[0].role=admin'
```
## Breaking changes
- [ ] Yes
- [x] No
## Related issues
## Security considerations
Zitadel and Google Workspace service account credentials (`serviceAccountClientSecret`, `serviceAccountEnvVar`, `serviceAccountFile`) should be supplied via Kubernetes Secrets or external secret managers rather than plain `values.yaml` to avoid credential exposure. The `adminEmail` field for Google domain-wide delegation grants broad Directory API access and should be scoped to a dedicated admin account.
## 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
…imitation for oss (#3549) ## Summary Adds a warning to the budget and limits documentation clarifying that running multiple OSS Bifrost nodes with a Postgres backend is not supported, and explains why this is an intentional architectural boundary between the OSS and Enterprise offerings. ## Issues closes #3547 ## Changes - Added a `<Warning>` block to `budget-and-limits.mdx` explaining that Bifrost keeps all critical state (provider configs, API keys, budgets, usage, traffic distribution) in memory and does not re-read it from the database after initialization. - Documents that multi-node synchronization is handled in the Enterprise version via a modified RAFT consensus mechanism, while the database serves only as a passive store. - Sets expectations for OSS users (~3,000–5,000 RPS on a single instance) and directs high-availability needs toward the Enterprise plan. ## Type of change - [ ] Bug fix - [ ] Feature - [ ] Refactor - [x] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [ ] UI (React) - [x] Docs ## How to test Review the rendered documentation to confirm the warning block displays correctly and the content is accurate. ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations None. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [x] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## Summary During shutdown, any in-memory budget and rate-limit deltas accumulated since the last background worker tick were silently dropped. This change ensures those deltas are flushed to the database before the tracker stops. ## Issues Closes #3548 ## Changes - Added a final call to `DumpBudgets` and `DumpRateLimits` at the start of `Cleanup()`, so in-flight deltas are persisted before background workers are cancelled. ## Type of change - [x] Bug fix - [ ] 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/governance/... ``` Verify that after a graceful shutdown, budget and rate-limit values written between the last periodic flush and shutdown are present in the database rather than lost. ## 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
|
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughA new custom linting tool ChangesBifrostLint Static Analysis Tool
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
| name: golangci-lint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.26.2" | ||
| - uses: golangci/golangci-lint-action@v6 | ||
| with: | ||
| version: v2.9.0 | ||
| args: --timeout=5m | ||
| bifrostlint: |
| name: bifrostlint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.26.2" | ||
| - name: Run bifrostlint analyzers | ||
| run: make lint-bifrost |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
Makefile (1)
1516-1522: 💤 Low valueTarget body length exceeds checkmake recommendation.
The
lint-bifrosttarget has 6 lines, exceeding checkmake's suggested limit of 5. This is a minor style concern; the logic is clear and correct: build bifrostlint, run analyzers, run exportedtests with baseline.Based on checkmake static analysis hint.
🤖 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 `@Makefile` around lines 1516 - 1522, The lint-bifrost Makefile target body is over the 5-line checkmake recommendation; collapse commands into fewer lines by chaining related commands with && or using semicolons so the target has at most 5 physical lines while preserving behavior: keep the build step (cd cmd/bifrostlint && GOWORK=off go build -o ../../bin/bifrostlint .), the analyzer run (./bin/bifrostlint ./...), and the exportedtests run with baseline (./bin/bifrostlint exportedtests -baseline=cmd/bifrostlint/baseline.txt ./...) but combine adjacent echo/build/run lines into single lines under the lint-bifrost target to reduce total lines.cmd/bifrostlint/internal/sqlscan/sqlscan.go (1)
60-65: ⚡ Quick winCache compiled regexes for repeated keyword/phrase checks.
regexp.MustCompileon every invocation adds avoidable overhead in analyzer hot paths; memoizing by pattern keeps behavior the same and improves runtime.Also applies to: 79-90
🤖 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 `@cmd/bifrostlint/internal/sqlscan/sqlscan.go` around lines 60 - 65, ContainsKeyword currently recompiles the regex on every call; change it to memoize compiled *regexp.Regexp instances keyed by the pattern (built with `(?i)\b` + regexp.QuoteMeta(keyword) + `\b`) in a package-level cache (use sync.Map or a map+sync.RWMutex) and load-or-store the compiled regexp (regexp.MustCompile or Compile) before calling MatchString; apply the same memoization approach to the other regex-based keyword/phrase check in this file so repeated analyzer hot-path calls reuse compiled regexes.
🤖 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 @.github/workflows/lint.yml:
- Around line 9-33: The workflow grants the default write GITHUB_TOKEN; add
explicit minimal permissions to avoid excess write access by adding a
permissions block (e.g., permissions: contents: read) for the jobs in
.github/workflows/lint.yml—specifically add a permissions: { contents: read }
under the golangci job and the bifrostlint job (or add a top-level permissions
key to apply to all jobs) so the golangci and bifrostlint jobs only have
read-only access needed for checkout and linting.
- Around line 18-20: Update the golangci-lint GitHub Action and its tool
version: replace the action reference golangci/golangci-lint-action@v6 with the
newer action tag and change the tool version field currently set to "v2.9.0" to
"v2.12.2" so the workflow uses the latest golangci-lint release; ensure both the
action ref and the version: value are updated together to "v2.12.2".
- Line 17: Update the Go version in the CI workflow by changing the go-version
value used in the lint job from "1.26.2" to "1.26.3"; locate the go-version key
in the workflow (the go-version: "1.26.2" entry) and replace it with "1.26.3" so
the workflow uses the latest stable Go release.
In @.pre-commit-config.yaml:
- Around line 32-38: The YAML parser error comes from the unquoted bash
one-liner in the entry field for the hook id bifrostlint-go-filenames; update
the entry value to be a proper YAML string or block literal to avoid
mapping/quote conflicts and long-line issues: either wrap the entire command in
double quotes and escape internal double quotes, or replace the scalar with a
block literal (entry: |) and place the bash -c '... --' script on the next line;
ensure the modified entry references the same bash one-liner logic (failed loop,
basename, *_test.go skip, underscore check, exit $failed) but is correctly
quoted/escaped so the YAML parser accepts it and the line-length problem is
resolved.
In `@cmd/bifrostlint/analyzers/exportedtests/exportedtests.go`:
- Around line 240-274: collectReferenced is recording symbols from any package
because pkgPath is ignored; change it to only call recordRef when the referenced
object's package path equals the package-under-test (use pkgPath). In both
places where you get obj (the *types.Obj from info.Uses inside collectReferenced
for *ast.Ident and *ast.SelectorExpr), add a guard that obj.Pkg() != nil &&
obj.Pkg().Path() == pkgPath before calling recordRef(referenced), so only
same-package references are recorded; keep using the existing recordRef and
referenced map.
In `@cmd/bifrostlint/internal/sqlscan/exec.go`:
- Around line 43-45: The current switch treats any bare *ast.Ident as a DB exec
call (using dbExecMethods[fn.Name]), causing false positives; restrict detection
to selector calls only by removing or disabling the *ast.Ident branch and
instead match on *ast.SelectorExpr (use sel.Sel.Name) and, if available, confirm
the qualifier (sel.X) is a DB-like receiver via type info (pkg.TypesInfo) or
known var names before consulting dbExecMethods; update the logic that
references the case *ast.Ident, fn.Name, and dbExecMethods accordingly.
In `@cmd/bifrostlint/main.go`:
- Line 5: The usage comment in cmd/bifrostlint/main.go is out of sync: the
current comment "bifrostlint ./... # runs sqlmatview + sqldialect" omits the
sqlconcurrent analyzer that is actually included (see the analyzer list around
the analyzer registration in the file). Update that single-line usage comment to
list all default analyzers (sqlmatview, sqldialect, sqlconcurrent) so the usage
text matches the analyzers registered in main (referencing the usage/comment
string near the top of main.go and the analyzer registration block where
sqlconcurrent is added).
---
Nitpick comments:
In `@cmd/bifrostlint/internal/sqlscan/sqlscan.go`:
- Around line 60-65: ContainsKeyword currently recompiles the regex on every
call; change it to memoize compiled *regexp.Regexp instances keyed by the
pattern (built with `(?i)\b` + regexp.QuoteMeta(keyword) + `\b`) in a
package-level cache (use sync.Map or a map+sync.RWMutex) and load-or-store the
compiled regexp (regexp.MustCompile or Compile) before calling MatchString;
apply the same memoization approach to the other regex-based keyword/phrase
check in this file so repeated analyzer hot-path calls reuse compiled regexes.
In `@Makefile`:
- Around line 1516-1522: The lint-bifrost Makefile target body is over the
5-line checkmake recommendation; collapse commands into fewer lines by chaining
related commands with && or using semicolons so the target has at most 5
physical lines while preserving behavior: keep the build step (cd
cmd/bifrostlint && GOWORK=off go build -o ../../bin/bifrostlint .), the analyzer
run (./bin/bifrostlint ./...), and the exportedtests run with baseline
(./bin/bifrostlint exportedtests -baseline=cmd/bifrostlint/baseline.txt ./...)
but combine adjacent echo/build/run lines into single lines under the
lint-bifrost target to reduce total lines.
🪄 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: dac325a4-d0b3-4b32-a658-bb3c37de7ecb
⛔ Files ignored due to path filters (1)
cmd/bifrostlint/go.sumis excluded by!**/*.sum
📒 Files selected for processing (21)
.github/workflows/lint.yml.golangci.yml.pre-commit-config.yamlMakefilecmd/bifrostlint/analyzers/exportedtests/exportedtests.gocmd/bifrostlint/analyzers/sqlconcurrent/sqlconcurrent.gocmd/bifrostlint/analyzers/sqlconcurrent/sqlconcurrent_test.gocmd/bifrostlint/analyzers/sqlconcurrent/testdata/src/concurrent/concurrent.gocmd/bifrostlint/analyzers/sqldialect/sqldialect.gocmd/bifrostlint/analyzers/sqldialect/sqldialect_test.gocmd/bifrostlint/analyzers/sqldialect/testdata/src/dialect/dialect.gocmd/bifrostlint/analyzers/sqlmatview/sqlmatview.gocmd/bifrostlint/analyzers/sqlmatview/sqlmatview_test.gocmd/bifrostlint/analyzers/sqlmatview/testdata/src/matview/matview.gocmd/bifrostlint/go.modcmd/bifrostlint/internal/ignore/ignore.gocmd/bifrostlint/internal/sqlscan/exec.gocmd/bifrostlint/internal/sqlscan/sqlscan.gocmd/bifrostlint/main.goframework/configstore/migrations.goframework/logstore/rdb_postgres_perf_test.go
| jobs: | ||
| golangci: | ||
| name: golangci-lint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.26.2" | ||
| - uses: golangci/golangci-lint-action@v6 | ||
| with: | ||
| version: v2.9.0 | ||
| args: --timeout=5m | ||
| bifrostlint: | ||
| name: bifrostlint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.26.2" | ||
| - name: Run bifrostlint analyzers | ||
| run: make lint-bifrost |
There was a problem hiding this comment.
Set explicit permissions for GITHUB_TOKEN.
Both jobs run without explicit permissions, granting default write access to the workflow token. Restrict permissions to the minimum required scope.
🔒 Proposed fix to add read-only permissions
+permissions:
+ contents: read
+
jobs:
golangci:Based on CodeQL static analysis hint.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| jobs: | |
| golangci: | |
| name: golangci-lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26.2" | |
| - uses: golangci/golangci-lint-action@v6 | |
| with: | |
| version: v2.9.0 | |
| args: --timeout=5m | |
| bifrostlint: | |
| name: bifrostlint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26.2" | |
| - name: Run bifrostlint analyzers | |
| run: make lint-bifrost | |
| permissions: | |
| contents: read | |
| jobs: | |
| golangci: | |
| name: golangci-lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26.2" | |
| - uses: golangci/golangci-lint-action@v6 | |
| with: | |
| version: v2.9.0 | |
| args: --timeout=5m | |
| bifrostlint: | |
| name: bifrostlint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26.2" | |
| - name: Run bifrostlint analyzers | |
| run: make lint-bifrost |
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 11-22: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}
[warning] 23-33: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}
🤖 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 @.github/workflows/lint.yml around lines 9 - 33, The workflow grants the
default write GITHUB_TOKEN; add explicit minimal permissions to avoid excess
write access by adding a permissions block (e.g., permissions: contents: read)
for the jobs in .github/workflows/lint.yml—specifically add a permissions: {
contents: read } under the golangci job and the bifrostlint job (or add a
top-level permissions key to apply to all jobs) so the golangci and bifrostlint
jobs only have read-only access needed for checkout and linting.
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.26.2" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest stable Go version released as of May 2026?
💡 Result:
As of May 17, 2026, the latest stable version of the Go programming language is Go 1.26.3 [1][2]. This version was released on May 7, 2026 [3].
Citations:
Update Go version to 1.26.3 (latest stable release).
Go 1.26.2 is released, but Go 1.26.3 is now the latest stable version as of May 2026 (released May 7, 2026). Update the workflow to use 1.26.3 instead.
🤖 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 @.github/workflows/lint.yml at line 17, Update the Go version in the CI
workflow by changing the go-version value used in the lint job from "1.26.2" to
"1.26.3"; locate the go-version key in the workflow (the go-version: "1.26.2"
entry) and replace it with "1.26.3" so the workflow uses the latest stable Go
release.
| - uses: golangci/golangci-lint-action@v6 | ||
| with: | ||
| version: v2.9.0 |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest version of golangci-lint as of May 2026?
💡 Result:
As of May 17, 2026, the latest version of golangci-lint is v2.12.2, which was released on May 6, 2026 [1][2][3].
Citations:
- 1: https://github.com/golangci/golangci-lint/releases/tag/v2.12.2
- 2: https://golangci-lint.run/docs/product/changelog/
- 3: https://github.com/golangci/golangci-lint
Update golangci-lint to the latest version.
The workflow currently uses golangci/golangci-lint-action@v6 with version: v2.9.0. This is outdated; as of May 2026, the latest version is v2.12.2. Update to the current version to benefit from bug fixes and improvements.
🤖 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 @.github/workflows/lint.yml around lines 18 - 20, Update the golangci-lint
GitHub Action and its tool version: replace the action reference
golangci/golangci-lint-action@v6 with the newer action tag and change the tool
version field currently set to "v2.9.0" to "v2.12.2" so the workflow uses the
latest golangci-lint release; ensure both the action ref and the version: value
are updated together to "v2.12.2".
| - repo: local | ||
| hooks: | ||
| - id: bifrostlint-go-filenames | ||
| name: bifrostlint - go filenames have no underscores except _test.go | ||
| language: system | ||
| entry: bash -c 'failed=0; for f in "$@"; do base=$(basename "$f" .go); case "$f" in *_test.go) continue;; esac; case "$base" in *_*) echo "$f: underscores not allowed in Go filenames (see AGENTS.md Code Style)"; failed=1;; esac; done; exit $failed' -- | ||
| types: [go] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Validate the .pre-commit-config.yaml syntax
yamllint .pre-commit-config.yaml 2>&1 || trueRepository: maximhq/bifrost
Length of output: 904
Fix YAML syntax error in the entry field.
Line 37 contains a YAML syntax error at position 146: "mapping values are not allowed here". The bash one-liner in the entry field must be properly quoted to avoid YAML parser conflicts. The line also exceeds the 80-character limit.
The fix requires wrapping the entry value in double quotes and escaping any internal double quotes (or use YAML's block literal syntax with | or |-):
entry: "bash -c 'failed=0; for f in \"$@\"; do base=$(basename \"$f\" .go); case \"$f\" in *_test.go) continue;; esac; case \"$base\" in *_*) echo \"$f: underscores not allowed in Go filenames (see AGENTS.md Code Style)\"; failed=1;; esac; done; exit $failed' --"Or use YAML block literal (cleaner):
entry: |
bash -c 'failed=0; for f in "$@"; do base=$(basename "$f" .go); case "$f" in *_test.go) continue;; esac; case "$base" in *_*) echo "$f: underscores not allowed in Go filenames (see AGENTS.md Code Style)"; failed=1;; esac; done; exit $failed' --🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 37-37: syntax error: mapping values are not allowed here
(syntax)
🤖 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 @.pre-commit-config.yaml around lines 32 - 38, The YAML parser error comes
from the unquoted bash one-liner in the entry field for the hook id
bifrostlint-go-filenames; update the entry value to be a proper YAML string or
block literal to avoid mapping/quote conflicts and long-line issues: either wrap
the entire command in double quotes and escape internal double quotes, or
replace the scalar with a block literal (entry: |) and place the bash -c '...
--' script on the next line; ensure the modified entry references the same bash
one-liner logic (failed loop, basename, *_test.go skip, underscore check, exit
$failed) but is correctly quoted/escaped so the YAML parser accepts it and the
line-length problem is resolved.
| func collectReferenced( | ||
| file *ast.File, | ||
| info *types.Info, | ||
| pkgPath string, | ||
| referenced map[string]bool, | ||
| ) { | ||
| if file == nil { | ||
| return | ||
| } | ||
| ast.Inspect(file, func(n ast.Node) bool { | ||
| switch x := n.(type) { | ||
| case *ast.Ident: | ||
| if info == nil { | ||
| return true | ||
| } | ||
| obj := info.Uses[x] | ||
| if obj == nil { | ||
| return true | ||
| } | ||
| if obj.Pkg() == nil { | ||
| return true | ||
| } | ||
| recordRef(obj, referenced) | ||
| case *ast.SelectorExpr: | ||
| if info == nil { | ||
| return true | ||
| } | ||
| if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil { | ||
| recordRef(obj, referenced) | ||
| } | ||
| } | ||
| return true | ||
| }) | ||
| _ = pkgPath | ||
| } |
There was a problem hiding this comment.
Constrain test references to the package under test.
collectReferenced currently records every referenced object from any _test.go file, while pkgPath is unused (see Line 273). That allows unrelated package tests to satisfy coverage for a symbol, which weakens the rule’s stated same-package/foo_test contract.
💡 Proposed fix
func collectReferenced(
file *ast.File,
info *types.Info,
pkgPath string,
referenced map[string]bool,
) {
if file == nil {
return
}
ast.Inspect(file, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.Ident:
if info == nil {
return true
}
obj := info.Uses[x]
if obj == nil {
return true
}
if obj.Pkg() == nil {
return true
}
+ objPkg := strings.TrimSuffix(strings.TrimSuffix(obj.Pkg().Path(), "_test"), ".test")
+ if objPkg != pkgPath {
+ return true
+ }
recordRef(obj, referenced)
case *ast.SelectorExpr:
if info == nil {
return true
}
- if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil {
+ if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil {
+ objPkg := strings.TrimSuffix(strings.TrimSuffix(obj.Pkg().Path(), "_test"), ".test")
+ if objPkg != pkgPath {
+ return true
+ }
recordRef(obj, referenced)
}
}
return true
})
- _ = pkgPath
}🤖 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 `@cmd/bifrostlint/analyzers/exportedtests/exportedtests.go` around lines 240 -
274, collectReferenced is recording symbols from any package because pkgPath is
ignored; change it to only call recordRef when the referenced object's package
path equals the package-under-test (use pkgPath). In both places where you get
obj (the *types.Obj from info.Uses inside collectReferenced for *ast.Ident and
*ast.SelectorExpr), add a guard that obj.Pkg() != nil && obj.Pkg().Path() ==
pkgPath before calling recordRef(referenced), so only same-package references
are recorded; keep using the existing recordRef and referenced map.
| case *ast.Ident: | ||
| return dbExecMethods[fn.Name] | ||
| } |
There was a problem hiding this comment.
Narrow direct identifier matching to avoid cross-analyzer false positives.
Treating *ast.Ident as DB execution for names like Get/Select can flag unrelated helper calls and cascade noisy findings in both SQL analyzers.
Suggested fix
func isDBExecCall(call *ast.CallExpr) bool {
switch fn := call.Fun.(type) {
case *ast.SelectorExpr:
return dbExecMethods[fn.Sel.Name]
case *ast.Ident:
- return dbExecMethods[fn.Name]
+ // Keep direct function support only for unambiguous execution names.
+ switch fn.Name {
+ case "Exec", "ExecContext", "MustExec", "Query", "QueryContext", "QueryRow", "QueryRowContext":
+ return true
+ default:
+ return false
+ }
}
return false
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case *ast.Ident: | |
| return dbExecMethods[fn.Name] | |
| } | |
| case *ast.Ident: | |
| // Keep direct function support only for unambiguous execution names. | |
| switch fn.Name { | |
| case "Exec", "ExecContext", "MustExec", "Query", "QueryContext", "QueryRow", "QueryRowContext": | |
| return true | |
| default: | |
| return false | |
| } | |
| } |
🤖 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 `@cmd/bifrostlint/internal/sqlscan/exec.go` around lines 43 - 45, The current
switch treats any bare *ast.Ident as a DB exec call (using
dbExecMethods[fn.Name]), causing false positives; restrict detection to selector
calls only by removing or disabling the *ast.Ident branch and instead match on
*ast.SelectorExpr (use sel.Sel.Name) and, if available, confirm the qualifier
(sel.X) is a DB-like receiver via type info (pkg.TypesInfo) or known var names
before consulting dbExecMethods; update the logic that references the case
*ast.Ident, fn.Name, and dbExecMethods accordingly.
| // | ||
| // Usage: | ||
| // | ||
| // bifrostlint ./... # runs sqlmatview + sqldialect |
There was a problem hiding this comment.
Usage text is out of sync with the actual default analyzer set.
Line 5 says bifrostlint ./... runs only sqlmatview + sqldialect, but Lines 29-33 also include sqlconcurrent. Update the usage comment to prevent operator confusion.
Suggested patch
-// bifrostlint ./... # runs sqlmatview + sqldialect
+// bifrostlint ./... # runs sqlconcurrent + sqlmatview + sqldialect📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // bifrostlint ./... # runs sqlmatview + sqldialect | |
| // bifrostlint ./... # runs sqlconcurrent + sqlmatview + sqldialect |
🤖 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 `@cmd/bifrostlint/main.go` at line 5, The usage comment in
cmd/bifrostlint/main.go is out of sync: the current comment "bifrostlint ./...
# runs sqlmatview + sqldialect" omits the sqlconcurrent analyzer that is
actually included (see the analyzer list around the analyzer registration in the
file). Update that single-line usage comment to list all default analyzers
(sqlmatview, sqldialect, sqlconcurrent) so the usage text matches the analyzers
registered in main (referencing the usage/comment string near the top of main.go
and the analyzer registration block where sqlconcurrent is added).
Confidence Score: 4/5Safe to merge; the new linter tool runs entirely at analysis time and has no runtime effect on the application. All four analyzers and their tests look correct. The only concerns are housekeeping items in exportedtests.go (dead exportedFile map, unused pkgPath parameter, empty if block) and sqlscan.go (uncached regexes, dead ContainsPhrase). None affect the tool's correctness today. cmd/bifrostlint/analyzers/exportedtests/exportedtests.go (dead code); cmd/bifrostlint/internal/sqlscan/sqlscan.go (uncached regexes, dead ContainsPhrase) Important Files Changed
Reviews (1): Last reviewed commit: "linter" | Re-trigger Greptile |
| } | ||
|
|
||
| exported := map[string]token.Pos{} | ||
| exportedFile := map[string]*ast.File{} |
There was a problem hiding this comment.
exportedFile map is allocated and written but never read
exportedFile is populated in three places inside collectExported (lines 172, 188, 199) but nothing in Run ever reads it. It consumes memory proportional to the number of exported symbols across all packages and has no effect on the tool's output. Either use it for richer diagnostics or drop it (along with the exportedFile parameter from collectExported).
| func collectReferenced( | ||
| file *ast.File, | ||
| info *types.Info, | ||
| pkgPath string, | ||
| referenced map[string]bool, | ||
| ) { | ||
| if file == nil { | ||
| return | ||
| } | ||
| ast.Inspect(file, func(n ast.Node) bool { | ||
| switch x := n.(type) { | ||
| case *ast.Ident: | ||
| if info == nil { | ||
| return true | ||
| } | ||
| obj := info.Uses[x] | ||
| if obj == nil { | ||
| return true | ||
| } | ||
| if obj.Pkg() == nil { | ||
| return true | ||
| } | ||
| recordRef(obj, referenced) | ||
| case *ast.SelectorExpr: | ||
| if info == nil { | ||
| return true | ||
| } | ||
| if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil { | ||
| recordRef(obj, referenced) | ||
| } | ||
| } | ||
| return true | ||
| }) | ||
| _ = pkgPath | ||
| } |
There was a problem hiding this comment.
pkgPath is passed into collectReferenced but is immediately discarded with _ = pkgPath. The parameter serves no purpose and should be removed from the signature.
| func collectReferenced( | |
| file *ast.File, | |
| info *types.Info, | |
| pkgPath string, | |
| referenced map[string]bool, | |
| ) { | |
| if file == nil { | |
| return | |
| } | |
| ast.Inspect(file, func(n ast.Node) bool { | |
| switch x := n.(type) { | |
| case *ast.Ident: | |
| if info == nil { | |
| return true | |
| } | |
| obj := info.Uses[x] | |
| if obj == nil { | |
| return true | |
| } | |
| if obj.Pkg() == nil { | |
| return true | |
| } | |
| recordRef(obj, referenced) | |
| case *ast.SelectorExpr: | |
| if info == nil { | |
| return true | |
| } | |
| if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil { | |
| recordRef(obj, referenced) | |
| } | |
| } | |
| return true | |
| }) | |
| _ = pkgPath | |
| } | |
| func collectReferenced( | |
| file *ast.File, | |
| info *types.Info, | |
| referenced map[string]bool, | |
| ) { | |
| if file == nil { | |
| return | |
| } | |
| ast.Inspect(file, func(n ast.Node) bool { | |
| switch x := n.(type) { | |
| case *ast.Ident: | |
| if info == nil { | |
| return true | |
| } | |
| obj := info.Uses[x] | |
| if obj == nil { | |
| return true | |
| } | |
| if obj.Pkg() == nil { | |
| return true | |
| } | |
| recordRef(obj, referenced) | |
| case *ast.SelectorExpr: | |
| if info == nil { | |
| return true | |
| } | |
| if obj := info.Uses[x.Sel]; obj != nil && obj.Pkg() != nil { | |
| recordRef(obj, referenced) | |
| } | |
| } | |
| return true | |
| }) | |
| } |
| func ContainsKeyword(s, keyword string) bool { | ||
| // build a word-bounded matcher; cache via a small map per token would help | ||
| // but the analyzer pass count is small. | ||
| re := regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(keyword) + `\b`) | ||
| return re.MatchString(s) | ||
| } |
There was a problem hiding this comment.
Regex recompiled on every
ContainsKeyword and ContainsPhrase call
Both functions call regexp.MustCompile inline on every invocation. ContainsAnyKeyword calls ContainsKeyword once per needle, and sqldialect passes four fixed needles for every string literal in every file. The code comment acknowledges the missing cache but defers it. A simple sync.Map keyed on the pattern string would eliminate the overhead. ContainsPhrase is also never called anywhere in the current codebase and appears to be dead code.
| if hasLoadErrors { | ||
| // Continue - load errors are often noise in multi-module repos | ||
| } |
There was a problem hiding this comment.
Empty
if hasLoadErrors block is confusing
The block has no executable body; the intent is conveyed only by the comment inside it. An empty if reads as accidental missing code to reviewers and linters alike. The comment can be moved to an unconditional position above the block, or the block dropped entirely.

Summary
Introduces
bifrostlint, a custom static analysis tool for Bifrost-specific code quality rules, along with a GitHub Actions CI workflow and golangci-lint configuration to enforce them automatically on PRs and pushes.Changes
cmd/bifrostlintas a standalone Go module containing threego/analysis-based analyzers:sqlconcurrent: FlagsCREATE INDEXstatements missingCONCURRENTLYunless the code path is guarded against Postgres or is inside a migration transaction (whereCONCURRENTLYis invalid).sqlmatview: FlagsCREATE/REFRESH MATERIALIZED VIEWoperations on the boot path unless they run inside a goroutine or a function annotated/named as a background task.sqldialect: Flags Postgres-only SQL tokens (e.g.,CONCURRENTLY,pg_advisory_lock) in string literals that are not gated by a dialect check.exportedtestsas a subcommand ofbifrostlintthat verifies every exported symbol incore/andframework/has at least one test reference, with baseline file support for pre-existing untested symbols.ignore(parses// bifrostlint:ignore <rule> <reason>directives) andsqlscan(regex-based SQL keyword detection and execution-call detection)..golangci.ymlenablingerrcheck,forbidigo,govet,ineffassign,staticcheck, andunused. Theforbidigorule bansjson.Marshal/json.MarshalIndentincore/andframework/, directing callers toschemas.MarshalSortedinstead..github/workflows/lint.ymlrunning bothgolangci-lintandbifrostlinton PRs and pushes todevandmain.bifrostlint-go-filenamespre-commit hook that rejects Go filenames containing underscores (except_test.go).make lint-bifrostandmake lint-bifrost-baselineMakefile targets;make lintnow depends onlint-bifrost.CREATE INDEXcalls inframework/configstore/migrations.goand a test fixture inframework/logstore/rdb_postgres_perf_test.gowith// bifrostlint:ignore sqlconcurrentdirectives explaining whyCONCURRENTLYis intentionally absent (migration transaction context or test setup).Type of change
Affected areas
How to test
The
sqlconcurrent,sqlmatview, andsqldialectanalyzers each have testdata fixtures under their respectivetestdata/src/directories that are exercised byanalysistest.Run.Breaking changes
Related issues
Security considerations
None. This change only adds static analysis tooling and CI enforcement; it does not modify runtime behavior, authentication, secrets handling, or data access.
Checklist
docs/contributing/README.mdand followed the guidelines