Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a9884c7
Keep unknown windows and reset times unknown (OpenCode, OpenCode Go, …
DreamyTalesPAN Oct 28, 2026
50b9e9b
Mark Azure OpenAI deployment status as informational
DreamyTalesPAN Oct 28, 2026
0376c2f
Keep Bedrock spend without a budget informational
DreamyTalesPAN Oct 28, 2026
1903bb1
Keep Claude Admin costs and token counts informational
DreamyTalesPAN Oct 28, 2026
334c408
Merge branch 'claude/missing-data-no-zero' into claude/289-c
DreamyTalesPAN Oct 28, 2026
f58958b
Dashboard: mark informational and unknown windows as unavailable
DreamyTalesPAN Oct 28, 2026
822cefe
Report Crof balance as informational
DreamyTalesPAN Oct 28, 2026
f491e41
Keep CrossModel balances and costs informational
DreamyTalesPAN Oct 28, 2026
ff69ea7
Keep Deepgram usage counters informational
DreamyTalesPAN Oct 28, 2026
fc638f5
Keep Ollama model lists and missing primary informational
DreamyTalesPAN Oct 28, 2026
e1c2573
Keep OpenAI API balances without totals and costs informational
DreamyTalesPAN Oct 28, 2026
d700bb2
Keep OpenRouter costs and invalid credit totals informational
DreamyTalesPAN Oct 28, 2026
111fcab
Report Poe balances as information and reject missing balances
DreamyTalesPAN Oct 28, 2026
c084d83
Distinguish Venice balances from measured allocation quotas
DreamyTalesPAN Oct 28, 2026
635c7c9
Stop reporting Vertex AI metadata and failed fetches as quotas
DreamyTalesPAN Oct 28, 2026
8165456
Keep Wayfinder gateway statistics informational
DreamyTalesPAN Oct 28, 2026
94b4d08
Keep DeepInfra billing without a spending limit informational
DreamyTalesPAN Oct 28, 2026
5d0d00d
Keep DeepSeek balances and activity counters informational
DreamyTalesPAN Oct 28, 2026
a10ec27
Keep Fireworks rated spend informational
DreamyTalesPAN Oct 28, 2026
3f27325
Keep Groq metrics informational and reject missing scalars
DreamyTalesPAN Oct 28, 2026
67ce4a5
Require a complete Kimi quota pair and keep balances informational
DreamyTalesPAN Oct 28, 2026
bb8658a
Keep LLMProxy counters and missing quota informational
DreamyTalesPAN Oct 28, 2026
876deb7
Keep Mistral monthly billing informational
DreamyTalesPAN Oct 28, 2026
64a58b6
Fix missing Abacus usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
623f869
Fix missing Alibaba usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
eae0cef
Fix missing Amp usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
65a7673
Fix missing Augment usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
769e223
Fix missing Antigravity legacy usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
68a2995
Fix missing CodeBuddy usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
0ebb4c5
Fix missing Codebuff usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
1e3c6f0
Fix missing CommandCode usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
962f6a2
Fix missing Devin usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
25baa97
Fix missing Doubao usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
eb75eec
Fix missing Factory usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
ebf5f9e
Fix missing JetBrains usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
c01f006
Fix missing Kilo usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
bc78039
Fix missing LiteLLM usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
728f36f
Fix missing LongCat usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
15d3cb2
Fix missing Manus usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
fa78104
Fix missing MiMo usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
a7fda99
Fix missing MiniMax usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
03851e1
Fix missing Perplexity usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
1c26ec9
Merge branch 'cx289b' into claude/289-c
DreamyTalesPAN Oct 28, 2026
f01045d
Fix missing Sub2API usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
c299b4e
Fix missing T3 Chat usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
84d2aec
Fix missing Warp usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
5b08bde
Fix missing Z.ai usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
d44f495
Fix missing Zed usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
0dc0aa2
Fix missing ZoomMate usage being reported as a quota
DreamyTalesPAN Oct 28, 2026
e1ae557
DeepInfra: keep depleted or suspended accounts with a spending limit …
DreamyTalesPAN Oct 28, 2026
18a7f73
Merge branch 'cx289a' into claude/289-c
DreamyTalesPAN Oct 28, 2026
9cfbe9e
Sub2API: adapt the expiry test to the fallible snapshot builder
DreamyTalesPAN Oct 28, 2026
953401a
ZoomMate: an unlimited account is informational, not 0 % or an error
DreamyTalesPAN Oct 28, 2026
87d440a
Manus: add-on and event credit balances are informational, not 0 % wi…
DreamyTalesPAN Oct 28, 2026
ec3c8b6
MiniMax: billing-only token and spend counters are informational, not…
DreamyTalesPAN Oct 28, 2026
33447d1
Reword five new provider errors so the Companion health scan does not…
DreamyTalesPAN Oct 28, 2026
6ecc5c2
cargo fmt after the error reword
DreamyTalesPAN Oct 28, 2026
b6668e2
Reword three more provider errors that the Companion reads as setup, …
DreamyTalesPAN Oct 28, 2026
8630589
fix(codebuddy): preserve quota with an empty package (review #16)
DreamyTalesPAN Oct 28, 2026
64dd812
fix(warp): represent unlimited usage as information (review #16)
DreamyTalesPAN Oct 28, 2026
5847a51
fix(kilo): preserve PAYG balance as information (review #16)
DreamyTalesPAN Oct 28, 2026
5c21933
fix(mimo): retain balance without optional plan data (review #16)
DreamyTalesPAN Oct 28, 2026
f8e32bc
fix(manus): retain known balances without a quota (review #16)
DreamyTalesPAN Oct 28, 2026
12e93dc
fix(codebuff): retain observed remaining credits without a quota (rev…
DreamyTalesPAN Oct 28, 2026
1497599
fix(devin): preserve balances when quota counters are unavailable (re…
DreamyTalesPAN Oct 28, 2026
6504a94
fix(doubao): retain observed probe tokens as information (review #16)
DreamyTalesPAN Oct 28, 2026
b5ddf28
fix(sub2api): preserve independent balance and usage totals (review #16)
DreamyTalesPAN Oct 28, 2026
bf9fbeb
fix(venice): keep measured DIEM quota when consumption is blocked (re…
DreamyTalesPAN Oct 28, 2026
d9940e1
fix(abacus): treat zero denominator as unavailable quota (review #16)
DreamyTalesPAN Oct 28, 2026
dca67e4
fix(devin): avoid inventing a weekly quota from generic counters (rev…
DreamyTalesPAN Oct 28, 2026
5f8a757
fix(minimax): require an observed remaining counter for coding quotas…
DreamyTalesPAN Oct 28, 2026
d8c14aa
fix(minimax): export unlimited coding windows as information (review …
DreamyTalesPAN Oct 28, 2026
476cbad
fix(deepinfra): keep account status separate from measured budget usa…
DreamyTalesPAN Oct 28, 2026
48c5522
fix(opencodego): retain real weekly and monthly windows without rolli…
DreamyTalesPAN Oct 28, 2026
508093b
fix(minimax): resolve reset clocks against the timezone calendar day …
DreamyTalesPAN Oct 28, 2026
09789cd
fix(opencodego): replace inferred local quotas with observed cost inf…
DreamyTalesPAN Oct 28, 2026
f327cfc
fix(providers): keep usage data errors distinct from setup errors (re…
DreamyTalesPAN Oct 28, 2026
cacb7c8
fix(providers): keep Kimi K2 cash/voucher amounts and Doubao observed…
DreamyTalesPAN Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions rust/src/cli/serve/dashboard/antigravity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ pub(super) fn quota_summary_windows(
));
}
windows.extend(usage.extra_rate_windows.iter().map(|extra| {
make_window_with_idle(
let mut window = make_window_with_idle(
&extra.id,
&extra.title,
&extra.window,
idle_ids.contains(&extra.id),
)
);
window.unavailable |= !extra.usage_known;
window
}));
windows
}
Expand Down
2 changes: 1 addition & 1 deletion rust/src/cli/serve/dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ <h1>CodexBar</h1>
return "bar";
}
function visibleWindows(windows) {
return (windows || []).filter(w => w.idle !== true);
return (windows || []).filter(w => w.idle !== true && w.unavailable !== true);
}
function windowRow(w) {
const used = Number.isFinite(w.usedPercent) ? Math.max(0, Math.min(100, w.usedPercent)) : 0;
Expand Down
2 changes: 1 addition & 1 deletion rust/src/cli/serve/dashboard/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod tests {
fn web_ui_skips_windows_marked_idle() {
let html = render_shell(60);
assert!(html.contains("function visibleWindows(windows)"));
assert!(html.contains("w.idle !== true"));
assert!(html.contains("w.idle !== true && w.unavailable !== true"));
assert!(html.contains("visibleWindows(account.windows).forEach"));
assert!(html.contains("visibleWindows(row.windows).forEach"));
}
Expand Down
43 changes: 43 additions & 0 deletions rust/src/cli/serve/dashboard/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,49 @@ mod tests {
assert!(windows.iter().all(|window| window.get("idle").is_none()));
}

#[test]
fn antigravity_dashboard_marks_unknown_lane_unavailable() {
let windows = vec![
NamedRateWindow::new("model-gemini", "Gemini Pro", RateWindow::new(20.0)),
NamedRateWindow::new("model-claude", "Claude Sonnet", RateWindow::new(0.0))
.with_usage_known(false),
NamedRateWindow::new("model-gpt", "GPT", RateWindow::new(0.0)),
];
let json = serde_json::to_value(build_snapshot(&input(
vec![antigravity_envelope(windows)],
DashboardIdentity::Redacted,
)))
.unwrap();
let windows = json["providers"][0]["windows"].as_array().unwrap();
let unavailable: Vec<&str> = windows
.iter()
.filter(|window| window["unavailable"] == true)
.map(|window| window["label"].as_str().unwrap())
.collect();
assert_eq!(unavailable, vec!["Claude Sonnet"]);
}

#[test]
fn informational_window_is_unavailable_and_a_real_zero_is_not() {
let usage = UsageSnapshot::new(RateWindow::informational("No primary quota"))
.with_secondary(RateWindow::new(0.0));
let json = serde_json::to_value(build_snapshot(&input(
vec![ProviderFetchEnvelope {
id: "windsurf".to_string(),
display_name: "Windsurf".to_string(),
session_label: "Daily".to_string(),
weekly_label: "Weekly".to_string(),
fetch: Ok(ProviderFetchResult::new(usage, "local")),
}],
DashboardIdentity::Redacted,
)))
.unwrap();
let windows = json["providers"][0]["windows"].as_array().unwrap();
assert_eq!(windows[0]["unavailable"], true);
assert!(windows[1].get("unavailable").is_none());
assert_eq!(windows[1]["usedPercent"], 0.0);
}

#[test]
fn antigravity_dashboard_keeps_all_families_after_global_reset() {
let windows = vec![
Expand Down
5 changes: 5 additions & 0 deletions rust/src/cli/serve/dashboard/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub struct WindowPayload {
/// Display-only hint. Script clients can ignore this additive schema-v1 key.
#[serde(skip_serializing_if = "is_false")]
pub idle: bool,
/// Informational or unknown lane: `used_percent` / `remaining_percent` are
/// placeholders, not a measurement. Additive schema-v1 key.
#[serde(skip_serializing_if = "is_false")]
pub unavailable: bool,
}

fn is_false(value: &bool) -> bool {
Expand All @@ -33,5 +37,6 @@ pub(super) fn make_window_with_idle(
remaining_percent: (100.0 - used).clamp(0.0, 100.0),
reset_at: window.resets_at,
idle,
unavailable: window.is_informational,
}
}
9 changes: 6 additions & 3 deletions rust/src/core/usage_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ pub struct NamedRateWindow {
pub id: String,
pub title: String,
pub window: RateWindow,
/// Whether the provider explicitly reported usage for this lane.
/// In-memory presentation metadata only; external snapshot JSON stays stable.
#[serde(default = "named_rate_window_usage_known_default", skip_serializing)]
/// Whether the provider explicitly reported usage for this lane. Exported
/// so consumers can tell an unknown lane from a real 0 %.
#[serde(
default = "named_rate_window_usage_known_default",
rename = "usageKnown"
)]
pub usage_known: bool,
}

Expand Down
111 changes: 95 additions & 16 deletions rust/src/providers/abacus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ struct ApiEnvelope<T> {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ComputePoints {
#[serde(default)]
total_compute_points: f64,
#[serde(default)]
compute_points_left: f64,
}

Expand All @@ -43,6 +41,18 @@ struct BillingInfo {
current_tier: Option<String>,
}

fn parse_compute_response(body: &str) -> Result<ComputePoints, ProviderError> {
let env: ApiEnvelope<ComputePoints> = serde_json::from_str(body)
.map_err(|_| ProviderError::Parse("Abacus compute response is invalid".into()))?;

if !env.success {
return Err(ProviderError::AuthRequired);
}
env.result.ok_or_else(|| {
ProviderError::Parse("Abacus response contains no compute points result".into())
})
}

pub struct AbacusProvider {
metadata: ProviderMetadata,
client: Client,
Expand Down Expand Up @@ -88,12 +98,17 @@ impl AbacusProvider {
.and_then(|b| b.next_billing_date.as_deref())
.and_then(|ts| DateTime::parse_from_rfc3339(ts).ok())
.map(|dt| dt.with_timezone(&Utc));
let primary = RateWindow::with_details(
percent,
RateWindow::monthly_window_minutes(resets_at).or(Some(FALLBACK_MONTHLY_WINDOW_MINUTES)),
resets_at,
Some(format_credit_detail(used, total)),
);
let primary = if total <= 0.0 {
RateWindow::informational(format_credit_detail(used, total))
} else {
RateWindow::with_details(
percent,
RateWindow::monthly_window_minutes(resets_at)
.or(Some(FALLBACK_MONTHLY_WINDOW_MINUTES)),
resets_at,
Some(format_credit_detail(used, total)),
)
};

let mut snapshot = UsageSnapshot::new(primary).with_primary_label(CREDITS_LABEL);
if let Some(b) = billing
Expand Down Expand Up @@ -126,14 +141,7 @@ impl AbacusProvider {
}

let body = resp.text().await?;
let env: ApiEnvelope<ComputePoints> = serde_json::from_str(&body)
.map_err(|e| ProviderError::Parse(format!("Failed to parse compute points: {}", e)))?;

if !env.success {
return Err(ProviderError::AuthRequired);
}
env.result
.ok_or_else(|| ProviderError::Parse("Missing compute points result".to_string()))
parse_compute_response(&body)
}

async fn fetch_billing(&self, cookie_header: &str) -> Option<BillingInfo> {
Expand Down Expand Up @@ -334,3 +342,74 @@ mod tests {
assert_eq!(format_credit_detail(42.5, 100.0), "42.5 / 100 credits");
}
}

#[cfg(test)]
mod cx289a_tests {
use super::*;
use serde_json::json;
#[test]
fn cx289a_missing_compute_fields_are_rejected() {
for payload in [
json!({}),
json!({"totalComputePoints":100}),
json!({"computePointsLeft":100}),
] {
let result = serde_json::from_value::<ComputePoints>(payload)
.map_err(|e| ProviderError::Parse(e.to_string()))
.and_then(|compute| AbacusProvider::build_snapshot(compute, None));
assert!(result.is_err());
}
}
#[test]
fn cx289a_real_zero_is_preserved() {
let compute =
serde_json::from_value(json!({"totalComputePoints":100,"computePointsLeft":100}))
.unwrap();
let snapshot = AbacusProvider::build_snapshot(compute, None).unwrap();
assert_eq!(snapshot.primary.used_percent, 0.0);
assert!(!snapshot.primary.is_informational);
}
}

#[cfg(test)]
mod p1_11_zero_denominator_is_information_tests {
use super::*;
#[test]
fn review16_p1_11_zero_denominator_is_information() {
let compute = serde_json::from_value(
serde_json::json!({"totalComputePoints":0,"computePointsLeft":0}),
)
.unwrap();
let snapshot = AbacusProvider::build_snapshot(compute, None).unwrap();
assert!(snapshot.primary.is_informational);
assert!(snapshot.primary.window_minutes.is_none());
assert!(snapshot.primary.resets_at.is_none());
}
}

#[cfg(test)]
mod p2_02_incomplete_compute_is_a_data_error_tests {
use super::*;
#[test]
fn review16_p2_02_incomplete_compute_is_a_data_error() {
for body in [
r#"{"success":true,"result":{"totalComputePoints":100}}"#,
r#"{"success":true}"#,
] {
let error = parse_compute_response(body).unwrap_err();
assert!(matches!(error, ProviderError::Parse(_)));
let text = error.to_string().to_lowercase();
for marker in [
"missing",
"not found",
"required",
"auth",
"token",
"cookie",
"login",
] {
assert!(!text.contains(marker), "{text}");
}
}
}
}
119 changes: 87 additions & 32 deletions rust/src/providers/alibaba/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,10 @@ pub(crate) fn parse_response(json: &serde_json::Value) -> Result<UsageSnapshot,
.and_then(|ms| Utc.timestamp_opt(ms / 1000, 0).single())
};

let pct = |used_key: &str, total_key: &str| -> f64 {
let used = quota.get(used_key).and_then(|v| v.as_f64()).unwrap_or(0.0);
let total = quota.get(total_key).and_then(|v| v.as_f64()).unwrap_or(1.0);
if total > 0.0 {
(used / total * 100.0).clamp(0.0, 100.0)
} else {
0.0
}
let pct = |used_key: &str, total_key: &str| -> Option<f64> {
let used = quota.get(used_key).and_then(|v| v.as_f64())?;
let total = quota.get(total_key).and_then(|v| v.as_f64())?;
(total > 0.0).then(|| (used / total * 100.0).clamp(0.0, 100.0))
};

let detail = |used_key: &str, total_key: &str| -> Option<String> {
Expand All @@ -90,30 +86,48 @@ pub(crate) fn parse_response(json: &serde_json::Value) -> Result<UsageSnapshot,
))
};

let five_hour = RateWindow::with_details(
pct("per5HourUsedQuota", "per5HourTotalQuota"),
Some(300),
ms_to_dt("per5HourQuotaNextRefreshTime"),
detail("per5HourUsedQuota", "per5HourTotalQuota"),
);
let weekly = RateWindow::with_details(
pct("perWeekUsedQuota", "perWeekTotalQuota"),
Some(7 * 24 * 60),
ms_to_dt("perWeekQuotaNextRefreshTime"),
detail("perWeekUsedQuota", "perWeekTotalQuota"),
);
let monthly_reset = ms_to_dt("perBillMonthQuotaNextRefreshTime");
let monthly = RateWindow::with_details(
pct("perBillMonthUsedQuota", "perBillMonthTotalQuota"),
RateWindow::monthly_window_minutes(monthly_reset).or(Some(30 * 24 * 60)),
monthly_reset,
detail("perBillMonthUsedQuota", "perBillMonthTotalQuota"),
);

Ok(UsageSnapshot::new(five_hour)
.with_secondary(weekly)
.with_tertiary(monthly)
.with_login_method(plan_name))
let five_hour = pct("per5HourUsedQuota", "per5HourTotalQuota").map(|percent| {
RateWindow::with_details(
percent,
Some(300),
ms_to_dt("per5HourQuotaNextRefreshTime"),
detail("per5HourUsedQuota", "per5HourTotalQuota"),
)
});
let weekly = pct("perWeekUsedQuota", "perWeekTotalQuota").map(|percent| {
RateWindow::with_details(
percent,
Some(7 * 24 * 60),
ms_to_dt("perWeekQuotaNextRefreshTime"),
detail("perWeekUsedQuota", "perWeekTotalQuota"),
)
});
let monthly = pct("perBillMonthUsedQuota", "perBillMonthTotalQuota").map(|percent| {
let reset = ms_to_dt("perBillMonthQuotaNextRefreshTime");
RateWindow::with_details(
percent,
RateWindow::monthly_window_minutes(reset).or(Some(30 * 24 * 60)),
reset,
detail("perBillMonthUsedQuota", "perBillMonthTotalQuota"),
)
});
if five_hour.is_none() && weekly.is_none() && monthly.is_none() {
return Err(ProviderError::Parse(
"Alibaba response contains no usable quota".into(),
));
}
let mut snapshot = UsageSnapshot::new(
five_hour
.unwrap_or_else(|| RateWindow::informational("Alibaba five-hour usage unavailable")),
)
.with_login_method(plan_name);
if let Some(weekly) = weekly {
snapshot = snapshot.with_secondary(weekly);
}
if let Some(monthly) = monthly {
snapshot = snapshot.with_tertiary(monthly);
}
Ok(snapshot)
}

fn fmt_tokens(n: i64) -> String {
Expand Down Expand Up @@ -248,3 +262,44 @@ mod tests {
assert_eq!(fmt_tokens(i64::MIN), "-9,223,372,036,854,775,808");
}
}

#[cfg(test)]
mod cx289a_tests {
use super::*;
use serde_json::json;
fn payload(quota: serde_json::Value) -> serde_json::Value {
json!({"data":{"DataV2":{"data":{"data":{"codingPlanInstanceInfos":[{"codingPlanQuotaInfo":quota}]}}}}})
}
#[test]
fn cx289a_incomplete_quotas_are_rejected() {
for quota in [
json!({}),
json!({"per5HourTotalQuota":100}),
json!({"per5HourUsedQuota":0}),
] {
assert!(parse_response(&payload(quota)).is_err());
}
}
#[test]
fn cx289a_weekly_only_keeps_primary_unknown() {
let snapshot = parse_response(&payload(
json!({"perWeekUsedQuota":0,"perWeekTotalQuota":100}),
))
.unwrap();
assert!(snapshot.primary.is_informational);
let weekly = snapshot.secondary.unwrap();
assert_eq!(weekly.used_percent, 0.0);
assert!(!weekly.is_informational);
assert!(snapshot.tertiary.is_none());
}
#[test]
fn cx289a_real_zero_is_preserved() {
let snapshot = parse_response(&payload(
json!({"per5HourUsedQuota":0,"per5HourTotalQuota":100}),
))
.unwrap();
assert_eq!(snapshot.primary.used_percent, 0.0);
assert!(!snapshot.primary.is_informational);
assert!(snapshot.secondary.is_none());
}
}
Loading