Skip to content

feat: migrate TableVirtualKey.Value from string to SecretVar to support env/vault references - #4504

Merged
akshaydeo merged 1 commit into
devfrom
06-18-feat_vk_as_secretvar
Jun 22, 2026
Merged

feat: migrate TableVirtualKey.Value from string to SecretVar to support env/vault references#4504
akshaydeo merged 1 commit into
devfrom
06-18-feat_vk_as_secretvar

Conversation

@BearTS

@BearTS BearTS commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Virtual key values can now be sourced from environment variables or vault references (env.X / vault.X) rather than only from literal strings. The resolved plaintext is stored in the existing Value column (preserving hash-based lookup and auth), while a new value_source_ref column persists the original reference so the SecretVar shape survives a DB round-trip.

Changes

  • TableVirtualKey structValue is now json:"-" (excluded from direct marshalling). Two new fields are added: ValueSourceRef (persisted, encrypted) stores the env.X/vault.X reference; ValueSource (transient, gorm:"-") holds the runtime SecretVar. Custom MarshalJSON/UnmarshalJSON handle both bare strings and SecretVar objects for the value JSON field, redacting resolved secrets for env/vault sources.
  • BeforeSave / AfterFindValueSourceRef is encrypted alongside Value on write and decrypted on read; AfterFind reconstructs ValueSource from the stored plaintext and reference without re-resolving the live environment, keeping it consistent with the hashed value.
  • Database migrationadd_virtual_key_value_source_ref_column adds the value_source_ref column to governance_virtual_keys. Existing rows default to NULL/"" (literal value); no backfill is needed.
  • UpdateVirtualKeyvalue_source_ref is included in the explicit Select column list so updates persist the reference.
  • GenerateVirtualKeyHashValueSourceRef is now included in the hash so a changed env/vault reference triggers a config resync.
  • mergeGovernanceConfig — Removed the inline env. prefix resolution logic; env/vault references are now resolved during TableVirtualKey.UnmarshalJSON, so Value already holds the plaintext by the time the merge runs. ValueSourceRef and ValueSource are propagated from the existing DB record when the value is carried forward.
  • CreateVirtualKeyRequest — Accepts an optional value field (literal, reference string, or SecretVar object). When omitted, a value is generated server-side as before.
  • Config schemavalue under virtual_keys is updated from type: string to anyOf: [string, object] to accept SecretVar objects.
  • SQLite storeRegisterVaultCallbacks is now called on the SQLite DB at init, consistent with the Postgres path.
  • schemasync ignore list — The value property is added to the ignore list with an explanation of the custom marshalling.
  • UI (governance.ts)VirtualKey.value is typed as string | SecretVar. A resolveVirtualKeyValue helper extracts the usable string from either form. All UI call sites (virtual keys table, MCP usage guide, prompt settings panel, API key selector) use this helper instead of accessing .value directly.
  • Testsvirtualkey_secretvar_test.go covers env-sourced, vault-sourced, and literal round-trips; hash stability across sources; MarshalJSON redaction; and UnmarshalJSON accepting bare strings, reference strings, and SecretVar objects.

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

# Core/Transports
go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/...

# UI
cd ui
pnpm i
pnpm build

To exercise env-sourced virtual keys end-to-end:

  1. Set an environment variable, e.g. export MY_VK=sk-bf-test-abc123.
  2. Create a virtual key via the API with "value": "env.MY_VK" or "value": {"from_env": true, "env_var": "env.MY_VK"}.
  3. Confirm the response shows "value": {"from_env": true, "env_var": "env.MY_VK"} with the resolved secret redacted.
  4. Confirm the x-bf-vk auth path still works with the resolved plaintext value.
  5. Restart the service and confirm the virtual key is reconstructed correctly from the DB without re-reading the environment.

Breaking changes

  • Yes
  • No

The value field on VirtualKey in the API response changes from a bare string to a SecretVar object for env/vault-sourced keys. Consumers that assumed value is always a string will need to handle the object form. Literal-value keys continue to emit a plain string in value.value with no other fields set, so the impact is limited to env/vault-sourced keys. The UI is updated accordingly.

Security considerations

  • Resolved plaintext virtual key values are never emitted in API responses for env/vault-sourced keys; only the reference and source flags are returned.
  • ValueSourceRef is encrypted at rest alongside Value using the same encryption path.
  • AfterFind does not re-resolve the live environment on read, preventing a class of TOCTOU issues where the env var changes after the key is created.

Checklist

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

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9220f4d2-1f90-4160-a672-6332626e424e

📥 Commits

Reviewing files that changed from the base of the PR and between 11373fa and 55c4860.

