Skip to content

Pulse — platform-wide activity stream

Pulse is Cavaridge™‘s platform-wide activity stream. Every customer-facing app emits Pulse events on user-journey milestones and per-app domain events. The status page, activity feeds, and analytics all consume from this stream.

  • Canonical events (5). Every app must emit these on the relevant user-journey milestones: signup, first_meaningful_action, daily_active, paid_conversion, churn_signal.
  • Trial-lifecycle events (7). PROD-03 added a per-trial subset: trial_started, trial_first_action_started, trial_first_action_completed, trial_help_needed, trial_converted, trial_downgraded_to_free, trial_expired_no_action.
  • Per-app domain events. Each app declares its verbs (e.g., aegis:scan_completed, forge:report_generated, nimbus:domain_registered).

The pulse-coverage gate enforces emit/consume parity: every domain event declared in packages/core-events/src/pulse-events.ts must have at least one emitter and a consumer reachable through the activity feed. CI fails if a verb is registered but never emitted, or emitted but never read.

import { pulseEmit } from "@cavaridge/core-events";
await pulseEmit({
event: "scan_completed",
tenantId,
sourceApp: "aegis",
summary: "AEGIS scan completed for tenant",
metadata: { scanId, durationSeconds },
});

pulseEmit is fire-and-forget — it never throws and never blocks the calling request. Severity defaults to success for *_completed events and info otherwise.

  • Don’t add a new domain event without registering it in packages/core-events/src/pulse-events.ts — the coverage gate will fail.
  • Don’t await Pulse emits inside a hot path expecting them to back-pressure — they are best-effort.
  • Don’t paste raw user input into summary without sanitization.