feat: fetch providers over DRPC - #26650
Conversation
334315a to
9973338
Compare
c2488d9 to
b9c4565
Compare
9973338 to
06af6f1
Compare
b9c4565 to
5dad084
Compare
06af6f1 to
83efd04
Compare
5dad084 to
af3a8af
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
af3a8af to
bb14ffb
Compare
|
/coder-agents-review |
|
Chat: Spend limit reached | View chat Review historydeep-review v0.9.0 | Round 4 | Last posted: Round 4, 14 findings (2 P2, 5 P3, 7 Nit), COMMENT. Review Finding inventoryFindings
Round logRound 1Panel. 2 P2, 3 P3, 6 Nit. 5 dropped (3 Note, 1 scope, 1 negligible). Reviewed against 83efd044efc9..bb14ffbddc0f. Round 2BLOCKED. CRF-1, CRF-3, CRF-4, CRF-6, CRF-7, CRF-8, CRF-9, CRF-10, CRF-11 addressed. CRF-2 (P2) and CRF-5 (P3) silent. No review. Round 3Panel. CRF-2 and CRF-5 addressed. All R1 findings resolved. Netero found CRF-17 (P3) and CRF-18 (Nit). Panel found CRF-19 (P3). No regressions from R1 fixes. Reviewed against e01b0a6a7b5a..e769b460c917. Round 4All R3 findings addressed. CRF-17 replaced with production code. CRF-18 comment fixed. CRF-19 test moved to PR #26605. Netero clean (1 dead-code finding dropped: NewWebsocketDialer consumer in stacked PR #26605). Approved. Reviewed against 4820cbf..8f1d264. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
Clean, well-scoped refactor. The DB-to-DRPC migration is symmetric across embedded and standalone paths, the proto surface is minimal, dead code is cleanly removed, and the concurrency test against real Postgres advisory locks is solid. Test density is healthy at 44%.
As Knov put it: "Good perimeter: disabled providers withhold keys and settings at the server, not the client."
Severity breakdown: 2 P2, 3 P3, 6 Nit.
The two P2s: (1) the embedded daemon can now start serving with an empty provider pool if the pubsub subscription or initial reload fails, losing the old boot-time fallback that guaranteed providers were loaded before the daemon accepted requests; (2) the DRPCServer interface was not updated to include DRPCProviderConfiguratorServer, so the compile-time assertion and any future mock targeting that interface silently omits the new service.
Observations not posted as findings: the advisory lock uses pg_advisory_xact_lock (exclusive) where pg_advisory_xact_lock_shared would let concurrent reads proceed without serializing against each other. At current call rates this has no impact, but worth noting before AIGOV-465 adds refresh-on-reseed. Server-side row skipping for corrupt settings is an intentional tradeoff (one corrupt blob does not break the fleet); the observability gap (gateway metrics cannot see the skip) is acceptable since the server-side Error log is the correct signal.
coderd/aibridged/server.go:5
P2 [CRF-2] DRPCServer embeds DRPCRecorderServer, DRPCMCPConfiguratorServer, and DRPCAuthorizerServer, but not DRPCProviderConfiguratorServer. The client-side DRPCClient (client.go:17) was updated to embed DRPCProviderConfiguratorClient, but the server-side union was not.
"Every consumer that type-asserts against DRPCServer silently misses the new service. If a future mock or alternative implementation targets DRPCServer, it will silently omit GetAIProviders." (Ryosuke)
The compile-time assertion at aibridgedserver.go:58 (var _ aibridged.DRPCServer = &Server{}) does not verify ProviderConfigurator.
Fix: add proto.DRPCProviderConfiguratorServer to this interface.
🤖
🤖 This review was automatically generated with Coder Agents.
83efd04 to
bf6d5b8
Compare
bb14ffb to
c3a38c2
Compare
|
/coder-agents-review |
There was a problem hiding this comment.
9 of 11 findings from Round 1 were addressed in c3a38c2. Good progress.
Further review is blocked until the remaining 2 silent findings receive a response (fix, acknowledge, or contest):
CRF-2 (P2) coderd/aibridged/server.go:5: DRPCServer interface still embeds only 3 services; DRPCProviderConfiguratorServer is missing. The compile-time assertion at aibridgedserver.go:58 does not verify the new service, and any future mock targeting DRPCServer silently omits GetAIProviders. Fix: add proto.DRPCProviderConfiguratorServer to the interface.
CRF-5 (P3) coderd/aibridgedserver/providers_test.go:155: The negative assertion in TestGetAIProvidersBlocksOnSeedLock relies on time.After(testutil.IntervalMedium) without verifying the fetch goroutine reached AcquireLock. On a loaded CI runner the assertion can pass vacuously. Fix: verify the goroutine entered the blocking call (e.g., query pg_stat_activity) or acknowledge the known limitation.
🤖 This review was automatically generated with Coder Agents.
|
CRF-2: Fixed in f5b7463309 — |
bf6d5b8 to
e01b0a6
Compare
f5b7463 to
e769b46
Compare
|
/coder-agents-review |
4c9b451 to
a1de112
Compare
0241b7a to
1ff23e2
Compare
1ff23e2 to
c782cbc
Compare
a1de112 to
60d20ce
Compare
|
/coder-agents-review |
60d20ce to
8f1d264
Compare
There was a problem hiding this comment.
All 14 findings from rounds 1 and 3 are resolved. The author addressed every finding with substantive fixes, not band-aids: subscription failure made fatal (CRF-1), DRPCServer interface aligned with DRPCClient (CRF-2), pg_locks polling replaced timing-based inference (CRF-5), test helper replaced with production code (CRF-17), retry-succeed test coverage added and moved to stacked PR #26605 (CRF-19).
The refactor is clean. coderd owns the database; the gateway fetches provider config over DRPC. Both embedded and standalone daemons share an identical data path through NewPoolRPCReloader. The advisory lock serializes reads against the env seed. Disabled providers withhold keys and credentials at the server. Dead code is fully removed. Test density is healthy at 39%.
NewWebsocketDialer has no in-tree callers in this PR; its consumer lives in the stacked PR #26605.
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
|
@dannykopping ⛔ This review has reached its per-chat spend limit ($139.52 / $100.00). Further review rounds are paused. To raise the limit and continue, comment: This is a per-chat budget, separate from any account-level usage limit.
|
Add a DRPC ProviderConfigurator service (GetAIProviders) so the embedded and standalone AI Gateway daemons build their provider pool from coderd over DRPC instead of reading the database directly. Includes the shared poolRPCReloader, pubsub-driven reloads (fatal on subscription failure), the WebSocket dialer for standalone gateways, and the DRPCServer interface/proto plumbing.
cli.BuildProviders (the DB-based builder) was removed in favor of fetching providers over the in-memory DRPC. Update StartTestAIBridgeDaemon to start with an empty pool and populate it via cli.NewPoolRPCReloader + SubscribeProviderReload, matching cli.newAIBridgeDaemon.
…t branch NewWebsocketDialer is only used by the standalone "coder ai-gateway start" command, which is added in pawel/aigov-315. It landed here via a stack reorder; move it to the branch that consumes it so each PR is correctly scoped.
a0ee907 to
3ddcfb0
Compare
Merge activity
|