📒 Files selected for processing (34)
  • core/internal/mcptests/extraheaders_test.go
  • core/schemas/vault_test.go
  • framework/configstore/clientconfig.go
  • framework/configstore/encryption_test.go
  • framework/configstore/migrations_test.go
  • framework/configstore/rdb_deadlock_postgres_test.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/rdb_test.go
  • framework/configstore/sqlite.go
  • framework/configstore/tables/encryption.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/virtualkey.go
  • framework/configstore/tables/virtualkey_secretvar_test.go
  • framework/logstore/asyncjob_test.go
  • framework/postgresconn/postgresconn_test.go
  • plugins/governance/store.go
  • plugins/governance/store_test.go
  • plugins/governance/test_utils.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/governance_test.go
  • transports/bifrost-http/handlers/list_models_vk_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/plugins_test.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • ui/components/prompts/fragments/settingsPanel.tsx
  • ui/lib/types/governance.ts

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Virtual keys now support environment variable and vault references (env.VAR or vault.path format) that resolve to plaintext at load time, in addition to literal values.
    • Virtual key values are now encrypted at rest in the database.
  • Improvements

    • Virtual key value resolution is consistently applied across the UI when displaying and using virtual keys.

Walkthrough

TableVirtualKey.Value is changed from a plain string to schemas.SecretVar, enabling env/vault-backed virtual key values. The model gains vault integration hooks, SecretVar-aware encrypt/decrypt, and hash generation from the resolved plaintext. The API handler, config reconciliation, in-memory store, MCP server cache, and all UI components are updated to use the resolved value via GetValue() / resolveVirtualKeyValue.

Changes

Virtual Key SecretVar Flow

Layer / File(s) Summary
SecretVar contracts: config schema, API request, table model, and UI types
transports/config.schema.json, transports/bifrost-http/handlers/governance.go, framework/configstore/tables/virtualkey.go, ui/lib/types/governance.ts
governance.virtual_keys[].value schema description updates to document literal vs env.X/vault.X references resolved to plaintext; CreateVirtualKeyRequest gains an optional *schemas.SecretVar field; TableVirtualKey.Value changes from string to schemas.SecretVar; UI VirtualKey.value widens to string | SecretVar and exports resolveVirtualKeyValue helper.
TableVirtualKey persistence hooks: hash, vault write, encrypt/decrypt
framework/configstore/tables/virtualkey.go, framework/configstore/sqlite.go, framework/configstore/clientconfig.go, framework/configstore/tables/encryption.go
BeforeSave hashes from Value.GetValue(), writes to owned vault when enabled, and encrypts via encryptSecretVar; AfterFind decrypts via decryptSecretVar; VaultPathKey and VaultStoreSelfManaged methods added; newSqliteConfigStore registers vault callbacks immediately after DB creation; GenerateVirtualKeyHash resolves via GetValue(); EncryptionStatusVault constant removed.
API handler create/rotate and config reconciliation
transports/bifrost-http/handlers/governance.go, transports/bifrost-http/lib/config.go
createVirtualKey wraps the generated key in SecretVar and uses the request-supplied value when set and non-empty; rotateVirtualKeyByID compares plaintext via GetValue(); mergeGovernanceConfig removes explicit env-prefix resolution, validates the resolved plaintext, falls back to the DB SecretVar when the file entry is unset, and generates missing values wrapped in SecretVar.
In-memory store keying and MCP server caching
plugins/governance/store.go, transports/bifrost-http/handlers/mcpserver.go, transports/bifrost-http/server/server.go
rebuildInMemoryStructures, CreateVirtualKeyInMemory, and UpdateVirtualKeyInMemory key virtualKeys by vk.Value.GetValue(); SyncVKMCPServer uses GetValue() for cache lookup/storage and defers map storage until after syncServer completes; ReloadVirtualKey and RemoveVirtualKey pass GetValue() to MCP server deletion.
Plugin redacted-value restoration
transports/bifrost-http/handlers/plugins.go
restoreRedactedValue detects SecretVar-shaped objects and parses incoming strings as SecretVar, conditionally restoring from stored value when the redacted value is not sourced from env or vault.
UI resolveVirtualKeyValue propagation
ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts, ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx, ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx, ui/components/prompts/components/apiKeySelectorView.tsx, ui/components/prompts/fragments/settingsPanel.tsx
All MCP client command/config builders (Claude CLI, Codex, Cursor, Windsurf, VS Code, OpenCode, Antigravity), usage guide masking, virtual key table display/copy, API key selector options, and settings panel VK matching now wrap vk.value with resolveVirtualKeyValue(...) before use.
TableVirtualKey and configstore SecretVar test coverage
framework/configstore/tables/virtualkey_secretvar_test.go, framework/configstore/tables/encryption_test.go, framework/configstore/rdb_test.go, framework/configstore/migrations_test.go, framework/configstore/rdb_deadlock_postgres_test.go, framework/configstore/rdb_mcp_sessions_test.go, framework/logstore/asyncjob_test.go, plugins/governance/store.go, plugins/governance/test_utils.go
New virtualkey_secretvar_test.go covers env-sourced round-trip, literal round-trip, hash stability across env/literal, JSON marshal/unmarshal, and vault rotation re-save behavior; all existing model/store fixtures updated from string literals to schemas.NewSecretVar(...) and assertions updated to use GetValue(); in-memory store tests updated for cache-keying changes.
Handler, config merge, plugins, and fixture integration tests
transports/bifrost-http/lib/config_test.go, transports/bifrost-http/handlers/governance_test.go, transports/bifrost-http/handlers/list_models_vk_test.go, transports/bifrost-http/handlers/plugins_test.go, framework/postgresconn/postgresconn_test.go, core/internal/mcptests/extraheaders_test.go, core/schemas/vault_test.go
Governance handler rotation/quota tests, config merge unit tests, list-models VK tests, and plugin header redaction tests all update virtual key fixtures to use NewSecretVar and assertions to use GetValue() where applicable; postgres DSN and MCP client config tests switch from NewEnvVar to NewSecretVar; vault test updates SecretVar struct initialization for env-sourced headers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • maximhq/bifrost#4157: Both PRs modify framework/configstore/tables/virtualkey.go's TableVirtualKey persistence flow—this PR changes hashing/value resolution via SecretVar and vault callbacks, while #4157 adds vault-backed storage behavior, making them closely coupled at the model level.
  • maximhq/bifrost#3599: Both PRs update the virtual-key rotation flow and its in-memory/MCP cleanup to use the underlying resolved vk.Value.GetValue() (and adjust rotation/reload logic and cache keying) so changing a VK value doesn't leave stale entries behind.
  • maximhq/bifrost#4486: Both PRs modify plugin config redaction restoration logic in transports/bifrost-http/handlers/plugins.go to update how masked credential values are restored, switching from EnvVar to SecretVar patterns.

