返回 CodeWhale
authority.rs
根目录 / crates / config / src / route / authority.rs
1 //! One immutable authority for a compiled provider catalog and its route resolver.
2 //!
3 //! Before this seam, callers could independently project a
4 //! [`crate::catalog::CatalogSnapshot`] through
5 //! [`crate::catalog::CatalogSnapshot::to_offerings`] and create a
6 //! [`super::RouteResolver`]. That made it possible for a picker, readiness
7 //! view, and execution path to use different catalog snapshots without any
8 //! type-level signal. `RouteAuthoritySnapshot` replaces that ad-hoc pairing for
9 //! new consumers: it owns the exact compiled catalog and the resolver projected
10 //! from that catalog together.
11 //!
12 //! It deliberately does not claim that a catalog row is runnable. Calling
13 //! [`RouteAuthoritySnapshot::resolve`] still mints a
14 //! [`super::ReadyRouteCandidate`] through the sole resolver. The returned
15 //! receipt distinguishes an exact catalog row, a custom-endpoint route whose
16 //! provider facts are intentionally not reused, and an allowed pass-through
17 //! route with no catalog row. All state is secret-free.
18
19 use std::collections::BTreeMap;
20
21 use serde::{Deserialize, Serialize};
22
23 use crate::catalog::{CatalogOffering, CatalogSnapshot, CatalogStatus};
24
25 use super::{
26 ProviderId, ReadyRouteCandidate, RouteError, RouteRequest, RouteResolver, WireModelId,
27 };
28
29 /// A secret-free provider catalog cache scope.
30 ///
31 /// The base URL is represented only by its already-redacted fingerprint. The
32 /// provider remains an open catalog string because a catalog can include a
33 /// discoverable provider that is not yet a built-in [`crate::ProviderKind`].
34 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
35 pub struct RouteCatalogScope {
36 provider: String,
37 base_url_fingerprint: String,
38 }
39
40 impl RouteCatalogScope {
41 /// Construct a normalized, secret-free cache scope.
42 #[must_use]
43 pub fn new(provider: impl Into<String>, base_url_fingerprint: impl Into<String>) -> Self {
44 Self {
45 provider: provider.into().trim().to_string(),
46 base_url_fingerprint: base_url_fingerprint.into().trim().to_string(),
47 }
48 }
49
50 /// Provider id associated with this cache scope.
51 #[must_use]
52 pub fn provider(&self) -> &str {
53 &self.provider
54 }
55
56 /// Secret-free base URL fingerprint associated with this cache scope.
57 #[must_use]
58 pub fn base_url_fingerprint(&self) -> &str {
59 &self.base_url_fingerprint
60 }
61 }
62
63 /// Provenance receipt for a route resolved from a [`RouteAuthoritySnapshot`].
64 ///
65 /// A catalog source describes metadata provenance, not account authorization or
66 /// endpoint health. Consumers must keep readiness/auth checks separate.
67 #[derive(Debug, Clone, PartialEq, Serialize)]
68 #[serde(tag = "kind", rename_all = "snake_case")]
69 pub enum CatalogOfferingReceipt {
70 /// The resolved provider/wire pair exactly matches an offering in the
71 /// compiled catalog owned by this authority snapshot.
72 Catalog { offering: Box<CatalogOffering> },
73 /// The caller selected an explicit base URL. The resolver deliberately
74 /// clears catalog-owned capabilities and pricing for that custom endpoint,
75 /// so it must not be presented as an exact catalog offering.
76 CustomEndpoint {
77 provider: ProviderId,
78 wire_model_id: WireModelId,
79 },
80 /// The resolver permitted an unknown/pass-through wire model, but the
81 /// compiled catalog did not assert facts for that provider/wire pair.
82 NotCataloged {
83 provider: ProviderId,
84 wire_model_id: WireModelId,
85 },
86 }
87
88 impl CatalogOfferingReceipt {
89 /// Return the exact catalog offering, if this route was catalog-backed.
90 #[must_use]
91 pub fn offering(&self) -> Option<&CatalogOffering> {
92 match self {
93 Self::Catalog { offering } => Some(offering),
94 Self::CustomEndpoint { .. } | Self::NotCataloged { .. } => None,
95 }
96 }
97 }
98
99 /// One route-resolution result plus honest catalog provenance.
100 #[derive(Debug, Clone)]
101 pub struct AuthorityResolution {
102 candidate: ReadyRouteCandidate,
103 receipt: CatalogOfferingReceipt,
104 }
105
106 impl AuthorityResolution {
107 /// The executable candidate minted by the snapshot's resolver.
108 #[must_use]
109 pub fn candidate(&self) -> &ReadyRouteCandidate {
110 &self.candidate
111 }
112
113 /// Honest catalog provenance for the candidate's provider/wire pair.
114 #[must_use]
115 pub fn receipt(&self) -> &CatalogOfferingReceipt {
116 &self.receipt
117 }
118 }
119
120 /// A compiled catalog and the resolver derived from that exact catalog.
121 ///
122 /// The private fields prevent a consumer from retaining the snapshot while
123 /// silently replacing its resolver. Refreshers build a new immutable snapshot
124 /// when their catalog changes; existing consumers keep their coherent view.
125 #[derive(Debug, Clone)]
126 pub struct RouteAuthoritySnapshot {
127 catalog: CatalogSnapshot,
128 resolver: RouteResolver,
129 scope_statuses: BTreeMap<RouteCatalogScope, CatalogStatus>,
130 }
131
132 impl RouteAuthoritySnapshot {
133 /// Bind a compiled catalog to the resolver that consumes its offerings.
134 #[must_use]
135 pub fn new(catalog: CatalogSnapshot) -> Self {
136 let resolver = RouteResolver::from_offerings(catalog.to_offerings());
137 Self {
138 catalog,
139 resolver,
140 scope_statuses: BTreeMap::new(),
141 }
142 }
143
144 /// Record the cache health known for one provider/base-URL scope.
145 ///
146 /// An omitted status stays [`CatalogStatus::Unknown`]; this API never
147 /// derives freshness from a model row or invents a successful refresh.
148 #[must_use]
149 pub fn with_scope_status(mut self, scope: RouteCatalogScope, status: CatalogStatus) -> Self {
150 self.scope_statuses.insert(scope, status);
151 self
152 }
153
154 /// The exact compiled catalog bound to this resolver.
155 #[must_use]
156 pub fn catalog(&self) -> &CatalogSnapshot {
157 &self.catalog
158 }
159
160 /// Cache health for one provider/base-URL scope, or honestly unknown.
161 #[must_use]
162 pub fn scope_status(&self, scope: &RouteCatalogScope) -> CatalogStatus {
163 self.scope_statuses
164 .get(scope)
165 .cloned()
166 .unwrap_or(CatalogStatus::Unknown)
167 }
168
169 /// Resolve through the authority-bound resolver and retain catalog receipt.
170 ///
171 /// # Errors
172 /// Returns the route resolver's validation error when a request cannot
173 /// produce an executable candidate.
174 pub fn resolve(&self, request: &RouteRequest) -> Result<AuthorityResolution, RouteError> {
175 let candidate = self.resolver.resolve(request)?;
176 let receipt = if request.base_url_override.is_some() {
177 CatalogOfferingReceipt::CustomEndpoint {
178 provider: candidate.provider_id().clone(),
179 wire_model_id: candidate.wire_model_id().clone(),
180 }
181 } else if let Some(offering) = self.catalog.offerings.iter().find(|offering| {
182 offering.provider == candidate.provider_id().as_str()
183 && offering.wire_model_id == candidate.wire_model_id().as_str()
184 }) {
185 CatalogOfferingReceipt::Catalog {
186 offering: Box::new(offering.clone()),
187 }
188 } else {
189 CatalogOfferingReceipt::NotCataloged {
190 provider: candidate.provider_id().clone(),
191 wire_model_id: candidate.wire_model_id().clone(),
192 }
193 };
194
195 Ok(AuthorityResolution { candidate, receipt })
196 }
197 }
198
199 #[cfg(test)]
200 mod tests {
201 use crate::ProviderKind;
202 use crate::catalog::{CatalogCompiler, CatalogSource};
203 use crate::models_dev::ModelsDevLimit;
204 use crate::route::{LogicalModelRef, RouteRequest};
205
206 use super::{CatalogOfferingReceipt, RouteAuthoritySnapshot, RouteCatalogScope};
207
208 fn request(model: &str) -> RouteRequest {
209 RouteRequest {
210 explicit_provider: Some(ProviderKind::Deepseek),
211 model_selector: Some(LogicalModelRef::from(model)),
212 saved_provider_model: None,
213 base_url_override: None,
214 limit_overrides: Vec::new(),
215 }
216 }
217
218 fn offering(source: CatalogSource, context: u64) -> crate::catalog::CatalogOffering {
219 crate::catalog::CatalogOffering {
220 provider: "deepseek".to_string(),
221 wire_model_id: "deepseek-v4-flash-vision-exp".to_string(),
222 endpoint_key: "responses".to_string(),
223 default_for_provider: true,
224 limit: Some(ModelsDevLimit {
225 context: Some(context),
226 ..Default::default()
227 }),
228 source,
229 ..Default::default()
230 }
231 }
232
233 #[test]
234 fn one_compiled_snapshot_drives_candidate_and_catalog_receipt() {
235 let snapshot = CatalogCompiler::new()
236 .with_bundled(vec![offering(CatalogSource::Bundled, 32_000)])
237 .with_config(vec![offering(CatalogSource::ConfigOverride, 64_000)])
238 .compile();
239 let authority = RouteAuthoritySnapshot::new(snapshot);
240
241 let resolved = authority
242 .resolve(&request("deepseek-v4-flash-vision-exp"))
243 .expect("catalog-backed direct route resolves");
244
245 assert_eq!(resolved.candidate().limits().context_tokens, Some(64_000));
246 let CatalogOfferingReceipt::Catalog { offering } = resolved.receipt() else {
247 panic!("compiled catalog row must be retained as the receipt");
248 };
249 assert_eq!(offering.source, CatalogSource::ConfigOverride);
250 assert_eq!(
251 offering.limit.as_ref().and_then(|limit| limit.context),
252 Some(64_000)
253 );
254 }
255
256 #[test]
257 fn custom_endpoint_never_claims_catalog_offering_facts() {
258 let snapshot = CatalogCompiler::new()
259 .with_bundled(vec![offering(CatalogSource::Bundled, 32_000)])
260 .compile();
261 let authority = RouteAuthoritySnapshot::new(snapshot);
262 let mut request = request("deepseek-v4-flash-vision-exp");
263 request.base_url_override = Some("https://compatible.example/v1".to_string());
264
265 let resolved = authority
266 .resolve(&request)
267 .expect("custom compatible endpoint still resolves");
268
269 assert!(matches!(
270 resolved.receipt(),
271 CatalogOfferingReceipt::CustomEndpoint { .. }
272 ));
273 assert!(resolved.receipt().offering().is_none());
274 }
275
276 #[test]
277 fn direct_provider_pass_through_is_explicitly_not_cataloged() {
278 let authority = RouteAuthoritySnapshot::new(CatalogCompiler::new().compile());
279
280 let resolved = authority
281 .resolve(&request("future-deepseek-model"))
282 .expect("direct-provider pass-through remains executable");
283
284 assert!(matches!(
285 resolved.receipt(),
286 CatalogOfferingReceipt::NotCataloged { .. }
287 ));
288 assert!(resolved.receipt().offering().is_none());
289 }
290
291 #[test]
292 fn scope_status_is_explicit_and_defaults_to_unknown() {
293 let snapshot = CatalogCompiler::new().compile();
294 let fresh_scope = RouteCatalogScope::new("deepseek", "fingerprint-a");
295 let absent_scope = RouteCatalogScope::new("deepseek", "fingerprint-b");
296 let authority = RouteAuthoritySnapshot::new(snapshot)
297 .with_scope_status(fresh_scope.clone(), crate::catalog::CatalogStatus::Fresh);
298
299 assert_eq!(
300 authority.scope_status(&fresh_scope),
301 crate::catalog::CatalogStatus::Fresh
302 );
303 assert_eq!(
304 authority.scope_status(&absent_scope),
305 crate::catalog::CatalogStatus::Unknown
306 );
307 }
308 }
309
309 lines RUST