Closes AIGOV-455.
Why
The AI Gateway (
aibridged) is being split into a standalone process that must not touch the database.coderdstays the source of truth and seeds theai_providers/ai_provider_keystables from the environment. This PR adds a DRPC call so the gateway fetches provider config fromcoderdinstead of reading the DB, for both the embedded and standalone daemons.What
ProviderConfiguratorservice with a unaryGetAIProvidersRPC, plusAIProvider/AIProviderBedrockmessages.CurrentMinorbumped to 1 (additive).coderd/aibridgedserver):GetAIProvidersruns a read-onlyInTxunderLockIDAIProvidersEnvSeedso it never returns a mid-seed snapshot, reads providers (incl. disabled) plus keys for enabled ones, and maps to proto underdbauthz.AsAIBridged. Unmappable rows are skipped and logged; plaintext keys and Bedrock secrets are never logged.DRPCProviderConfiguratorClientwired into the client union,dialer.go, andCreateInMemoryAIBridgeServer.BuildProvidersFromProtomaps the response through the existing DB-neutralbuildProvider. A sharedpoolRPCReloaderdoes the fetch/build/replace for both daemons: the embedded daemon reloads on everyai_providerschange and fails startup if it cannot subscribe; the standalone gateway drives the same reloader once at startup, retrying until success and staying interruptible.BuildProvidersFromConfig,ProvidersFromConfig,AIProviderFromConfig, and the DB-readBuildProviderspath.