Suggested reviewers

  • akshaydeo
  • danpiths
  • roroghost17

🐇 Oh the keys had a secret, a vault, and a name,
Now SecretVar wraps them — they're never the same!
GetValue() resolves what the env-var once hid,
SHA256 hashes the plaintext — how fancy we did!
From SQLite to UI, the bunny hops free,
No raw strings remain — just encrypted debris! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: migrating TableVirtualKey.Value from string to SecretVar to support env/vault references. It is clear, specific, and directly related to the primary objective of this PR.
Description check ✅ Passed The PR description comprehensively covers all required sections: Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Security considerations, and Checklist. All key implementation details are documented with clear explanations of design decisions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

Warning

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

🔧 golangci-lint (2.12.2)

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


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

@BearTS BearTS changed the title feat: vk as secretVar feat: support SecretVar (env/vault) as virtual key value with DB persistence, hash stability, and UI resolution Jun 17, 2026
@BearTS
BearTS marked this pull request as ready for review June 17, 2026 19:00
@BearTS
BearTS requested a review from a team as a code owner June 17, 2026 19:00
@coderabbitai
coderabbitai Bot requested review from danpiths and roroghost17 June 17, 2026 19:01
@BearTS
BearTS marked this pull request as draft June 17, 2026 19:04
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 3/5

The in-memory auth path works correctly, but the DB-backed lookup path used by MCP sessions, list-models, and provider queries breaks for any env-sourced VK after its environment variable is rotated — and for API-created env-sourced VKs there is no automatic recovery mechanism.

Two distinct defects affect the DB-backed lookup path and the encryption-status metadata written for env/vault refs. The stale-hash issue (rdb.go) means MCP auth and other DB-lookup callers return ErrNotFound after an env var changes value, with no self-healing path for API-created VKs. The encryption-status mismatch (virtualkey.go) risks silent failures in any re-encryption tooling that trusts that column. Neither defect prevents in-memory governance from working, so the service remains functional for the common case, but the edge conditions are reachable in production.

framework/configstore/rdb.go (GetVirtualKeyByValue hash/fallback logic for env refs) and framework/configstore/tables/virtualkey.go (EncryptionStatus assignment in BeforeSave).

Important Files Changed

