Watch
1
0
Fork
You've already forked souveraine
0

cross last-seen over the wire as seconds-ago per source

TASK-08(f): EvidenceSeen held the stamps internally and nothing outside
the daemon could read them. to_ipc_json now carries
evidence_last_seen_secs_ago; null stays distinct from stale, because
never-reported and long-ago are different claims.
This commit is contained in:
Fimeg 2026-08-15 00:52:52 -04:00
commit 0dd8aaaba0

View file

@ -3315,6 +3315,15 @@ impl DeviceStateMachine {
"light": self.is_fresh(self.evidence_seen.light),
"touch": self.is_fresh(self.evidence_seen.touch),
},
// Seconds since each source last reported; null means never.
// `fresh` is a threshold answer, this is the raw number — a page
// rendering "last seen 4 s ago" needs the number, not the bool.
"evidence_last_seen_secs_ago": {
"proximity": Self::secs_since(self.evidence_seen.proximity),
"accel": Self::secs_since(self.evidence_seen.accel),
"light": Self::secs_since(self.evidence_seen.light),
"touch": Self::secs_since(self.evidence_seen.touch),
},
// Health is the other axis: `fresh` says whether the reading may
// be believed, `health` says whether the source is there at all.
"sensor_health": self.source_health.as_json(),
@ -3327,6 +3336,10 @@ impl DeviceStateMachine {
Instant::now().saturating_duration_since(t) <= self.policy.evidence_ttl
})
}
fn secs_since(seen: Option<Instant>) -> Option<u64> {
seen.map(|t| Instant::now().saturating_duration_since(t).as_secs())
}
}
impl fmt::Display for DeviceState {
@ -4966,6 +4979,34 @@ mod tests {
assert_eq!(json["evidence_fresh"]["proximity"], serde_json::json!(true));
}
#[test]
fn last_seen_crosses_the_wire_as_seconds_ago() {
// TASK-08(f): the machine held last-seen internally and nothing
// outside could read it. null is "never reported", a different claim
// from "long ago".
let mut sm = DeviceStateMachine::new();
let json = sm.to_ipc_json();
assert_eq!(
json["evidence_last_seen_secs_ago"]["proximity"],
serde_json::Value::Null
);
sm.mark_evidence_seen_at(
SensorSource::Proximity,
Instant::now() - Duration::from_secs(30),
);
let json = sm.to_ipc_json();
let secs = json["evidence_last_seen_secs_ago"]["proximity"]
.as_u64()
.expect("a stamp 30 s old crosses as a number");
assert!((30..=31).contains(&secs), "got {secs}");
assert_eq!(
json["evidence_last_seen_secs_ago"]["light"],
serde_json::Value::Null,
"a source that never reported stays null"
);
}
#[test]
fn a_repeated_reading_is_not_a_trail_entry() {
// Reporters heartbeat every 30s so silence is meaningful. If each