feat(analytics-controller): add independent marketing consent and Segment context flag - #10232
gauthierpetetin wants to merge 5 commits into
Conversation
…ment context flag Classify named track/view events as marketing or product, gate each lane on its own consent, and stamp context.marketing for destinations. Phase 1 keeps marketingEventNames as a persisted/seeded list without a remote fetch. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 15829c6. Configure here.
Regenerate messenger action types, fix formatting, link the changelog to #10232, and classify fragments from event names only so caller context.marketing cannot bypass the correct consent lane. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🟡 Changes recommended
Consent-lane handling issues remain for identify events, persisted fragments, and mixed-fragment emission.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds independent marketing consent handling and Segment context flags to AnalyticsController.
Changes:
- Adds marketing consent state, selectors, APIs, and actions.
- Classifies events and routes queues/fragments by consent lane.
- Updates tests, documentation, and changelog.
File summaries
| File | Summary |
|---|---|
packages/analytics-controller/src/selectors.ts |
Adds marketing consent selectors. |
packages/analytics-controller/src/selectors.test.ts |
Tests the new selectors. |
packages/analytics-controller/src/index.ts |
Exports new action types. |
packages/analytics-controller/src/AnalyticsController.ts |
Implements consent lanes and event classification. |
packages/analytics-controller/src/AnalyticsController.test.ts |
Tests marketing consent behavior and lane handling. |
packages/analytics-controller/src/AnalyticsController-method-action-types.ts |
Defines marketing consent actions. |
packages/analytics-controller/README.md |
Documents marketing consent and event classification. |
packages/analytics-controller/CHANGELOG.md |
Records the new functionality. |
Review details
Suppressed comments (3)
packages/analytics-controller/src/AnalyticsController.test.ts:5012
- This test name says the context remains unset, but the assertion verifies that
context.marketing: falseis added. The contradictory name makes the intended backward-compatibility behavior unclear; rename it to describe the stamped context.
it('keeps context unset when updating a persisted fragment that has none', async () => {
packages/analytics-controller/src/AnalyticsController.ts:809
- The public
initand#maybeResolveLocationdocumentation still describes geolocation as being deferred untiloptIn, but this new condition also starts it afteroptInToMarketing. Update those JSDoc blocks to document both consent lanes, since this changes when location data is requested.
// Resolve geolocation only when the user is already opted in to product or
// marketing analytics. For undecided or opted-out users it is deferred to
// {@link optIn} / {@link optInToMarketing}. Awaited so that an already-opted-in
// session has location available before events replay.
packages/analytics-controller/src/AnalyticsController.ts:1623
- This admits a mixed fragment as the marketing lane, but
#emitEventFragmentstill sends each declared name throughtrackEvent, which reclassifies it from the individual name. With product consent off and marketing consent on, a mixed fragment's productinitialEventis therefore silently dropped even though the changelog says mixed fragments are treated as marketing. Propagate the fragment lane through emission (or otherwise make the mixed-fragment policy consistent) so all of its lifecycle events are gated and stamped the same way.
const captureAllowed = fragment
? this.#isCaptureAllowed(this.#laneFromFragment(fragment))
: this.#isCaptureAllowed(AnalyticsLane.Product) ||
this.#isCaptureAllowed(AnalyticsLane.Marketing);
- Files reviewed: 8/8 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Classify identify before trusting context.marketing so a caller-supplied marketing stamp cannot retain identify across product opt-out. Co-authored-by: Cursor <cursoragent@cursor.com>
…ng lanes Keep persisted fragments on their capture-time lane when marketingEventNames is missing or changed, while create still classifies and stamps from names. Co-authored-by: Cursor <cursoragent@cursor.com>
Make #laneFromQueuedEvent exhaustive after the identify-first check so the unreachable product fallback no longer breaks the coverage threshold. Co-authored-by: Cursor <cursoragent@cursor.com>

Explanation
Product analytics (
optedIn) and marketing analytics (optedInToMarketing) are independent consent lanes onAnalyticsController.Named
track/viewevents are classified first against the persistedmarketingEventNameslist. Marketing names are governed only by marketing consent. Everything else is governed only by product consent. Queues, fragments, and delivery share the same machinery for both lanes.identifyhas no name, so it stays on product consent.At capture, named payloads stamp
context.marketing(trueorfalse) so Segment destinations can separate marketing from product without reading properties. Destinations should treat a missing flag as product (older clients).Phase 1: there is no remote fetch yet.
#fetchMarketingEventNamesis a no-op stub. With an empty list, every named event is treated as product, so marketing consent APIs exist but do not change classification until a list is seeded or persisted.References
Checklist
Made with Cursor