Filename Overview
framework/configstore/tables/virtualkey.go Core VK struct migrated from string to SecretVar; BeforeSave correctly hashes resolved value and skips encryption for refs, but unconditionally stamps EncryptionStatus=encrypted even when encryptSecretVar is a no-op for env/vault refs.
framework/configstore/rdb.go GetVirtualKeyByValue/GetVirtualKeyQuotaByValue use SHA-256 hash of the resolved plaintext; stale hash after env-var rotation causes both the hash lookup and the plaintext fallback to fail for env-sourced VKs.
framework/configstore/tables/virtualkey_secretvar_test.go Good coverage of env/vault round-trips, hash stability, MarshalJSON shape, and vault-rotation re-save; BeforeSave(nil) call with a nil tx can panic if vault hooks are registered in a parallel test.
plugins/governance/store.go In-memory map keying updated from vk.Value (string) to vk.Value.GetValue() across Store/Load/Delete sites; changes are consistent and correct.
transports/bifrost-http/lib/config.go mergeGovernanceConfig updated to use SecretVar IsSet/GetValue/IsFromEnv/IsFromVault; correctly skips creating/updating when env/vault ref is unresolved.
transports/bifrost-http/handlers/governance.go CreateVirtualKeyRequest gains optional Value field; handler correctly falls back to generated key when ref resolves to empty; rotateVirtualKeyByID updated consistently.
framework/configstore/clientconfig.go GenerateVirtualKeyHash hashes the resolved value for config-change detection; intentionally excludes the reference itself for backward compatibility.
ui/lib/types/governance.ts VirtualKey.value typed as string
framework/configstore/sqlite.go RegisterVaultCallbacks now called at SQLite init, consistent with the Postgres path.
framework/configstore/tables/encryption.go encryptSecretVar/decryptSecretVar correctly skip env/vault refs via IsFromEnv/IsFromVault guards; safe for all call sites.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Handler
    participant DB
    participant InMem as In-Memory Store

    Note over Handler,DB: Create env-sourced VK
    Client->>Handler: "POST value=env.MY_VK"
    Handler->>Handler: UnmarshalJSON resolves MY_VK to sk-bf-v1
    Handler->>DB: "BeforeSave writes value=env.MY_VK, value_hash=SHA256(sk-bf-v1)"
    DB-->>Handler: OK

    Note over Client,InMem: Auth — in-memory path
    Client->>Handler: x-bf-vk sk-bf-v1
    Handler->>InMem: Load(sk-bf-v1) found

    Note over DB,InMem: After env var rotation MY_VK=sk-bf-v2
    Handler->>DB: GetVirtualKeyByValue(sk-bf-v2)
    DB-->>Handler: "SHA256(v2) != SHA256(v1) AND env.MY_VK != sk-bf-v2 — ErrNotFound"
    Handler->>InMem: Load(sk-bf-v2)
    InMem-->>Handler: found via Scan re-resolution
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Handler
    participant DB
    participant InMem as In-Memory Store

    Note over Handler,DB: Create env-sourced VK
    Client->>Handler: "POST value=env.MY_VK"
    Handler->>Handler: UnmarshalJSON resolves MY_VK to sk-bf-v1
    Handler->>DB: "BeforeSave writes value=env.MY_VK, value_hash=SHA256(sk-bf-v1)"
    DB-->>Handler: OK

    Note over Client,InMem: Auth — in-memory path
    Client->>Handler: x-bf-vk sk-bf-v1
    Handler->>InMem: Load(sk-bf-v1) found

    Note over DB,InMem: After env var rotation MY_VK=sk-bf-v2
    Handler->>DB: GetVirtualKeyByValue(sk-bf-v2)
    DB-->>Handler: "SHA256(v2) != SHA256(v1) AND env.MY_VK != sk-bf-v2 — ErrNotFound"
    Handler->>InMem: Load(sk-bf-v2)
    InMem-->>Handler: found via Scan re-resolution
Loading

Reviews (9): Last reviewed commit: "feat: vk as secretVar" | Re-trigger Greptile

Comment thread framework/configstore/tables/virtualkey.go Outdated
Comment thread framework/configstore/tables/virtualkey_secretvar_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/configstore/tables/virtualkey.go (1)

362-368: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not decrypt an empty value_source_ref on existing encrypted rows.

After the migration, already-encrypted VK rows can have EncryptionStatusEncrypted with an empty new value_source_ref. Line 385 then attempts to decrypt an empty/plain value and can make VK loads fail. Only encrypt/decrypt the source ref when it is non-empty.

