feat(comms): P1 presence — minimal Synapse + fleet presence room + mosaic.presence heartbeat + liveness (#888)
Some checks failed
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed

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:
2026-07-25 21:03:18 +00:00
committed by Mos
parent fabde1c834
commit 2698ddb7b5
33 changed files with 2140 additions and 0 deletions

View 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,
};
});
}