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:
89
packages/comms/src/__tests__/liveness.test.ts
Normal file
89
packages/comms/src/__tests__/liveness.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { classifyLiveness, computeFleetLiveness } from '../liveness.js';
|
||||
import type { HeartbeatObservation, LivenessPolicy } from '../types.js';
|
||||
|
||||
// Small, dev-scale policy so the arithmetic is obvious:
|
||||
// online window = interval * missTolerance = 1000 * 2 = 2000ms
|
||||
// dark threshold = 5000ms
|
||||
const policy: LivenessPolicy = {
|
||||
heartbeatIntervalMs: 1000,
|
||||
missTolerance: 2,
|
||||
darkThresholdMs: 5000,
|
||||
};
|
||||
|
||||
describe('classifyLiveness (deterministic, heartbeat-age based — RFC-001 §4.5)', () => {
|
||||
it('is online when age is within interval * missTolerance', () => {
|
||||
expect(classifyLiveness(0, policy)).toBe('online');
|
||||
expect(classifyLiveness(1999, policy)).toBe('online');
|
||||
expect(classifyLiveness(2000, policy)).toBe('online'); // inclusive boundary
|
||||
});
|
||||
|
||||
it('is away when past the online window but before dark threshold', () => {
|
||||
expect(classifyLiveness(2001, policy)).toBe('away');
|
||||
expect(classifyLiveness(4999, policy)).toBe('away');
|
||||
});
|
||||
|
||||
it('is offline/dark at or past the dark threshold', () => {
|
||||
expect(classifyLiveness(5000, policy)).toBe('offline');
|
||||
expect(classifyLiveness(50_000, policy)).toBe('offline');
|
||||
});
|
||||
|
||||
it('treats a never-seen agent (Infinity age) as offline', () => {
|
||||
expect(classifyLiveness(Number.POSITIVE_INFINITY, policy)).toBe('offline');
|
||||
});
|
||||
|
||||
it('never returns online for a negative-but-huge misconfig (guards NaN)', () => {
|
||||
// A NaN age must fail safe to offline, not silently report online.
|
||||
expect(classifyLiveness(Number.NaN, policy)).toBe('offline');
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeFleetLiveness (A2/A3 core)', () => {
|
||||
const now = 100_000;
|
||||
const obs = (slug: string, lastSeenTs: number, lastSeq = 1): HeartbeatObservation => ({
|
||||
slug,
|
||||
mxid: `@agent-${slug}:matrix.localhost`,
|
||||
lastSeenTs,
|
||||
lastSeq,
|
||||
assertedStatus: 'online',
|
||||
});
|
||||
|
||||
it('classifies a live fleet: fresh=online, stale=away, dark=offline', () => {
|
||||
const result = computeFleetLiveness(
|
||||
[
|
||||
obs('alpha', now - 500), // 500ms old -> online
|
||||
obs('bravo', now - 3000), // 3000ms old -> away
|
||||
obs('charlie', now - 8000), // 8000ms old -> offline
|
||||
],
|
||||
now,
|
||||
policy,
|
||||
);
|
||||
const byslug = Object.fromEntries(result.map((r) => [r.slug, r.status]));
|
||||
expect(byslug).toEqual({ alpha: 'online', bravo: 'away', charlie: 'offline' });
|
||||
});
|
||||
|
||||
it('A3: a previously-online agent flips to offline once age crosses dark threshold', () => {
|
||||
const lastBeat = 100_000; // agent was hard-killed right after this beat
|
||||
// Just before the threshold it is still merely "away"...
|
||||
const justBefore = computeFleetLiveness([obs('victim', lastBeat, 7)], lastBeat + 4999, policy);
|
||||
expect(justBefore[0]?.status).toBe('away');
|
||||
// ...and the instant age reaches darkThresholdMs it is deterministically offline,
|
||||
// with no dependence on native Matrix presence timeouts.
|
||||
const atThreshold = computeFleetLiveness([obs('victim', lastBeat, 7)], lastBeat + 5000, policy);
|
||||
expect(atThreshold[0]?.status).toBe('offline');
|
||||
expect(atThreshold[0]?.ageMs).toBe(5000);
|
||||
expect(atThreshold[0]?.lastSeq).toBe(7);
|
||||
});
|
||||
|
||||
it('reports ageMs and preserves mxid/slug/seq for the human view', () => {
|
||||
const [row] = computeFleetLiveness([obs('alpha', now - 1200, 42)], now, policy);
|
||||
expect(row).toMatchObject({
|
||||
slug: 'alpha',
|
||||
mxid: '@agent-alpha:matrix.localhost',
|
||||
ageMs: 1200,
|
||||
lastSeq: 42,
|
||||
status: 'online',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user