Proposed fix
 	if encrypt.IsEnabled() && vk.Value != "" {
 		if err := encryptString(&vk.Value); err != nil {
 			return fmt.Errorf("failed to encrypt virtual key value: %w", err)
 		}
-		if err := encryptString(&vk.ValueSourceRef); err != nil {
-			return fmt.Errorf("failed to encrypt virtual key value source ref: %w", err)
+		if vk.ValueSourceRef != "" {
+			if err := encryptString(&vk.ValueSourceRef); err != nil {
+				return fmt.Errorf("failed to encrypt virtual key value source ref: %w", err)
+			}
 		}
 		vk.EncryptionStatus = EncryptionStatusEncrypted
 	}
 	switch vk.EncryptionStatus {
 	case EncryptionStatusEncrypted:
 		if err := decryptString(&vk.Value); err != nil {
 			return fmt.Errorf("failed to decrypt virtual key value: %w", err)
 		}
-		if err := decryptString(&vk.ValueSourceRef); err != nil {
-			return fmt.Errorf("failed to decrypt virtual key value source ref: %w", err)
+		if vk.ValueSourceRef != "" {
+			if err := decryptString(&vk.ValueSourceRef); err != nil {
+				return fmt.Errorf("failed to decrypt virtual key value source ref: %w", err)
+			}
 		}
 	}

Based on learnings, AfterFind hooks should guard decrypt calls by checking for empty or placeholder values before decrypting.

Also applies to: 380-387

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

In `@framework/configstore/tables/virtualkey.go` around lines 362 - 368, In the
encryption block where vk.Value and vk.ValueSourceRef are encrypted, add a
non-empty check before encrypting vk.ValueSourceRef to prevent attempting to
encrypt empty values. Specifically, modify the encryptString call for
vk.ValueSourceRef to only execute when vk.ValueSourceRef is not empty (change
from encrypting unconditionally to adding a condition like if vk.ValueSourceRef
!= "" before the encryptString call). Additionally, apply the same guard in the
corresponding AfterFind hook decryption block around lines 380-387 to ensure
empty or placeholder values are not decrypted, preventing VK load failures on
already-encrypted rows that have an empty value_source_ref.

Source: Learnings

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

Inline comments:
In `@framework/configstore/clientconfig.go`:
- Around line 812-813: The hash computation for vk.Value and vk.ValueSourceRef
lacks boundary encoding between them, which can cause hash collisions when
different value pairs produce the same byte sequence. Add length-prefixed
encoding or a separator between the hash.Write calls for vk.Value and
vk.ValueSourceRef to disambiguate the hash input and ensure that different
(Value, ValueSourceRef) combinations always produce different hashes, preventing
missed config resync triggers.

In `@framework/configstore/migrations.go`:
- Around line 10835-10840: The Rollback function uses GORM's mg.DropColumn
method to drop the "ValueSourceRef" column from tables.TableVirtualKey, but this
will fail with SQLite foreign key constraint errors during the table rebuild
within a transaction (as documented in the migrationDropVKAccessProfileIDColumn
migration). Replace the mg.DropColumn call with a raw SQL approach using the
dropColumnSQL() helper function, following the same pattern established in
migrationDropVKAccessProfileIDColumn at line 10031, to ensure the column drop
can succeed without triggering FK rebuild failures.

In `@framework/configstore/tables/virtualkey.go`:
- Around line 347-356: The switch statement that sets ValueSourceRef is executed
on every save operation, including when a VirtualKey value is rotated. When a VK
loaded from env or vault is rotated (generating a new value), the ValueSourceRef
should be cleared to empty string to indicate it's no longer sourced from that
external location, rather than re-deriving it from the current ValueSource.
Modify the code to detect when a rotation is occurring (the value is being
changed/generated) and in that rotation path, set ValueSourceRef to an empty
string. The current switch logic that re-derives ValueSourceRef from IsFromEnv()
and IsFromVault() should only execute when the value is NOT being rotated.

In `@transports/bifrost-http/handlers/governance.go`:
- Around line 1314-1317: The code currently assigns the resolved value from
req.Value.GetValue() directly to vk.Value without validating that the resolved
value is non-empty. This can cause unresolved env or vault references to
overwrite the generated credential with an empty string, resulting in an
unusable VK. Modify the assignment block to call GetValue() once, validate that
the result is non-empty (not ""), and only assign to vk.Value and vk.ValueSource
if the resolved value is a non-empty plaintext string. This prevents empty
resolved values from replacing the generated key.

In `@transports/bifrost-http/lib/config.go`:
- Around line 2255-2259: When a virtual key Value fails the prefix check and is
replaced via governance.GenerateVirtualKey(), the ValueSourceRef and ValueSource
fields are not being reset, creating a mismatch between the auto-generated value
and its source metadata. Locate all places where governance.GenerateVirtualKey()
is called to replace an invalid Value (in the config validation logic around the
virtual keys handling), and in each of those locations, reset the ValueSourceRef
and ValueSource fields to empty strings immediately after assigning the newly
generated value to maintain metadata consistency across the SecretVar contract.

In `@transports/config.schema.json`:
- Around line 705-706: The object schema in the anyOf array at lines 705-706
declares allowed properties but does not enforce that at least one resolvable
field must be present, allowing invalid objects like {} or {"from_env": true} to
pass validation. Add a "required" constraint to the object schema definition
that mandates at least one of the actual value/source fields ("value",
"env_var", or "vault_var") must be present, ensuring the SecretVar object always
has a usable value or reference as required by the corresponding type definition
in ui/lib/types/schemas.ts.

In `@ui/components/prompts/components/apiKeySelectorView.tsx`:
- Around line 34-35: The virtual key options are being keyed by their resolved
values (o.value) which can cause React rendering issues if two virtual keys
resolve to identical plaintext values. In the rendering code around lines 69 and
80 where the options from vkOpts are rendered, use a stable unique identifier
(vk.id) as the React key instead of o.value. Additionally, add data-testid
attributes to the ComboboxItem elements for each option to enable E2E test
compatibility per the coding guidelines.

---

Outside diff comments:
In `@framework/configstore/tables/virtualkey.go`:
- Around line 362-368: In the encryption block where vk.Value and
vk.ValueSourceRef are encrypted, add a non-empty check before encrypting
vk.ValueSourceRef to prevent attempting to encrypt empty values. Specifically,
modify the encryptString call for vk.ValueSourceRef to only execute when
vk.ValueSourceRef is not empty (change from encrypting unconditionally to adding
a condition like if vk.ValueSourceRef != "" before the encryptString call).
Additionally, apply the same guard in the corresponding AfterFind hook
decryption block around lines 380-387 to ensure empty or placeholder values are
not decrypted, preventing VK load failures on already-encrypted rows that have
an empty value_source_ref.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f5269d9e-f80a-4b28-94ef-3e4a018af1c8

📥 Commits

Reviewing files that changed from the base of the PR and between ccadc7c and 6f0884f.

📒 Files selected for processing (16)
  • .github/workflows/scripts/schemasync/main.go
  • framework/configstore/clientconfig.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/sqlite.go
  • framework/configstore/tables/virtualkey.go
  • framework/configstore/tables/virtualkey_secretvar_test.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/lib/config.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • ui/components/prompts/fragments/settingsPanel.tsx
  • ui/lib/types/governance.ts

Comment thread framework/configstore/clientconfig.go Outdated
Comment thread framework/configstore/migrations.go Outdated
Comment thread framework/configstore/tables/virtualkey.go Outdated
Comment thread transports/bifrost-http/handlers/governance.go Outdated
Comment thread transports/bifrost-http/lib/config.go Outdated
Comment thread transports/config.schema.json Outdated
Comment thread ui/components/prompts/components/apiKeySelectorView.tsx
@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from 6f0884f to e2f7200 Compare June 18, 2026 05:41
@BearTS
BearTS marked this pull request as ready for review June 18, 2026 06:25
@BearTS BearTS changed the title feat: support SecretVar (env/vault) as virtual key value with DB persistence, hash stability, and UI resolution feat: migrate TableVirtualKey.Value from string to SecretVar to support env/vault references Jun 18, 2026
@coderabbitai
coderabbitai Bot requested a review from akshaydeo June 18, 2026 06:27
@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from e2f7200 to b03fb66 Compare June 18, 2026 07:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@framework/configstore/tables/virtualkey_secretvar_test.go`:
- Around line 78-79: The test calls BeforeSave with a nil argument on both the
literal and envSourced variables, which can panic if VaultStoreWriteEnabled
returns true since the implementation dereferences tx.Statement.Table. Fix this
by either: (1) using setupTestDB(t) to create a proper test database instance
and passing that database to the BeforeSave calls instead of nil, or (2) if
vault functionality must remain disabled for this test, add an explicit comment
above each BeforeSave call documenting that the test assumes vault store is
disabled and explaining why passing nil is safe in this specific context.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4958cc03-afbf-4739-9f90-f86ea3f0fef1

📥 Commits

Reviewing files that changed from the base of the PR and between e2f7200 and b03fb66.

📒 Files selected for processing (27)
  • framework/configstore/clientconfig.go
  • framework/configstore/encryption_test.go
  • framework/configstore/migrations_test.go
  • framework/configstore/rdb_deadlock_postgres_test.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/rdb_test.go
  • framework/configstore/sqlite.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/virtualkey.go
  • framework/configstore/tables/virtualkey_secretvar_test.go
  • framework/logstore/asyncjob_test.go
  • plugins/governance/store.go
  • plugins/governance/store_test.go
  • plugins/governance/test_utils.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/governance_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • ui/components/prompts/fragments/settingsPanel.tsx
  • ui/lib/types/governance.ts
✅ Files skipped from review due to trivial changes (1)
  • transports/config.schema.json
🚧 Files skipped from review as they are similar to previous changes (25)
  • plugins/governance/test_utils.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/logstore/asyncjob_test.go
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • framework/configstore/sqlite.go
  • framework/configstore/encryption_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/governance/store_test.go
  • transports/bifrost-http/handlers/governance.go
  • framework/configstore/clientconfig.go
  • ui/components/prompts/fragments/settingsPanel.tsx
  • framework/configstore/migrations_test.go
  • plugins/governance/store.go
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/lib/types/governance.ts
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • transports/bifrost-http/handlers/governance_test.go
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • framework/configstore/rdb_deadlock_postgres_test.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/virtualkey.go
  • framework/configstore/rdb_test.go
  • transports/bifrost-http/lib/config_test.go

Comment thread framework/configstore/tables/virtualkey_secretvar_test.go
Comment thread framework/configstore/tables/virtualkey.go
@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Got it — you've already flagged this as a breaking change in the PR description, and the UI has been updated to handle both forms via resolveVirtualKeyValue(). If this format change is acceptable to your consumers, that's your call to make.

Just worth confirming: for literal-value keys, does MarshalJSON still emit a bare string (e.g. "value": "bf-vk-XXXXX") or does it always wrap in the SecretVar object shape? The PR description says literal keys continue to emit a plain string, but I want to make sure that's actually wired up in the custom marshaller so the breaking change surface is minimal.

@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from b03fb66 to 1d04c33 Compare June 18, 2026 09:01
Comment thread transports/bifrost-http/handlers/governance.go
@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from 1d04c33 to f5f2c18 Compare June 21, 2026 19:36
@BearTS
BearTS force-pushed the 06-15-chore_update_config_schema_and_helm branch from ccadc7c to 46072a4 Compare June 21, 2026 19:36
Comment thread transports/bifrost-http/lib/config.go
@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from f5f2c18 to 8788a96 Compare June 21, 2026 20:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@transports/bifrost-http/handlers/plugins.go`:
- Around line 619-624: The condition in the existing value check does not
properly account for vault references. Currently, the condition checks `if
!secretVal.IsFromEnv() && secretVal.IsRedacted()` but this incorrectly treats
vault references as redacted plain strings (since IsRedacted returns true for
both environment and vault references). Update the condition to also exclude
vault references by adding an additional check for `IsFromVault()` so that vault
references like vault.NEW_SECRET are passed through instead of being restored to
the existing value.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9667828f-4b32-4525-be98-dd8181cf4291

📥 Commits

Reviewing files that changed from the base of the PR and between f5f2c18 and 8788a96.

📒 Files selected for processing (34)
  • core/internal/mcptests/extraheaders_test.go
  • core/schemas/vault_test.go
  • framework/configstore/clientconfig.go
  • framework/configstore/encryption_test.go
  • framework/configstore/migrations_test.go
  • framework/configstore/rdb_deadlock_postgres_test.go
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/rdb_test.go
  • framework/configstore/sqlite.go
  • framework/configstore/tables/encryption.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/virtualkey.go
  • framework/configstore/tables/virtualkey_secretvar_test.go
  • framework/logstore/asyncjob_test.go
  • framework/postgresconn/postgresconn_test.go
  • plugins/governance/store.go
  • plugins/governance/store_test.go
  • plugins/governance/test_utils.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/governance_test.go
  • transports/bifrost-http/handlers/list_models_vk_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/plugins_test.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • ui/components/prompts/fragments/settingsPanel.tsx
  • ui/lib/types/governance.ts
💤 Files with no reviewable changes (1)
  • framework/configstore/tables/encryption.go
✅ Files skipped from review due to trivial changes (3)
  • core/schemas/vault_test.go
  • transports/config.schema.json
  • framework/postgresconn/postgresconn_test.go
🚧 Files skipped from review as they are similar to previous changes (26)
  • framework/configstore/sqlite.go
  • framework/configstore/encryption_test.go
  • plugins/governance/test_utils.go
  • ui/components/prompts/fragments/settingsPanel.tsx
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/mcpUsageGuideSheet.tsx
  • core/internal/mcptests/extraheaders_test.go
  • framework/configstore/clientconfig.go
  • plugins/governance/store_test.go
  • framework/configstore/rdb_deadlock_postgres_test.go
  • framework/configstore/migrations_test.go
  • ui/lib/types/governance.ts
  • framework/logstore/asyncjob_test.go
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • transports/bifrost-http/server/server.go
  • ui/components/prompts/components/apiKeySelectorView.tsx
  • framework/configstore/rdb_mcp_sessions_test.go
  • framework/configstore/tables/virtualkey_secretvar_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • framework/configstore/tables/virtualkey.go
  • transports/bifrost-http/handlers/governance.go
  • plugins/governance/store.go
  • framework/configstore/tables/encryption_test.go
  • ui/app/workspace/mcp-registry/views/mcpUsageGuide/commandBuilders.ts
  • framework/configstore/rdb_test.go
  • transports/bifrost-http/handlers/governance_test.go
  • transports/bifrost-http/lib/config_test.go

Comment thread transports/bifrost-http/handlers/plugins.go
@BearTS
BearTS force-pushed the 06-18-feat_vk_as_secretvar branch from 8788a96 to 11373fa Compare June 21, 2026 20:56
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 21, 2026
Comment thread framework/configstore/clientconfig.go

akshaydeo commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • Jun 22, 4:29 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 22, 4:37 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 22, 4:38 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from 06-15-chore_update_config_schema_and_helm to graphite-base/4504 June 22, 2026 16:33
@akshaydeo
akshaydeo changed the base branch from graphite-base/4504 to dev June 22, 2026 16:36
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review June 22, 2026 16:36

The base branch was changed.

@akshaydeo
akshaydeo force-pushed the 06-18-feat_vk_as_secretvar branch from 399ab83 to 55c4860 Compare June 22, 2026 16:36
@akshaydeo
akshaydeo merged commit 3aad4bf into dev Jun 22, 2026
14 of 15 checks passed
@akshaydeo
akshaydeo deleted the 06-18-feat_vk_as_secretvar branch June 22, 2026 16:38
akshaydeo pushed a commit that referenced this pull request Jun 24, 2026
… support env/vault references (#4504)

## Summary

Virtual key values can now be sourced from environment variables or vault references (`env.X` / `vault.X`) rather than only from literal strings. The resolved plaintext is stored in the existing `Value` column (preserving hash-based lookup and auth), while a new `value_source_ref` column persists the original reference so the `SecretVar` shape survives a DB round-trip.

## Changes

- **`TableVirtualKey` struct** — `Value` is now `json:"-"` (excluded from direct marshalling). Two new fields are added: `ValueSourceRef` (persisted, encrypted) stores the `env.X`/`vault.X` reference; `ValueSource` (transient, `gorm:"-"`) holds the runtime `SecretVar`. Custom `MarshalJSON`/`UnmarshalJSON` handle both bare strings and `SecretVar` objects for the `value` JSON field, redacting resolved secrets for env/vault sources.
- **`BeforeSave` / `AfterFind`** — `ValueSourceRef` is encrypted alongside `Value` on write and decrypted on read; `AfterFind` reconstructs `ValueSource` from the stored plaintext and reference without re-resolving the live environment, keeping it consistent with the hashed value.
- **Database migration** — `add_virtual_key_value_source_ref_column` adds the `value_source_ref` column to `governance_virtual_keys`. Existing rows default to `NULL`/`""` (literal value); no backfill is needed.
- **`UpdateVirtualKey`** — `value_source_ref` is included in the explicit `Select` column list so updates persist the reference.
- **`GenerateVirtualKeyHash`** — `ValueSourceRef` is now included in the hash so a changed env/vault reference triggers a config resync.
- **`mergeGovernanceConfig`** — Removed the inline `env.` prefix resolution logic; env/vault references are now resolved during `TableVirtualKey.UnmarshalJSON`, so `Value` already holds the plaintext by the time the merge runs. `ValueSourceRef` and `ValueSource` are propagated from the existing DB record when the value is carried forward.
- **`CreateVirtualKeyRequest`** — Accepts an optional `value` field (literal, reference string, or `SecretVar` object). When omitted, a value is generated server-side as before.
- **Config schema** — `value` under `virtual_keys` is updated from `type: string` to `anyOf: [string, object]` to accept `SecretVar` objects.
- **SQLite store** — `RegisterVaultCallbacks` is now called on the SQLite DB at init, consistent with the Postgres path.
- **`schemasync` ignore list** — The `value` property is added to the ignore list with an explanation of the custom marshalling.
- **UI (`governance.ts`)** — `VirtualKey.value` is typed as `string | SecretVar`. A `resolveVirtualKeyValue` helper extracts the usable string from either form. All UI call sites (virtual keys table, MCP usage guide, prompt settings panel, API key selector) use this helper instead of accessing `.value` directly.
- **Tests** — `virtualkey_secretvar_test.go` covers env-sourced, vault-sourced, and literal round-trips; hash stability across sources; `MarshalJSON` redaction; and `UnmarshalJSON` accepting bare strings, reference strings, and `SecretVar` objects.

## Type of change

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

## Affected areas

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

## How to test

```sh
# Core/Transports
go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/...

# UI
cd ui
pnpm i
pnpm build
```

To exercise env-sourced virtual keys end-to-end:
1. Set an environment variable, e.g. `export MY_VK=sk-bf-test-abc123`.
2. Create a virtual key via the API with `"value": "env.MY_VK"` or `"value": {"from_env": true, "env_var": "env.MY_VK"}`.
3. Confirm the response shows `"value": {"from_env": true, "env_var": "env.MY_VK"}` with the resolved secret redacted.
4. Confirm the `x-bf-vk` auth path still works with the resolved plaintext value.
5. Restart the service and confirm the virtual key is reconstructed correctly from the DB without re-reading the environment.

## Breaking changes

- [x] Yes
- [ ] No

The `value` field on `VirtualKey` in the API response changes from a bare string to a `SecretVar` object for env/vault-sourced keys. Consumers that assumed `value` is always a string will need to handle the object form. Literal-value keys continue to emit a plain string in `value.value` with no other fields set, so the impact is limited to env/vault-sourced keys. The UI is updated accordingly.

## Security considerations

- Resolved plaintext virtual key values are never emitted in API responses for env/vault-sourced keys; only the reference and source flags are returned.
- `ValueSourceRef` is encrypted at rest alongside `Value` using the same encryption path.
- `AfterFind` does not re-resolve the live environment on read, preventing a class of TOCTOU issues where the env var changes after the key is created.

## Checklist

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants