use tracing::{debug, info, warn};
use vti_common::consent::{ConsentGrant, PendingConsent};
use crate::error::AppError;
use crate::store::KeyspaceHandle;
fn now_epoch() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
pub async fn sweep_expired(
consent_ks: &KeyspaceHandle,
audit_ks: &KeyspaceHandle,
) -> Result<(), AppError> {
let now = now_epoch();
let mut pruned = 0usize;
for (key, value) in consent_ks.prefix_iter_raw("consent_pending:").await? {
let pending: PendingConsent = match serde_json::from_slice(&value) {
Ok(p) => p,
Err(e) => {
debug!(error = %e, "consent sweeper: skipping unreadable pending row");
continue;
}
};
if now >= pending.expires_at {
consent_ks.remove(key).await?;
pruned += 1;
audit_expire(audit_ks, &pending.subject.agent).await;
}
}
for (key, value) in consent_ks.prefix_iter_raw("grant:").await? {
let grant: ConsentGrant = match serde_json::from_slice(&value) {
Ok(g) => g,
Err(e) => {
debug!(error = %e, "consent sweeper: skipping unreadable grant row");
continue;
}
};
if grant.is_expired(now) {
consent_ks.remove(key).await?;
pruned += 1;
audit_expire(audit_ks, &grant.subject.agent).await;
}
}
if pruned > 0 {
info!(
consent_pruned = pruned,
"consent sweeper pruned expired records"
);
}
Ok(())
}
async fn audit_expire(audit_ks: &KeyspaceHandle, agent: &str) {
if let Err(e) = crate::audit::record(
audit_ks,
"consent.expire",
"system:sweeper",
Some(agent),
"success",
None,
None,
)
.await
{
warn!(error = %e, "consent sweeper: deletion succeeded but audit::record failed");
}
}