Repository navigation
feat(flags): measure per-user experiments by assignment reason and exposure - #716
Merged
Merged
Conversation
- carry each flag's resolution reason (static/split/override) from the server evaluation into the provider and onto RUM signals as lorekit.feature_flag.reason.<key> - replace, rather than append, flag signal attributes on re-sync so an override flip no longer leaves both arms on every later signal - add feature_flag.exposed, emitted once per mount from a flag's resolver with the semantic-convention flag attributes; InsightsView emits it - declare insights-visual as a disabled 50/50 experiment (on listed first so ramping never moves a user back to off) Co-authored-by: dash0-dev[bot] <257284812+dash0-dev[bot]@users.noreply.github.com>
Running an experiment: declare arms dark with the treatment first, count exposure in the resolver, launch and ramp, measure exposed users per arm with a sample-ratio check, and outcomes with overrides excluded. Documents the reason attribute, the replace-on-resync behaviour, the initial page-load tagging gap, and corrects the span reason values to the lowercase ones the hook emits. Co-authored-by: dash0-dev[bot] <257284812+dash0-dev[bot]@users.noreply.github.com>
Contributor
|
The dashboard preview for this PR — redeployed on each push that changes the web app.
Preview always points at this PR's latest commit; Deployment is the immutable build of |
Contributor
Author
⚪ 2 non-blocking findings · 2 notesReason and exposure plumbing holds: the deferred event lands after identify and the RUM sync, every caller is updated, the PromQL labels match Dash0's normalisation, and no regeneration is needed. Four low-severity doc/test notes.
Notes (2) — posted inline
Review details
Memories used
Nothing to report — measurability (skipped), integrations (not activated), 0 files skipped.
|
- derive the dark-experiment test from the registry, so launching one is still a single enabled: true edit - show the per-arm rate as one PromQL division, relabelling the exposure side onto the outcome side's label - say the exposure deferral exists for the provider's own RUM sync - name override as a custom reason value, not a semconv one Co-authored-by: dash0-dev[bot] <257284812+dash0-dev[bot]@users.noreply.github.com>
mthines
approved these changes
Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Per-user 50/50 splits already work (signed-in users are bucketed by Supabase user id), but the result could not be measured honestly: RUM carried the variant without saying how it was assigned, so a developer override read as treatment, and nothing recorded who actually reached a flagged surface.
What changed
static/split/override) flows from the server evaluation into the provider and onto every RUM signal aslorekit.feature_flag.reason.<key>.useFeatureFlagExposureemitsfeature_flag.exposed(semconv flag attributes) once per mount from a resolver;InsightsViewcalls it.insights-visualdeclares a 50/50 experiment, disabled (onfirst, so ramping never moves a user back tooff).docs/feature-flags.md§ Running an experiment: declare, expose, launch/ramp, measure (exposed users per arm, sample-ratio check, outcomes with overrides excluded), plus the known page-load tagging gap.How to verify
cd packages/feature-flags && npx vitest runandcd packages/web && npx vitest run src/lib/{dash0-rum,analytics/track}.spec.tsNotes for reviewers
Nothing user-visible changes (the experiment stays off), so the public docs and
llms.txtare untouched;docs/feature-flags.mdanddocs/otel.mdcarry the contributor-facing changes. Page-load signals (page view, early web vitals, browser errors) still miss flag tags becauseinit()runs before flags are known — documented, left for a follow-up.