Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion helm-charts/bifrost/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: bifrost
description: A Helm chart for deploying Bifrost - AI Gateway with unified interface for multiple providers
type: application
version: 2.0.14
version: 2.0.15
appVersion: "1.4.11"
keywords:
- ai
Expand Down
16 changes: 15 additions & 1 deletion helm-charts/bifrost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,24 @@

Official Helm charts for deploying [Bifrost](https://github.com/maximhq/bifrost) - a high-performance AI gateway with unified interface for multiple providers.

**Latest Version:** 2.0.14
**Latest Version:** 2.0.15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the example image tags alongside the chart bump.

The README now advertises 2.0.15, but several install/upgrade snippets in this file still pin mixed image tags (v1.4.3, v1.5.0, v1.5.2). Either align them with appVersion: 1.4.11 or drop the explicit tag so copy/paste installs do not silently deploy a different app version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@helm-charts/bifrost/README.md` at line 7, The README advertises "Latest
Version: 2.0.15" but install/upgrade snippets still pin mixed image tags (e.g.,
v1.4.3, v1.5.0, v1.5.2); update those snippets to either remove explicit image
tags or change them to match the chart's appVersion (appVersion: 1.4.11) so
copy/paste installs deploy the expected app version—search for the image tag
strings (v1.4.3, v1.5.0, v1.5.2) in README.md and replace them with either no
tag (let the chart default) or with v1.4.11 and ensure the "Latest Version:
2.0.15" header remains correct.


## Changelog

### v2.0.15

- Synced helm schema with transport `config.schema.json` — added missing properties:
- `client.mcpDisableAutoToolInject` — disable automatic MCP tool injection
- `governance.budgets[].calendar_aligned` — snap budget resets to calendar boundaries
- `governance.pricingOverrides` — scoped pricing overrides for the model catalog
- `mcp.clientConfigs[].allowedExtraHeaders` — header allowlist per MCP client
- `mcp.clientConfigs[].allowOnAllVirtualKeys` — make MCP server accessible to all virtual keys
- `mcp.toolManagerConfig.disableAutoToolInject` — disable auto tool injection at manager level
- `networkConfig.beta_header_overrides` — override Anthropic beta header support per provider
- `websocket` — full WebSocket gateway tuning (connections, pool, transcript buffer)
- Fixed SSE `connectionString` not being rendered in `_helpers.tpl` for MCP clients
- Added template rendering for all new properties in `_helpers.tpl`

### v2.0.14

- Added `placement` and `order` fields to custom plugin schema and template rendering
Expand Down
52 changes: 51 additions & 1 deletion helm-charts/bifrost/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ false
{{- if hasKey .Values.bifrost.client "hideDeletedVirtualKeysInFilters" }}
{{- $_ := set $client "hide_deleted_virtual_keys_in_filters" .Values.bifrost.client.hideDeletedVirtualKeysInFilters }}
{{- end }}
{{- if hasKey .Values.bifrost.client "mcpDisableAutoToolInject" }}
{{- $_ := set $client "mcp_disable_auto_tool_inject" .Values.bifrost.client.mcpDisableAutoToolInject }}
{{- end }}
{{- $_ := set $config "client" $client }}
{{- end }}
{{- /* Framework */ -}}
Expand Down Expand Up @@ -354,6 +357,9 @@ false
{{- if .Values.bifrost.governance.providers }}
{{- $_ := set $governance "providers" .Values.bifrost.governance.providers }}
{{- end }}
{{- if .Values.bifrost.governance.pricingOverrides }}
{{- $_ := set $governance "pricing_overrides" .Values.bifrost.governance.pricingOverrides }}
{{- end }}
{{- if .Values.bifrost.governance.authConfig }}
{{- $authConfig := dict }}
{{- if and .Values.bifrost.governance.authConfig.existingSecret .Values.bifrost.governance.authConfig.usernameKey }}
Expand All @@ -376,7 +382,7 @@ false
{{- $_ := set $governance "auth_config" $authConfig }}
{{- end }}
{{- end }}
{{- if or $governance.budgets $governance.rate_limits $governance.customers $governance.teams $governance.virtual_keys $governance.routing_rules $governance.model_configs $governance.providers $governance.auth_config }}
{{- if or $governance.budgets $governance.rate_limits $governance.customers $governance.teams $governance.virtual_keys $governance.routing_rules $governance.model_configs $governance.providers $governance.pricing_overrides $governance.auth_config }}
{{- $_ := set $config "governance" $governance }}
{{- end }}
{{- end }}
Expand Down Expand Up @@ -667,6 +673,10 @@ false
{{- if and (eq $client.connectionType "websocket") $client.websocketConfig }}
{{- $_ := set $cc "connection_string" $client.websocketConfig.url }}
{{- end }}
{{- /* Map connectionString for SSE connections */ -}}
{{- if and (eq $client.connectionType "sse") $client.connectionString }}
{{- $_ := set $cc "connection_string" $client.connectionString }}
{{- end }}
{{- /* Map stdioConfig -> stdio_config */ -}}
{{- if $client.stdioConfig }}
{{- $stdio := dict "command" $client.stdioConfig.command }}
Expand Down Expand Up @@ -709,6 +719,12 @@ false
{{- if $client.toolPricing }}
{{- $_ := set $cc "tool_pricing" $client.toolPricing }}
{{- end }}
{{- if $client.allowedExtraHeaders }}
{{- $_ := set $cc "allowed_extra_headers" $client.allowedExtraHeaders }}
{{- end }}
{{- if hasKey $client "allowOnAllVirtualKeys" }}
{{- $_ := set $cc "allow_on_all_virtual_keys" $client.allowOnAllVirtualKeys }}
{{- end }}
{{- $clientConfigs = append $clientConfigs $cc }}
{{- end }}
{{- $mcpConfig := dict "client_configs" $clientConfigs }}
Expand All @@ -723,6 +739,9 @@ false
{{- if .Values.bifrost.mcp.toolManagerConfig.codeModeBindingLevel }}
{{- $_ := set $tmConfig "code_mode_binding_level" .Values.bifrost.mcp.toolManagerConfig.codeModeBindingLevel }}
{{- end }}
{{- if hasKey .Values.bifrost.mcp.toolManagerConfig "disableAutoToolInject" }}
{{- $_ := set $tmConfig "disable_auto_tool_inject" .Values.bifrost.mcp.toolManagerConfig.disableAutoToolInject }}
{{- end }}
{{- if $tmConfig }}
{{- $_ := set $mcpConfig "tool_manager_config" $tmConfig }}
{{- end }}
Expand Down Expand Up @@ -900,6 +919,37 @@ false
{{- $_ := set $config "audit_logs" $auditLogs }}
{{- end }}
{{- end }}
{{- /* WebSocket Config */ -}}
{{- if .Values.bifrost.websocket }}
{{- $ws := dict }}
{{- if .Values.bifrost.websocket.maxConnectionsPerUser }}
{{- $_ := set $ws "max_connections_per_user" .Values.bifrost.websocket.maxConnectionsPerUser }}
{{- end }}
{{- if .Values.bifrost.websocket.transcriptBufferSize }}
{{- $_ := set $ws "transcript_buffer_size" .Values.bifrost.websocket.transcriptBufferSize }}
{{- end }}
{{- if .Values.bifrost.websocket.pool }}
{{- $pool := dict }}
{{- if .Values.bifrost.websocket.pool.maxIdlePerKey }}
{{- $_ := set $pool "max_idle_per_key" .Values.bifrost.websocket.pool.maxIdlePerKey }}
{{- end }}
{{- if .Values.bifrost.websocket.pool.maxTotalConnections }}
{{- $_ := set $pool "max_total_connections" .Values.bifrost.websocket.pool.maxTotalConnections }}
{{- end }}
{{- if .Values.bifrost.websocket.pool.idleTimeoutSeconds }}
{{- $_ := set $pool "idle_timeout_seconds" .Values.bifrost.websocket.pool.idleTimeoutSeconds }}
{{- end }}
{{- if .Values.bifrost.websocket.pool.maxConnectionLifetimeSeconds }}
{{- $_ := set $pool "max_connection_lifetime_seconds" .Values.bifrost.websocket.pool.maxConnectionLifetimeSeconds }}
{{- end }}
{{- if $pool }}
{{- $_ := set $ws "pool" $pool }}
{{- end }}
{{- end }}
{{- if $ws }}
{{- $_ := set $config "websocket" $ws }}
{{- end }}
{{- end }}
{{- $config | toJson }}
{{- end }}

Expand Down
120 changes: 112 additions & 8 deletions helm-charts/bifrost/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@
"hideDeletedVirtualKeysInFilters": {
"type": "boolean",
"description": "When true, deleted virtual keys are omitted from logs and MCP logs filter data"
},
"mcpDisableAutoToolInject": {
"type": "boolean",
"description": "When true, MCP tools are not automatically injected into requests. Tools are only included when explicitly specified via request context filters or headers."
}
},
"additionalProperties": false
Expand Down Expand Up @@ -449,6 +453,11 @@
"type": "string",
"enum": ["server", "tool"],
"description": "How tools are exposed in VFS for code execution"
},
"disableAutoToolInject": {
"type": "boolean",
"description": "When true, MCP tools are not automatically injected into requests. Tools are only included when explicitly specified.",
"default": false
}
}
},
Expand Down Expand Up @@ -910,6 +919,11 @@
"last_reset": {
"type": "string",
"format": "date-time"
},
"calendar_aligned": {
"type": "boolean",
"description": "Snap resets to calendar boundaries (day/week/month/year start)",
"default": false
}
},
"required": [
Expand Down Expand Up @@ -1213,6 +1227,40 @@
},
"required": ["name"]
}
},
"pricingOverrides": {
"type": "array",
"description": "Scoped pricing overrides applied at runtime by the model catalog",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "Unique pricing override ID" },
"name": { "type": "string", "description": "Human-readable name for this override" },
"scope_kind": {
"type": "string",
"enum": ["global", "provider", "provider_key", "virtual_key", "virtual_key_provider", "virtual_key_provider_key"],
"description": "Scope level for this override"
},
"virtual_key_id": { "type": "string", "description": "Virtual key ID (required for virtual_key* scopes)" },
"provider_id": { "type": "string", "description": "Provider ID (required for provider* scopes)" },
"provider_key_id": { "type": "string", "description": "Provider key ID (required for provider_key and virtual_key_provider_key scopes)" },
"match_type": {
"type": "string",
"enum": ["exact", "wildcard"],
"description": "How the pattern is matched against model names"
},
"pattern": { "type": "string", "description": "Model name pattern to match" },
"request_types": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"description": "Request types this override applies to"
},
"pricing_patch": { "type": "string", "description": "JSON-encoded pricing fields to override" },
"config_hash": { "type": "string", "description": "Internal hash for change detection (auto-managed)" }
},
"required": ["id", "name", "scope_kind", "match_type", "pattern", "request_types"]
}
Comment on lines +1231 to +1263
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce the scope-specific IDs in pricingOverrides.

virtual_key_id, provider_id, and provider_key_id are only documented in descriptions right now. A scope_kind: "virtual_key_provider_key" object with none of those fields still passes schema validation, so Helm will accept an override that cannot be resolved unambiguously.

Suggested schema tightening
             "pricingOverrides": {
               "type": "array",
               "description": "Scoped pricing overrides applied at runtime by the model catalog",
               "items": {
                 "type": "object",
                 "properties": {
                   "id": { "type": "string", "description": "Unique pricing override ID" },
                   "name": { "type": "string", "description": "Human-readable name for this override" },
                   "scope_kind": {
                     "type": "string",
                     "enum": ["global", "provider", "provider_key", "virtual_key", "virtual_key_provider", "virtual_key_provider_key"],
                     "description": "Scope level for this override"
                   },
                   "virtual_key_id": { "type": "string", "description": "Virtual key ID (required for virtual_key* scopes)" },
                   "provider_id": { "type": "string", "description": "Provider ID (required for provider* scopes)" },
                   "provider_key_id": { "type": "string", "description": "Provider key ID (required for provider_key and virtual_key_provider_key scopes)" },
                   "match_type": {
                     "type": "string",
                     "enum": ["exact", "wildcard"],
                     "description": "How the pattern is matched against model names"
                   },
                   "pattern": { "type": "string", "description": "Model name pattern to match" },
                   "request_types": {
                     "type": "array",
                     "minItems": 1,
                     "items": { "type": "string" },
                     "description": "Request types this override applies to"
                   },
                   "pricing_patch": { "type": "string", "description": "JSON-encoded pricing fields to override" },
                   "config_hash": { "type": "string", "description": "Internal hash for change detection (auto-managed)" }
                 },
-                "required": ["id", "name", "scope_kind", "match_type", "pattern", "request_types"]
+                "required": ["id", "name", "scope_kind", "match_type", "pattern", "request_types"],
+                "allOf": [
+                  {
+                    "if": {
+                      "properties": {
+                        "scope_kind": {
+                          "enum": ["provider", "provider_key", "virtual_key_provider", "virtual_key_provider_key"]
+                        }
+                      },
+                      "required": ["scope_kind"]
+                    },
+                    "then": {
+                      "required": ["provider_id"]
+                    }
+                  },
+                  {
+                    "if": {
+                      "properties": {
+                        "scope_kind": {
+                          "enum": ["virtual_key", "virtual_key_provider", "virtual_key_provider_key"]
+                        }
+                      },
+                      "required": ["scope_kind"]
+                    },
+                    "then": {
+                      "required": ["virtual_key_id"]
+                    }
+                  },
+                  {
+                    "if": {
+                      "properties": {
+                        "scope_kind": {
+                          "enum": ["provider_key", "virtual_key_provider_key"]
+                        }
+                      },
+                      "required": ["scope_kind"]
+                    },
+                    "then": {
+                      "required": ["provider_key_id"]
+                    }
+                  }
+                ]
               }
             }
📝 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.

Suggested change
"pricingOverrides": {
"type": "array",
"description": "Scoped pricing overrides applied at runtime by the model catalog",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "Unique pricing override ID" },
"name": { "type": "string", "description": "Human-readable name for this override" },
"scope_kind": {
"type": "string",
"enum": ["global", "provider", "provider_key", "virtual_key", "virtual_key_provider", "virtual_key_provider_key"],
"description": "Scope level for this override"
},
"virtual_key_id": { "type": "string", "description": "Virtual key ID (required for virtual_key* scopes)" },
"provider_id": { "type": "string", "description": "Provider ID (required for provider* scopes)" },
"provider_key_id": { "type": "string", "description": "Provider key ID (required for provider_key and virtual_key_provider_key scopes)" },
"match_type": {
"type": "string",
"enum": ["exact", "wildcard"],
"description": "How the pattern is matched against model names"
},
"pattern": { "type": "string", "description": "Model name pattern to match" },
"request_types": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"description": "Request types this override applies to"
},
"pricing_patch": { "type": "string", "description": "JSON-encoded pricing fields to override" },
"config_hash": { "type": "string", "description": "Internal hash for change detection (auto-managed)" }
},
"required": ["id", "name", "scope_kind", "match_type", "pattern", "request_types"]
}
"pricingOverrides": {
"type": "array",
"description": "Scoped pricing overrides applied at runtime by the model catalog",
"items": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "Unique pricing override ID" },
"name": { "type": "string", "description": "Human-readable name for this override" },
"scope_kind": {
"type": "string",
"enum": ["global", "provider", "provider_key", "virtual_key", "virtual_key_provider", "virtual_key_provider_key"],
"description": "Scope level for this override"
},
"virtual_key_id": { "type": "string", "description": "Virtual key ID (required for virtual_key* scopes)" },
"provider_id": { "type": "string", "description": "Provider ID (required for provider* scopes)" },
"provider_key_id": { "type": "string", "description": "Provider key ID (required for provider_key and virtual_key_provider_key scopes)" },
"match_type": {
"type": "string",
"enum": ["exact", "wildcard"],
"description": "How the pattern is matched against model names"
},
"pattern": { "type": "string", "description": "Model name pattern to match" },
"request_types": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"description": "Request types this override applies to"
},
"pricing_patch": { "type": "string", "description": "JSON-encoded pricing fields to override" },
"config_hash": { "type": "string", "description": "Internal hash for change detection (auto-managed)" }
},
"required": ["id", "name", "scope_kind", "match_type", "pattern", "request_types"],
"allOf": [
{
"if": {
"properties": {
"scope_kind": {
"enum": ["provider", "provider_key", "virtual_key_provider", "virtual_key_provider_key"]
}
},
"required": ["scope_kind"]
},
"then": {
"required": ["provider_id"]
}
},
{
"if": {
"properties": {
"scope_kind": {
"enum": ["virtual_key", "virtual_key_provider", "virtual_key_provider_key"]
}
},
"required": ["scope_kind"]
},
"then": {
"required": ["virtual_key_id"]
}
},
{
"if": {
"properties": {
"scope_kind": {
"enum": ["provider_key", "virtual_key_provider_key"]
}
},
"required": ["scope_kind"]
},
"then": {
"required": ["provider_key_id"]
}
}
]
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@helm-charts/bifrost/values.schema.json` around lines 1231 - 1263, The
pricingOverrides item schema currently only documents virtual_key_id,
provider_id, and provider_key_id but does not enforce them for specific
scope_kind values; add JSON Schema conditional rules under the
pricingOverrides->items object using "if"/"then" (or "oneOf" alternatives) that
require virtual_key_id when scope_kind is "virtual_key" or starts with
"virtual_key", require provider_id when scope_kind is "provider" or includes
"provider", and require provider_key_id for "provider_key" and
"virtual_key_provider_key"; ensure these conditions reference the existing
"scope_kind" discriminator and add appropriate required arrays so an object with
scope_kind "virtual_key_provider_key" cannot validate without all three IDs
(virtual_key_id, provider_id, provider_key_id).

}
},
"additionalProperties": false
Expand Down Expand Up @@ -1599,6 +1647,54 @@
"type": "string"
}
}
},
"websocket": {
"type": "object",
"description": "Optional tuning for the WebSocket gateway (Responses API WebSocket Mode, Realtime API)",
"properties": {
"maxConnectionsPerUser": {
"type": "integer",
"minimum": 1,
"description": "Maximum concurrent WebSocket connections per user",
"default": 100
},
"transcriptBufferSize": {
"type": "integer",
"minimum": 1,
"description": "Number of transcript entries to buffer for Realtime API mid-session fallback",
"default": 100
},
"pool": {
"type": "object",
"description": "Upstream WebSocket connection pool configuration",
"properties": {
"maxIdlePerKey": {
"type": "integer",
"minimum": 1,
"description": "Maximum idle connections per provider/key combination",
"default": 50
},
"maxTotalConnections": {
"type": "integer",
"minimum": 1,
"description": "Maximum total idle connections across all providers",
"default": 1000
},
"idleTimeoutSeconds": {
"type": "integer",
"minimum": 1,
"description": "Seconds before an idle connection is evicted",
"default": 600
},
"maxConnectionLifetimeSeconds": {
"type": "integer",
"minimum": 1,
"description": "Maximum lifetime of a connection in seconds",
"default": 7200
}
}
}
}
}
}
},
Expand Down Expand Up @@ -2513,6 +2609,11 @@
"minimum": 1,
"maximum": 10000,
"description": "Maximum number of TCP connections per provider host. For HTTP/2 (e.g. Bedrock), each connection supports ~100 concurrent streams. Default: 5000."
},
"beta_header_overrides": {
"type": "object",
"additionalProperties": { "type": "boolean" },
"description": "Override default Anthropic beta header support per provider. Keys are header prefixes, values are true (supported) or false (unsupported)."
}
}
},
Expand Down Expand Up @@ -2676,6 +2777,16 @@
"type": "number",
"minimum": 0
}
},
"allowedExtraHeaders": {
"type": "array",
"items": { "type": "string" },
"description": "Allowlist of request-level headers that callers may forward to this MCP server. Use ['*'] to allow all headers."
},
"allowOnAllVirtualKeys": {
"type": "boolean",
"description": "When true, this MCP server is accessible to all virtual keys without requiring explicit per-key assignment.",
"default": false
}
},
"required": [
Expand Down Expand Up @@ -2831,7 +2942,6 @@
"description": "Azure API version"
}
},
Comment thread
akshaydeo marked this conversation as resolved.
"required": ["endpoint"],
"additionalProperties": false
},
"vertex_key_config": {
Expand Down Expand Up @@ -2861,7 +2971,6 @@
"description": "Model to deployment mappings"
}
},
"required": ["project_id", "region"],
"additionalProperties": false
},
"bedrock_key_config": {
Expand Down Expand Up @@ -2909,10 +3018,6 @@
"description": "Exact model name served on this VLLM instance"
}
},
"required": [
"url",
"model_name"
],
"additionalProperties": false
}
},
Expand Down Expand Up @@ -2967,8 +3072,7 @@
]
}
}
],
"required": ["key_id", "name", "value"]
]
}
}
},
Expand Down
23 changes: 22 additions & 1 deletion helm-charts/index.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
apiVersion: v1
entries:
bifrost:
- apiVersion: v2
appVersion: 1.4.11
created: "2026-04-01T12:00:00.000000+00:00"
description: A Helm chart for deploying Bifrost - AI Gateway with unified interface for multiple providers
digest: ""
home: https://www.getmaxim.ai/bifrost
icon: https://www.getmaxim.ai/bifrost/bifrost-logo-only.png
keywords:
- ai
- gateway
- llm
maintainers:
- email: akshay@getmaxim.ai
name: Bifrost Team
name: bifrost
sources:
- https://github.com/maximhq/bifrost
type: application
urls:
- https://maximhq.github.io/bifrost/helm-charts/bifrost-2.0.15.tgz
version: 2.0.15
Comment on lines +4 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
from pathlib import Path

