use std::time::Duration;
use super::CheckResult;
use crate::prompt_facts::{
render_stale_tier_s_report, stale_tier_s_facts, TierSFact, TIER_S_MAX_FACTS,
TIER_S_REAFFIRM_DAYS,
};
const FETCH_TIMEOUT: Duration = Duration::from_secs(5);
pub(super) const PROMPT_FACTS_PATH: &str = "/api/v1/kg/prompt-facts";
pub async fn check_tier_s_reaffirmation() -> CheckResult {
let label = "Tier S re-affirmation".to_string();
let addr = match trusty_common::read_daemon_addr("trusty-memory") {
Ok(Some(a)) => a,
Ok(None) => {
return CheckResult::unknown(
label,
"no daemon address recorded — the Tier S surface lives in the daemon's palace \
registry, so its freshness is UNKNOWN while the daemon is down. Start it with \
`trusty-memory service start` and re-run."
.to_string(),
);
}
Err(e) => {
return CheckResult::unknown(
label,
format!("could not read the daemon address file: {e:#} — freshness is UNKNOWN"),
);
}
};
let base = if addr.starts_with("http://") || addr.starts_with("https://") {
addr
} else {
format!("http://{addr}")
};
let url = format!("{base}{PROMPT_FACTS_PATH}");
let client = match reqwest::Client::builder().timeout(FETCH_TIMEOUT).build() {
Ok(c) => c,
Err(e) => {
return CheckResult::unknown(label, format!("could not build HTTP client: {e}"));
}
};
let facts: Vec<TierSFact> = match client.get(&url).send().await {
Ok(resp) if resp.status().is_success() => match resp.json().await {
Ok(f) => f,
Err(e) => {
return CheckResult::unknown(
label,
format!(
"{url} answered, but the body could not be decoded as Tier S facts: {e}. \
A daemon predating #4890 does not report `affirmed_at`; upgrade it to \
get a real answer."
),
);
}
},
Ok(resp) => {
return CheckResult::unknown(label, format!("{url} → {}", resp.status()));
}
Err(e) => {
return CheckResult::unknown(
label,
format!("{url} did not answer ({e}) — Tier S freshness is UNKNOWN"),
);
}
};
interpret_tier_s_facts(label, &facts, chrono::Utc::now())
}
pub(super) fn interpret_tier_s_facts(
label: String,
facts: &[TierSFact],
now: chrono::DateTime<chrono::Utc>,
) -> CheckResult {
let stale = stale_tier_s_facts(facts, now);
if stale.is_empty() {
return CheckResult::pass(
label,
format!(
"{} of {TIER_S_MAX_FACTS} standing facts active, all affirmed within \
{TIER_S_REAFFIRM_DAYS} days",
facts.len()
),
);
}
CheckResult::warn(
label,
format!(
"{} of {} active standing fact(s) have not been re-affirmed in {TIER_S_REAFFIRM_DAYS} \
days (ADR-0028 D8). Tier S is injected into every turn of every session, so a rule \
that stopped being true is paid for on every turn until someone notices. Nothing was \
removed — retirement is a deliberate human act. Re-affirm a rule by asserting it \
again with `kg_assert` (re-asserting it verbatim counts), or retire it with \
`remove_prompt_fact` passing its `subject` and `predicate`. Overdue:{}",
stale.len(),
facts.len(),
render_stale_tier_s_report(&stale),
),
)
}
#[cfg(test)]
#[path = "tier_s_tests.rs"]
mod tests;