feat: add the growthsignals activity taxonomy and emitter - #6057
feat: add the growthsignals activity taxonomy and emitter#6057simplesagar wants to merge 5 commits into
Conversation
Replaces the eleven ad-hoc Gram Slack destinations with a single gram_activity event derived from the existing audit-log outbox stream, plus purpose-built destinations for the significant, firehose and all-events channels. Temporal actions/month: 0 (outbox -> existing streams handler, no Temporal). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01761VcQZ1sW4TFb16AoozTA
Describes notable moments in Gram as a single PostHog `gram_activity` event with a stable property shape: the activity taxonomy, the map from audit action to activity, the event builder, a repo-backed enricher behind a TTL cache, and the emitter itself. Uncurated audit actions fall through to a normalized pass-through name so the firehose has coverage without an allowlist to maintain, and a small exclusion list keeps high-volume noise out. The emitter skips the demo organization and logs capture failures rather than returning them, so a dropped analytics event can never fail the request that produced it. `dashboard_url` is the one property that is never omitted, because a Slack destination that renders it as a button link fails the whole message when the url is empty. It falls back to the organization's page and then to the site root. Nothing calls this package yet, so no events are emitted and no behaviour changes. Temporal actions/month: 0 (no background work added). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01761VcQZ1sW4TFb16AoozTA
🦋 Changeset detectedLatest commit: 2a19984 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
Three findings from review. An extra could occupy a key the event shape owns. Writing extras first was not enough on its own: a base property whose value is empty is omitted rather than written, and the project keys are skipped entirely on an organization-scoped activity, so an extra keyed `organization_slug` survived whenever the slug did not resolve. Reserved keys are now filtered when extras are copied. `dashboard_url` is now omitted rather than reported blank when no site URL is configured. An empty string is worse than an absent property: a Slack destination rendering it as a button link fails the whole message on a blank url, while an absent property lets the template omit the button. The emitter says so once at construction. Concurrent misses for the same key now collapse into one load. A burst of stream messages from one organization all miss together on the first event, and each was issuing its own query for the same row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01761VcQZ1sW4TFb16AoozTA
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Two findings on the coalescing added in the previous commit, plus a nit. The flight now re-reads the cache before loading. A caller that missed before an earlier flight finished could still enter the callback after that flight left the group, issuing a second query for a row already cached. Waiting on somebody else's query no longer ignores this caller's context. A canceled stream handler was held until an unrelated request's database query returned; it now leaves on cancellation while the flight continues for whoever else is waiting. The reserved-key test uses the exported property constant rather than a literal, matching its neighbour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01761VcQZ1sW4TFb16AoozTA
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
The coalesced flight inherited the context of whichever caller happened to start it, so that caller going away failed the lookup for everyone still waiting on it — the opposite of what coalescing is for. The shared load now runs on a context detached from cancellation but keeping its values, so it stays inside the originating trace, bounded by its own timeout so a detached query cannot outlive its usefulness. Callers still leave on their own cancellation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01761VcQZ1sW4TFb16AoozTA
Note
Stacked PR — merge bottom-up. Each PR targets the one above it, so its Files tab shows only its own diff.
Merging #6057 retargets #6058 to
mainautomatically, and so on down the stack.GRW-66
First of a five-PR stack. Nothing calls this package yet, so no events are emitted and no behaviour changes.
Summary
Adds
server/internal/growthsignals/, which describes notable moments in Gram as a single PostHoggram_activityevent with a stable property shape:Also includes the design document the rest of the stack follows.
Two decisions worth a reviewer's attention:
dashboard_urlis never omitted. A Slack destination that renders it as a button link fails the whole message when the url is empty, so it falls back to the organization's page and then to the site root.Motivation
The events Growth wants to see do not exist in PostHog. There is no event for a project being created, an MCP server deployed, a security policy written, or a member joining an existing organization.
Every one of those mutations is already recorded by the audit logger and published to Pub/Sub, and
gram streamsalready consumes that stream. The signal exists; nothing forwards it. This package is the shared vocabulary the rest of the stack uses to do that.Temporal actions/month: 0, scales with fixed. No background work is added.
Summary by cubic
Adds the
growthsignalspackage, which describes notable moments in Gram as a single PostHoggram_activityevent with a stable property shape. This is PR 1 of a five-PR stack for GRW-66 (revamping PostHog Slack notifications); nothing calls the package yet, so no events are emitted and no behavior changes.The package carries the activity taxonomy, the map from audit action to activity, the event builder, a repo-backed enricher behind a TTL cache, and the emitter, which skips the demo organization and logs capture failures rather than returning them. The design document the rest of the stack follows is included.
Reviewer notes
dashboard_urlis never omitted: a Slack destination that renders it as a button link fails the whole message when the URL is empty, so it falls back to the organization's page and then to the site root. With no site URL configured, it is omitted rather than sent blank, so the template can drop the button instead of failing the message.Written for commit 2a19984. Summary will update on new commits.