chart_lines = Path("helm-charts/bifrost/Chart.yaml").read_text().splitlines()
index_lines = Path("helm-charts/index.yaml").read_text().splitlines()

def scalar(lines, key):
    prefix = f"{key}:"
    for line in lines:
        stripped = line.strip()
        if stripped.startswith(prefix):
            return stripped.split(":", 1)[1].strip().strip('"')
    return None

def maintainer_email(lines):
    in_maintainers = False
    for line in lines:
        stripped = line.strip()
        if stripped == "maintainers:":
            in_maintainers = True
            continue
        if in_maintainers:
            if stripped.startswith("email:") or stripped.startswith("- email:"):
                return stripped.split(":", 1)[1].strip()
            if stripped and not stripped.startswith("-") and not stripped.startswith("name:") and not stripped.startswith("email:"):
                break
    return None

def extract_index_block(lines, version):
    for i, line in enumerate(lines):
        if line.strip() == f"version: {version}":
            start = i
            while start >= 0 and not lines[start].startswith("  - apiVersion:"):
                start -= 1
            end = i
            while end + 1 < len(lines) and not lines[end + 1].startswith("  - apiVersion:") and not lines[end + 1].startswith("generated:"):
                end += 1
            return lines[start:end + 1]
    raise SystemExit(f"ERROR: version {version} not found in helm-charts/index.yaml")

