返回 CodeWhale
provenance.rs
根目录 / crates / config / src / cloud_facts / provenance.rs
1 //! Provenance for `/status`: where the facts in use came from and how old they are.
2
3 use serde::{Deserialize, Serialize};
4
5 /// Where a verified payload was read from.
6 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7 #[serde(rename_all = "snake_case")]
8 pub enum FactsOrigin {
9 DiskCache,
10 Network,
11 LocalFile,
12 }
13
14 /// The state of the cloud facts layer.
15 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
16 #[serde(tag = "state", rename_all = "snake_case")]
17 pub enum CloudFactsState {
18 /// Feature flag off (default). Bundled facts only.
19 #[default]
20 Off,
21 /// Enabled but no active trusted key is pinned; nothing is fetched.
22 Inert,
23 /// Enabled; no verified payload yet (first launch, or every fetch failed).
24 BundledOnly,
25 /// A verified payload is merged over bundled facts.
26 Verified {
27 channel: String,
28 facts_version: u64,
29 key_id: String,
30 fetched_at: u64,
31 origin: FactsOrigin,
32 stale: bool,
33 patches: usize,
34 defaults: usize,
35 announcements: usize,
36 },
37 /// The last payload was rejected; bundled facts remain in use.
38 Rejected { reason: String, at: u64 },
39 /// Verified but not for this binary version.
40 NotApplicable { applies_to: String },
41 /// Fetch failed; prior verified facts (if any) stay in use.
42 Failed {
43 last_error: String,
44 at: u64,
45 keeping: Option<u64>,
46 },
47 }
48
49 /// Status snapshot for UI.
50 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
51 pub struct CloudFactsStatus {
52 pub state: CloudFactsState,
53 pub last_attempt: Option<u64>,
54 pub etag: Option<String>,
55 pub source_label: String,
56 }
57
58 /// Human-readable age (`12m ago`, `3h ago`, `3d ago`).
59 #[must_use]
60 pub fn age_label(then: u64, now: u64) -> String {
61 let secs = now.saturating_sub(then);
62 if secs < 60 {
63 "just now".to_string()
64 } else if secs < 3600 {
65 format!("{}m ago", secs / 60)
66 } else if secs < 86_400 {
67 format!("{}h ago", secs / 3600)
68 } else {
69 format!("{}d ago", secs / 86_400)
70 }
71 }
72
73 impl CloudFactsStatus {
74 /// One-line `/status` value.
75 #[must_use]
76 pub fn label(&self, now_unix: u64) -> String {
77 match &self.state {
78 CloudFactsState::Off => "off (bundled)".to_string(),
79 CloudFactsState::Inert => "inert (no trusted keys; bundled)".to_string(),
80 CloudFactsState::BundledOnly => "enabled, none verified yet (bundled)".to_string(),
81 CloudFactsState::Verified {
82 channel,
83 facts_version,
84 key_id,
85 fetched_at,
86 origin,
87 stale,
88 patches,
89 defaults,
90 announcements,
91 } => {
92 let origin = match origin {
93 FactsOrigin::DiskCache => "disk cache",
94 FactsOrigin::Network => "network",
95 FactsOrigin::LocalFile => "local file",
96 };
97 let mut out = format!(
98 "{channel} v{facts_version} · verified {key_id} · fetched {} ({origin})",
99 age_label(*fetched_at, now_unix)
100 );
101 if *stale {
102 out.push_str(" · stale");
103 }
104 let _ = std::fmt::Write::write_fmt(
105 &mut out,
106 format_args!(
107 " · {patches} patch{}, {defaults} default{}, {announcements} notice{}",
108 if *patches == 1 { "" } else { "es" },
109 if *defaults == 1 { "" } else { "s" },
110 if *announcements == 1 { "" } else { "s" },
111 ),
112 );
113 out
114 }
115 CloudFactsState::Rejected { reason, at } => {
116 format!(
117 "rejected: {reason} ({}; bundled in use)",
118 age_label(*at, now_unix)
119 )
120 }
121 CloudFactsState::NotApplicable { applies_to } => {
122 format!("not applicable to this build ({applies_to}; bundled in use)")
123 }
124 CloudFactsState::Failed {
125 last_error,
126 at,
127 keeping,
128 } => match keeping {
129 Some(version) => format!(
130 "fetch failed {} ({last_error}); keeping v{version}",
131 age_label(*at, now_unix)
132 ),
133 None => format!(
134 "fetch failed {} ({last_error}); bundled in use",
135 age_label(*at, now_unix)
136 ),
137 },
138 }
139 }
140 }
141
141 lines RUST