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.
Three event tiers
Section titled “Three event tiers”- 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).
Emit + consume parity
Section titled “Emit + consume parity”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.
How to emit
Section titled “How to emit”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.
Pitfalls
Section titled “Pitfalls”- 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
awaitPulse emits inside a hot path expecting them to back-pressure — they are best-effort. - Don’t paste raw user input into
summarywithout sanitization.