block = extract_index_block(index_lines, "2.0.15")

chart_email = maintainer_email(chart_lines)
index_email = maintainer_email(block)
chart_icon = scalar(chart_lines, "icon")
index_icon = scalar(block, "icon")
index_digest = scalar(block, "digest")

print("Chart.yaml maintainer email:", chart_email)
print("index.yaml maintainer email:", index_email)
print("Chart.yaml icon:", chart_icon)
print("index.yaml icon:", index_icon)
print("index.yaml digest:", index_digest)

mismatches = []
if chart_email != index_email:
    mismatches.append("maintainer email mismatch")
if chart_icon != index_icon:
    mismatches.append("icon mismatch")
if not index_digest:
    mismatches.append("index digest is empty")

if mismatches:
    for item in mismatches:
        print("ERROR:", item)
    raise SystemExit(1)
PY

pkg="$(find . -name 'bifrost-2.0.15.tgz' -print | head -n 1)"
if [ -z "$pkg" ]; then
  echo "ERROR: packaged chart bifrost-2.0.15.tgz is missing"
  exit 1
fi

echo "Packaged chart found: $pkg"
sha256sum "$pkg"

Repository: maximhq/bifrost

Length of output: 386


Regenerate the 2.0.15 index entry from the packaged chart.

The digest field is empty, and the maintainer email and icon URL in the index entry do not match helm-charts/bifrost/Chart.yaml (index has akshay@getmaxim.ai and getmaxim.ai/bifrost/bifrost-logo-only.png while Chart.yaml has support@getbifrost.ai and getbifrost.ai/favicon.png). This indicates the repo index was hand-edited instead of rebuilt from the actual bifrost-2.0.15.tgz artifact, which can cause published metadata to drift from what users install. If the packaging step occurs elsewhere in this Graphite stack, keep this out-of-sync only temporarily.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@helm-charts/index.yaml` around lines 4 - 24, The index entry for version
2.0.15 was hand-edited and has an empty digest and mismatched metadata;
regenerate the index from the packaged chart artifact (bifrost-2.0.15.tgz) so
the digest is computed and the entry fields (maintainers/email, icon URL, home,
etc.) match the chart’s Chart.yaml values (e.g., support@getbifrost.ai and
getbifrost.ai/favicon.png). Re-run the helm repo index generation against the
chart (or the CI step that produced the package) to replace the manual entry for
version 2.0.15 with the canonical data extracted from the tarball.

- apiVersion: v2
appVersion: 1.4.11
created: "2026-03-20T12:00:00.000000+00:00"
Expand Down Expand Up @@ -544,4 +565,4 @@ entries:
urls:
- https://maximhq.github.io/bifrost/helm-charts/bifrost-1.3.36.tgz
version: 1.3.36
generated: "2026-03-11T12:00:00.000000+00:00"
generated: "2026-04-01T12:00:00.000000+00:00"
Loading