返回 CodeWhale
overlay.rs
根目录 / crates / config / src / cloud_facts / overlay.rs
1 //! One process-wide publication gate for verified facts, status and reader generation.
2 //! Fetchers capture a settings ticket before work; stale settings cannot publish or
3 //! write their cache after a newer disable or source change has been admitted.
4
5 use std::collections::BTreeMap;
6 use std::sync::{Arc, LazyLock, RwLock};
7
8 use super::provenance::{CloudFactsState, CloudFactsStatus};
9 use super::scope::ScopedFacts;
10
11 #[derive(Debug, Clone, PartialEq, Eq)]
12 pub struct OverlayTicket {
13 epoch: u64,
14 source_identity: String,
15 }
16
17 #[derive(Debug, Clone, Default)]
18 pub struct OverlaySnapshot {
19 pub generation: u64,
20 pub fetched_at: Option<u64>,
21 pub facts: Option<Arc<ScopedFacts>>,
22 pub status: CloudFactsStatus,
23 }
24
25 #[derive(Default)]
26 struct State {
27 epoch: u64,
28 enabled: bool,
29 source_identity: String,
30 highest_seen: BTreeMap<String, u64>,
31 snapshot: OverlaySnapshot,
32 }
33
34 static STATE: LazyLock<RwLock<State>> = LazyLock::new(|| RwLock::new(State::default()));
35
36 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
37 pub enum DefaultSource {
38 Compiled,
39 CloudFacts { facts_version: u64 },
40 }
41
42 #[must_use]
43 pub fn hard_disabled() -> bool {
44 std::env::var("CODEWHALE_DISABLE_CLOUD_FACTS").is_ok_and(|value| {
45 matches!(
46 value.trim().to_ascii_lowercase().as_str(),
47 "1" | "true" | "yes" | "on"
48 )
49 })
50 }
51
52 fn disable(state: &mut State) {
53 if state.enabled
54 || state.snapshot.facts.is_some()
55 || state.snapshot.status.state != CloudFactsState::Off
56 {
57 state.epoch = state.epoch.saturating_add(1);
58 state.snapshot.generation = state.snapshot.generation.saturating_add(1);
59 }
60 state.enabled = false;
61 state.snapshot.facts = None;
62 state.snapshot.status = CloudFactsStatus::default();
63 }
64
65 /// Admit effective settings synchronously. Refreshes must use `current_ticket`
66 /// instead: an old settings object must never implicitly re-enable this layer.
67 pub fn configure(enabled: bool, source_identity: &str) -> Option<OverlayTicket> {
68 let mut state = STATE.write().ok()?;
69 if !enabled || hard_disabled() {
70 disable(&mut state);
71 return None;
72 }
73 if !state.enabled || state.source_identity != source_identity {
74 state.epoch = state.epoch.saturating_add(1);
75 state.snapshot.generation = state.snapshot.generation.saturating_add(1);
76 state.snapshot.facts = None;
77 state.snapshot.status = CloudFactsStatus {
78 state: CloudFactsState::BundledOnly,
79 ..Default::default()
80 };
81 state.source_identity = source_identity.to_string();
82 state.enabled = true;
83 }
84 Some(OverlayTicket {
85 epoch: state.epoch,
86 source_identity: state.source_identity.clone(),
87 })
88 }
89
90 /// Accepted in-process rollback floors survive settings and trust changes.
91 #[must_use]
92 pub fn highest_seen(channel: &str) -> Option<u64> {
93 STATE.read().ok()?.highest_seen.get(channel).copied()
94 }
95
96 #[must_use]
97 pub fn current_ticket(source_identity: &str) -> Option<OverlayTicket> {
98 let mut state = STATE.write().ok()?;
99 if hard_disabled() {
100 disable(&mut state);
101 }
102 (state.enabled && state.source_identity == source_identity).then(|| OverlayTicket {
103 epoch: state.epoch,
104 source_identity: state.source_identity.clone(),
105 })
106 }
107
108 #[must_use]
109 pub fn is_current(ticket: &OverlayTicket) -> bool {
110 let Ok(mut state) = STATE.write() else {
111 return false;
112 };
113 if hard_disabled() {
114 disable(&mut state);
115 }
116 state.enabled && ticket.epoch == state.epoch && ticket.source_identity == state.source_identity
117 }
118
119 pub fn publish(
120 ticket: &OverlayTicket,
121 facts: Option<ScopedFacts>,
122 status: CloudFactsStatus,
123 ) -> bool {
124 publish_with(ticket, facts, status, || {})
125 }
126
127 /// The callback may save/delete the bounded cache but must not reenter this
128 /// module. Its write and the state publication share the disable/source gate.
129 pub fn publish_with(
130 ticket: &OverlayTicket,
131 facts: Option<ScopedFacts>,
132 status: CloudFactsStatus,
133 before_publish: impl FnOnce(),
134 ) -> bool {
135 let Ok(mut state) = STATE.write() else {
136 return false;
137 };
138 if hard_disabled() {
139 disable(&mut state);
140 }
141 if !state.enabled
142 || ticket.epoch != state.epoch
143 || ticket.source_identity != state.source_identity
144 {
145 return false;
146 }
147 if let Some(facts) = &facts {
148 if state
149 .highest_seen
150 .get(&facts.channel)
151 .is_some_and(|version| facts.facts_version < *version)
152 {
153 return false;
154 }
155 state
156 .highest_seen
157 .insert(facts.channel.clone(), facts.facts_version);
158 }
159 before_publish();
160 state.snapshot.fetched_at = if facts.is_none() {
161 None
162 } else {
163 match &status.state {
164 CloudFactsState::Verified { fetched_at, .. } => Some(*fetched_at),
165 _ => state.snapshot.fetched_at,
166 }
167 };
168 state.snapshot.facts = facts.map(Arc::new);
169 state.snapshot.status = status;
170 state.snapshot.generation = state.snapshot.generation.saturating_add(1);
171 true
172 }
173
174 fn snapshot_at(now: u64) -> OverlaySnapshot {
175 let Ok(mut state) = STATE.write() else {
176 return OverlaySnapshot::default();
177 };
178 if hard_disabled() {
179 disable(&mut state);
180 }
181 if state
182 .snapshot
183 .facts
184 .as_ref()
185 .is_some_and(|facts| !facts.is_current_at(now))
186 {
187 state.snapshot.facts = None;
188 state.snapshot.generation = state.snapshot.generation.saturating_add(1);
189 if let CloudFactsState::Verified { stale, .. } = &mut state.snapshot.status.state {
190 *stale = true;
191 }
192 }
193 state.snapshot.clone()
194 }
195
196 #[must_use]
197 pub fn snapshot() -> OverlaySnapshot {
198 snapshot_at(crate::catalog::now_unix())
199 }
200
201 #[must_use]
202 pub fn overlay() -> Option<Arc<ScopedFacts>> {
203 snapshot().facts
204 }
205
206 #[must_use]
207 pub fn status() -> CloudFactsStatus {
208 snapshot().status
209 }
210
211 #[must_use]
212 pub fn is_verified() -> bool {
213 overlay().is_some()
214 }
215
216 #[must_use]
217 pub fn cloud_default_model(provider: &str) -> Option<(String, DefaultSource)> {
218 let overlay = overlay()?;
219 let model = overlay
220 .provider_defaults
221 .get(provider)?
222 .default_model
223 .clone()?;
224 Some((
225 model,
226 DefaultSource::CloudFacts {
227 facts_version: overlay.facts_version,
228 },
229 ))
230 }
231
232 /// Cloud defaults are a floor for official hosted endpoints, never an override
233 /// for named compatible routes, local machines, or a Codex account roster.
234 #[must_use]
235 pub fn cloud_default_model_for_route(
236 provider: crate::ProviderKind,
237 base_url: &str,
238 ) -> Option<(String, DefaultSource)> {
239 if matches!(
240 provider,
241 crate::ProviderKind::Custom
242 | crate::ProviderKind::Ollama
243 | crate::ProviderKind::Sglang
244 | crate::ProviderKind::Vllm
245 | crate::ProviderKind::OpenaiCodex
246 ) || !super::scope::base_url_allowed(provider.as_str(), base_url)
247 {
248 return None;
249 }
250 cloud_default_model(provider.as_str())
251 }
252
253 #[must_use]
254 pub fn cloud_default_base_url(provider: &str) -> Option<(String, DefaultSource)> {
255 let overlay = overlay()?;
256 let url = overlay.provider_defaults.get(provider)?.base_url.clone()?;
257 Some((
258 url,
259 DefaultSource::CloudFacts {
260 facts_version: overlay.facts_version,
261 },
262 ))
263 }
264
265 pub fn clear() {
266 if let Ok(mut state) = STATE.write() {
267 disable(&mut state);
268 }
269 }
270
270 lines RUST