Skip to content

refactor(registry): require SecretBackend on from_configs_with_models#285

Merged
Destynova2 merged 1 commit intomainfrom
refactor/registry-secret-backend
Apr 26, 2026
Merged

refactor(registry): require SecretBackend on from_configs_with_models#285
Destynova2 merged 1 commit intomainfrom
refactor/registry-secret-backend

Conversation

@Destynova2
Copy link
Copy Markdown
Contributor

Summary

ProviderRegistry::from_configs_with_models previously took raw &[ProviderConfig] and trusted callers to invoke storage::secrets::resolve_provider_secrets first. That trust failed three times in three months — once per reload path:

The bug repeated because the API made it optional to do the right thing. The fix: make secret resolution mandatory at the type level.

What changes

Add secret_backend: &dyn SecretBackend as a required parameter. Apply resolve_provider_secrets internally before building any provider. Every caller now compiles only if it supplies a backend; every backend it supplies will be exercised by the resolver.

// Before (3+ buggy callsites in the wild)
ProviderRegistry::from_configs_with_models(&config.providers, token, &models, &timeouts)

// After (compiler-enforced correctness)
ProviderRegistry::from_configs_with_models(
    &config.providers,
    secret_backend.as_ref(),  // ← mandatory
    token,
    &models,
    &timeouts,
)

Diff summary

File Change
src/providers/registry.rs New required parameter + internal resolve_provider_secrets call
src/server/init.rs Removed redundant explicit resolve call (now internal)
src/preset/validation.rs Removed redundant explicit resolve call (PR #280 step)
src/server/config_guard.rs Pass backend, drop the explicit resolve step (PR #284 fix becomes free)
src/server/config_api.rs Same
src/server/rpc/server_ns.rs Same
src/providers/registry.rs::tests Test fixture passes EnvBackend (no-op for literal keys)

Regression test

Added from_configs_routes_resolution_through_backend. It uses a CountingBackend whose get is asserted to be called exactly once for a single secret:-prefixed provider. A future caller that compiles but somehow bypasses resolution would surface as a zero-call counter even before reaching production — closing the loop the unit tests on resolve_provider_secrets left open.

Why no broader test was needed

The unit tests on resolve_provider_secrets already cover the resolution logic itself (4 tests: secret prefix, unknown name, plain string passthrough, missing api_key). The new regression test covers the integration between the resolver and the registry constructor. Together with the type-level enforcement, the bug class is closed.

Test plan

  • cargo check --all-features clean
  • cargo nextest run --all-features1231 tests, 1231 passed
  • cargo nextest run -E 'test(from_configs)' — 1/1 (new regression test)
  • cargo fmt --all -- --check clean
  • cargo clippy -- -D warnings clean
  • CI: full nextest + clippy + fmt + audit + deny

Relationship to PRs #280 and #284

This refactor subsumes the per-callsite fixes in PR #280 and PR #284. If they merge first, this PR removes the now-redundant explicit resolve_provider_secrets calls (the resolution moves into from_configs_with_models). If this PR merges first, those PRs become no-ops on the affected lines (the type signature change forces correctness).

Either ordering works without conflict.

🤖 Generated with Claude Code

@Destynova2 Destynova2 enabled auto-merge April 26, 2026 15:57
@Destynova2 Destynova2 force-pushed the refactor/registry-secret-backend branch from 7848c17 to 497fb78 Compare April 26, 2026 17:09
The signature of `ProviderRegistry::from_configs_with_models` previously
took raw `&[ProviderConfig]` and trusted callers to invoke
`storage::secrets::resolve_provider_secrets` first. That trust failed
three times in three months — once per reload path:

  - PR #280: `preset::build_registry` (CLI `grob validate`)
  - PR #284: `server::config_guard`, `server::config_api`,
             `server::rpc::server_ns` (PUT /api/config, POST
             /api/config/reload, JSON-RPC server/reload)

The bug repeated because the API made it optional to do the right thing.
This refactor makes resolution mandatory at the type level: every caller
must pass `&dyn SecretBackend`, and the function applies
`resolve_provider_secrets` internally before constructing any provider.

Diff summary:
  - 1 new required parameter (`secret_backend: &dyn SecretBackend`)
  - 5 callers simplified (build_backend → pass through, drop the local
    `resolve_provider_secrets` step that PRs #280/#284 added)
  - 1 test fixture switched to `EnvBackend` (no-op for literal keys)
  - 1 new regression test (`from_configs_routes_resolution_through_backend`)
    that asserts the backend's `get` is called exactly once for a
    `secret:`-prefixed provider — would have caught all three historical
    bugs at compile-AND-test time

Full test suite passes: 1231 tests, 1231 passed.

Closes the bug class entirely. Any future caller that builds a
ProviderRegistry now compiles only if it supplies a backend, and any
backend it passes will be exercised by the resolver. We can never again
"forget to call resolve_provider_secrets".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Destynova2 Destynova2 force-pushed the refactor/registry-secret-backend branch from 497fb78 to 8b1a1d7 Compare April 26, 2026 17:14
@Destynova2 Destynova2 merged commit 734d2ec into main Apr 26, 2026
43 checks passed
@Destynova2 Destynova2 deleted the refactor/registry-secret-backend branch April 26, 2026 17:22
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.

1 participant