feat(comms): P1 presence — minimal Synapse + fleet presence room + mosaic.presence heartbeat + liveness (#888)
Co-authored-by: jason.woltje <jason@diversecanvas.com> Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #888.
This commit is contained in:
63
packages/comms/src/liveness.ts
Normal file
63
packages/comms/src/liveness.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Deterministic liveness computation (RFC-001 §4.5).
|
||||
*
|
||||
* The authoritative liveness signal is the `mosaic.presence` heartbeat, NOT
|
||||
* native Matrix presence. Given the age of an agent's last heartbeat and a
|
||||
* policy, these pure functions classify online/away/offline the same way every
|
||||
* time — which is exactly what makes the A3 "hard-killed agent flips to
|
||||
* offline within dark_threshold" guarantee deterministic and testable without
|
||||
* standing up a homeserver.
|
||||
*/
|
||||
|
||||
import type {
|
||||
AgentLiveness,
|
||||
HeartbeatObservation,
|
||||
LivenessPolicy,
|
||||
PresenceStatus,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
* Classify a single agent from the age (ms) of its last heartbeat.
|
||||
*
|
||||
* - `age <= heartbeatIntervalMs * missTolerance` → **online**
|
||||
* - `age < darkThresholdMs` → **away**
|
||||
* - otherwise (or non-finite age) → **offline / dark**
|
||||
*
|
||||
* A non-finite age (never seen / NaN) fails safe to `offline`: we never assert
|
||||
* a liveness we cannot substantiate.
|
||||
*/
|
||||
export function classifyLiveness(ageMs: number, policy: LivenessPolicy): PresenceStatus {
|
||||
if (!Number.isFinite(ageMs)) {
|
||||
return 'offline';
|
||||
}
|
||||
const onlineWindowMs = policy.heartbeatIntervalMs * policy.missTolerance;
|
||||
if (ageMs <= onlineWindowMs) {
|
||||
return 'online';
|
||||
}
|
||||
if (ageMs < policy.darkThresholdMs) {
|
||||
return 'away';
|
||||
}
|
||||
return 'offline';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute liveness for every observed agent at wall-clock `nowMs`.
|
||||
* The result order mirrors the input order (stable for display).
|
||||
*/
|
||||
export function computeFleetLiveness(
|
||||
observations: readonly HeartbeatObservation[],
|
||||
nowMs: number,
|
||||
policy: LivenessPolicy,
|
||||
): AgentLiveness[] {
|
||||
return observations.map((o) => {
|
||||
const ageMs = nowMs - o.lastSeenTs;
|
||||
return {
|
||||
slug: o.slug,
|
||||
mxid: o.mxid,
|
||||
status: classifyLiveness(ageMs, policy),
|
||||
lastSeenTs: o.lastSeenTs,
|
||||
ageMs,
|
||||
lastSeq: o.lastSeq,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user