[{"data":1,"prerenderedAt":1762},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fexperimentation":188,"\u002Fdocs\u002Fexperimentation-surround":1757},[4,8,20,24,59,65,69,73,77,100,104,108,112,116,120,124,128,160,164,168,172,176,180,184],{"title":5,"path":6,"stem":7},"","\u002Fdocs","1.docs\u002Findex",{"title":9,"path":10,"stem":11,"children":12,"icon":19},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F01.getting-started\u002F1.index",[13,15],{"title":14,"path":10,"stem":11},"Bosca",{"title":16,"path":17,"stem":18},"Quickstart","\u002Fdocs\u002Fgetting-started\u002Fquickstart","1.docs\u002F01.getting-started\u002F2.quickstart",false,{"title":21,"path":22,"stem":23},"Kit","\u002Fdocs\u002Fkit","1.docs\u002F01.kit",{"title":25,"path":26,"stem":27,"children":28},"Content","\u002Fdocs\u002Fcontent","1.docs\u002F02.content\u002F1.index",[29,31,53],{"title":30,"path":26,"stem":27},"Content Strategy",{"title":32,"path":33,"stem":34,"children":35},"Metadata","\u002Fdocs\u002Fcontent\u002Fmetadata","1.docs\u002F02.content\u002F1.metadata\u002F1.index",[36,37,41,45,49],{"title":32,"path":33,"stem":34},{"title":38,"path":39,"stem":40},"Supplementary","\u002Fdocs\u002Fcontent\u002Fmetadata\u002Fsupplementary","1.docs\u002F02.content\u002F1.metadata\u002F2.supplementary",{"title":42,"path":43,"stem":44},"Documents","\u002Fdocs\u002Fcontent\u002Fmetadata\u002Fdocuments","1.docs\u002F02.content\u002F1.metadata\u002F3.documents",{"title":46,"path":47,"stem":48},"Guides","\u002Fdocs\u002Fcontent\u002Fmetadata\u002Fguides","1.docs\u002F02.content\u002F1.metadata\u002F4.guides",{"title":50,"path":51,"stem":52},"Bible","\u002Fdocs\u002Fcontent\u002Fmetadata\u002Fbible","1.docs\u002F02.content\u002F1.metadata\u002F5.bible",{"title":54,"path":55,"stem":56,"children":57},"Collections","\u002Fdocs\u002Fcontent\u002Fcollections","1.docs\u002F02.content\u002F2.collections\u002F1.index",[58],{"title":54,"path":55,"stem":56},{"title":60,"path":61,"stem":62,"children":63},"Workflows","\u002Fdocs\u002Fworkflows","1.docs\u002F03.workflows\u002F1.index",[64],{"title":60,"path":61,"stem":62},{"title":66,"path":67,"stem":68},"Search","\u002Fdocs\u002Fsearch","1.docs\u002F04.search",{"title":70,"path":71,"stem":72},"Profiles","\u002Fdocs\u002Fprofiles","1.docs\u002F05.profiles",{"title":74,"path":75,"stem":76},"Organizations","\u002Fdocs\u002Forganizations","1.docs\u002F06.organizations",{"title":78,"path":79,"stem":80,"children":81,"icon":99},"Engineering","\u002Fdocs\u002Fengineering","1.docs\u002F07.engineering\u002F1.index",[82,83,87,91,95],{"title":78,"path":79,"stem":80},{"title":84,"path":85,"stem":86},"Infrastructure","\u002Fdocs\u002Fengineering\u002Finfrastructure","1.docs\u002F07.engineering\u002F2.infrastructure",{"title":88,"path":89,"stem":90},"Backend Services","\u002Fdocs\u002Fengineering\u002Fservices","1.docs\u002F07.engineering\u002F3.services",{"title":92,"path":93,"stem":94},"Deployment","\u002Fdocs\u002Fengineering\u002Fdeployment","1.docs\u002F07.engineering\u002F4.deployment",{"title":96,"path":97,"stem":98},"Framework Modules","\u002Fdocs\u002Fengineering\u002Fframework","1.docs\u002F07.engineering\u002F5.framework","i-heroicons-wrench-screwdriver",{"title":101,"path":102,"stem":103},"Identity Management","\u002Fdocs\u002Fidentity","1.docs\u002F07.identity",{"title":105,"path":106,"stem":107},"Localization","\u002Fdocs\u002Flocalization","1.docs\u002F08.localization",{"title":109,"path":110,"stem":111},"Analytics","\u002Fdocs\u002Fanalytics","1.docs\u002F09.analytics",{"title":113,"path":114,"stem":115},"AI & Agents","\u002Fdocs\u002Fai","1.docs\u002F10.ai",{"title":117,"path":118,"stem":119},"Messaging & Email","\u002Fdocs\u002Fmessages","1.docs\u002F11.messages",{"title":121,"path":122,"stem":123},"Scheduler","\u002Fdocs\u002Fscheduler","1.docs\u002F12.scheduler",{"title":125,"path":126,"stem":127},"Backup & Restore","\u002Fdocs\u002Fbackup","1.docs\u002F13.backup",{"title":129,"path":130,"stem":131,"children":132},"Architecture","\u002Fdocs\u002Farchitecture","1.docs\u002F14.architecture\u002F1.index",[133,134,137,141,145,149,153,157],{"title":129,"path":130,"stem":131},{"title":92,"path":135,"stem":136},"\u002Fdocs\u002Farchitecture\u002Fdeployment","1.docs\u002F14.architecture\u002F2.deployment",{"title":138,"path":139,"stem":140},"Security","\u002Fdocs\u002Farchitecture\u002Fsecurity","1.docs\u002F14.architecture\u002F3.security",{"title":142,"path":143,"stem":144},"Telemetry","\u002Fdocs\u002Farchitecture\u002Ftelemetry","1.docs\u002F14.architecture\u002F4.telemetry",{"title":146,"path":147,"stem":148},"Administration","\u002Fdocs\u002Farchitecture\u002Fadministration","1.docs\u002F14.architecture\u002F5.administration",{"title":150,"path":151,"stem":152},"GraphQL Schema","\u002Fdocs\u002Farchitecture\u002Fgraphql","1.docs\u002F14.architecture\u002F6.graphql",{"title":154,"path":155,"stem":156},"Storage","\u002Fdocs\u002Farchitecture\u002Fstorage","1.docs\u002F14.architecture\u002F7.storage",{"title":105,"path":158,"stem":159},"\u002Fdocs\u002Farchitecture\u002Flocalization","1.docs\u002F14.architecture\u002F8.localization",{"title":161,"path":162,"stem":163},"Scripting","\u002Fdocs\u002Fscripting","1.docs\u002F15.scripting",{"title":165,"path":166,"stem":167},"Configuration","\u002Fdocs\u002Fconfiguration","1.docs\u002F16.configuration",{"title":169,"path":170,"stem":171},"Forms","\u002Fdocs\u002Fforms","1.docs\u002F17.forms",{"title":173,"path":174,"stem":175},"Segmentation & Campaigns","\u002Fdocs\u002Fsegmentation","1.docs\u002F18.segmentation",{"title":177,"path":178,"stem":179},"Devices & Push","\u002Fdocs\u002Fdevices","1.docs\u002F19.devices",{"title":181,"path":182,"stem":183},"Feature Flags & Experimentation","\u002Fdocs\u002Fexperimentation","1.docs\u002F20.experimentation",{"title":185,"path":186,"stem":187},"Recommendations","\u002Fdocs\u002Frecommendations","1.docs\u002F20.recommendations",{"id":189,"title":181,"body":190,"description":1751,"extension":1752,"meta":1753,"navigation":1754,"path":182,"seo":1755,"stem":183,"__hash__":1756},"docs\u002F1.docs\u002F20.experimentation.md",{"type":191,"value":192,"toc":1722},"minimark",[193,197,202,264,268,273,280,331,335,401,405,459,467,471,477,515,519,525,554,557,560,631,642,647,658,782,807,811,829,832,841,844,897,904,922,926,937,955,961,979,1004,1007,1011,1014,1020,1030,1036,1039,1081,1085,1102,1106,1109,1119,1122,1126,1133,1144,1148,1151,1193,1197,1204,1216,1220,1280,1301,1305,1311,1377,1381,1422,1434,1438,1453,1484,1487,1493,1497,1503,1507,1510,1564,1568,1636,1640,1643,1685,1688],[194,195,196],"p",{},"Bosca includes a feature-flag and experimentation system for delivering software changes to defined audiences, measuring whether they actually moved a metric, and rolling them out gradually. Flags decide what value each user sees; targeting rules decide which users get which split; experiments observe the split and report statistical lift on conversion goals.",[198,199,201],"h2",{"id":200},"what-you-get","What you get",[203,204,205,213,219,225,231,246,252,258],"ul",{},[206,207,208,212],"li",{},[209,210,211],"strong",{},"Feature Flags:"," Boolean, percentage, string, and JSON-valued flags with stable per-user bucket assignments.",[206,214,215,218],{},[209,216,217],{},"Targeted Rollouts:"," Top-to-bottom targeting rules with segment, principal, profile-attribute, and device-attribute conditions.",[206,220,221,224],{},[209,222,223],{},"Weighted Variations:"," Per-rule rollouts that split matched users across multiple variations by weight, with bucket-range stability so widening a rollout doesn't reshuffle existing users.",[206,226,227,230],{},[209,228,229],{},"Experiments:"," Observation and statistics attached to a single targeting rule, comparing variations on conversion goals.",[206,232,233,236,237,241,242,245],{},[209,234,235],{},"Conversion Goals:"," Match analytics events by type, element, and page, with two metric types (",[238,239,240],"code",{},"UNIQUE_CONVERSION"," for binary \"did they convert?\" questions and ",[238,243,244],{},"EVENT_COUNT"," for \"how often per user?\" questions).",[206,247,248,251],{},[209,249,250],{},"Live Distribution:"," Per-flag panel showing the actual variation distribution among recently active users, with a date-range picker.",[206,253,254,257],{},[209,255,256],{},"Mutual Exclusion:"," Exclusion layers prevent the same user from being assigned to conflicting experiments at once.",[206,259,260,263],{},[209,261,262],{},"AI Analysis:"," Automated experiment summaries with significance, lift, and a recommendation.",[198,265,267],{"id":266},"core-concepts","Core Concepts",[269,270,272],"h3",{"id":271},"feature-flags","Feature Flags",[194,274,275,276,279],{},"A ",[209,277,278],{},"FeatureFlag"," is a named decision the system can make at runtime. Each flag has:",[203,281,282,293,299,305,311,318,325],{},[206,283,284,285,288,289,292],{},"A stable ",[209,286,287],{},"key"," clients use to evaluate it (e.g. ",[238,290,291],{},"checkout-redesign",").",[206,294,275,295,298],{},[209,296,297],{},"type"," declaring what shape its values take.",[206,300,275,301,304],{},[209,302,303],{},"palette of variations"," — the candidate values the flag can resolve to.",[206,306,275,307,310],{},[209,308,309],{},"default variation"," served when no targeting rule matches.",[206,312,313,314,317],{},"An ordered list of ",[209,315,316],{},"targeting rules"," that can override the default for matched users.",[206,319,320,321,324],{},"A per-flag ",[209,322,323],{},"salt"," that mixes into bucket-assignment hashes; regenerating it reshuffles every user.",[206,326,275,327,330],{},[209,328,329],{},"status"," controlling whether the flag is currently evaluated.",[269,332,334],{"id":333},"flag-types","Flag Types",[336,337,338,351],"table",{},[339,340,341],"thead",{},[342,343,344,348],"tr",{},[345,346,347],"th",{},"Type",[345,349,350],{},"Variation values are",[352,353,354,371,381,391],"tbody",{},[342,355,356,362],{},[357,358,359],"td",{},[238,360,361],{},"BOOLEAN",[357,363,364,367,368],{},[238,365,366],{},"true"," \u002F ",[238,369,370],{},"false",[342,372,373,378],{},[357,374,375],{},[238,376,377],{},"PERCENTAGE",[357,379,380],{},"numbers 0–100",[342,382,383,388],{},[357,384,385],{},[238,386,387],{},"STRING",[357,389,390],{},"arbitrary strings",[342,392,393,398],{},[357,394,395],{},[238,396,397],{},"JSON",[357,399,400],{},"arbitrary JSON payloads",[269,402,404],{"id":403},"flag-status","Flag Status",[336,406,407,417],{},[339,408,409],{},[342,410,411,414],{},[345,412,413],{},"Status",[345,415,416],{},"Description",[352,418,419,429,439,449],{},[342,420,421,426],{},[357,422,423],{},[238,424,425],{},"DRAFT",[357,427,428],{},"Being built; not yet evaluated",[342,430,431,436],{},[357,432,433],{},[238,434,435],{},"ENABLED",[357,437,438],{},"Evaluating normally per targeting rules",[342,440,441,446],{},[357,442,443],{},[238,444,445],{},"DISABLED",[357,447,448],{},"Turned off; everyone gets the default variation",[342,450,451,456],{},[357,452,453],{},[238,454,455],{},"ARCHIVED",[357,457,458],{},"Retired; hidden from the main UI",[194,460,461,463,464,466],{},[238,462,445],{}," is the \"stop serving this flag\" state used both for planned pauses and oncall incident response — flipping a flag to ",[238,465,445],{}," makes evaluation short-circuit to the default variation regardless of rules.",[269,468,470],{"id":469},"variations","Variations",[194,472,275,473,476],{},[209,474,475],{},"Variation"," is one entry in a flag's palette of candidate values. Each variation has:",[203,478,479,495,502,509],{},[206,480,284,481,483,484,487,488,487,491,494],{},[209,482,287],{}," (e.g. ",[238,485,486],{},"on",", ",[238,489,490],{},"control",[238,492,493],{},"treatment-a",") referenced by targeting rules and bucket assignments. Treat keys as forever-stable once a flag is in production — renaming reshuffles bucket ranges.",[206,496,497,498,501],{},"A human-readable ",[209,499,500],{},"name"," shown in dashboards.",[206,503,504,505,508],{},"An optional ",[209,506,507],{},"description",".",[206,510,275,511,514],{},[209,512,513],{},"value"," returned to clients when this variation is served.",[269,516,518],{"id":517},"targeting-rules","Targeting Rules",[194,520,275,521,524],{},[209,522,523],{},"TargetingRule"," is one entry in a flag's ordered rule list. Each rule has:",[203,526,527,533,541,548],{},[206,528,284,529,532],{},[209,530,531],{},"id"," (UUID string) referenced by attached experiments and by the bucket-assignment hash. Reordering rules in the UI does not change ids.",[206,534,504,535,537,538,540],{},[209,536,500],{}," and ",[209,539,507],{}," for human readers.",[206,542,543,544,547],{},"A list of ",[209,545,546],{},"conditions"," that all must match for the rule to apply (AND semantics).",[206,549,275,550,553],{},[209,551,552],{},"rollout"," describing how matched users are split across variations.",[194,555,556],{},"Rules are evaluated top-to-bottom. The first rule whose conditions all match wins, and matched users are bucketed across that rule's rollout. If no rule matches, the flag's default variation is returned.",[269,558,559],{"id":546},"Conditions",[336,561,562,575],{},[339,563,564],{},[342,565,566,569,572],{},[345,567,568],{},"Condition Type",[345,570,571],{},"Matches when",[345,573,574],{},"Requires",[352,576,577,590,602,614],{},[342,578,579,584,587],{},[357,580,581],{},[238,582,583],{},"Segment",[357,585,586],{},"The user is a member of the named segment",[357,588,589],{},"Authenticated principal",[342,591,592,597,600],{},[357,593,594],{},[238,595,596],{},"Principal",[357,598,599],{},"The user's principal id is in the supplied list",[357,601,589],{},[342,603,604,609,612],{},[357,605,606],{},[238,607,608],{},"ProfileAttribute",[357,610,611],{},"A value inside a profile attribute's JSON object matches via an operator",[357,613,589],{},[342,615,616,621,628],{},[357,617,618],{},[238,619,620],{},"DeviceAttribute",[357,622,623,624,627],{},"A field on the analytics ",[238,625,626],{},"Device"," payload matches via an operator",[357,629,630],{},"Client-supplied device",[194,632,633,634,637,638,641],{},"Every condition type supports a ",[238,635,636],{},"negate"," flag, which inverts the match result without needing a separate ",[238,639,640],{},"NOT_X"," operator.",[643,644,646],"h4",{"id":645},"deviceattribute-keys","DeviceAttribute keys",[194,648,649,651,652,654,655,657],{},[238,650,620],{}," conditions resolve their ",[238,653,287],{}," against the analytics SDK's ",[238,656,626],{}," class — the same object the SDK ships when emitting analytics events. The recognized keys (with common aliases) are:",[336,659,660,670],{},[339,661,662],{},[342,663,664,667],{},[345,665,666],{},"Key",[345,668,669],{},"Aliases",[352,671,672,684,693,702,711,726,741,753,767],{},[342,673,674,679],{},[357,675,676],{},[238,677,678],{},"installationId",[357,680,681],{},[238,682,683],{},"installation_id",[342,685,686,691],{},[357,687,688],{},[238,689,690],{},"manufacturer",[357,692],{},[342,694,695,700],{},[357,696,697],{},[238,698,699],{},"model",[357,701],{},[342,703,704,709],{},[357,705,706],{},[238,707,708],{},"platform",[357,710],{},[342,712,713,718],{},[357,714,715],{},[238,716,717],{},"primaryLocale",[357,719,720,487,723],{},[238,721,722],{},"primary_locale",[238,724,725],{},"locale",[342,727,728,733],{},[357,729,730],{},[238,731,732],{},"systemName",[357,734,735,487,738],{},[238,736,737],{},"system_name",[238,739,740],{},"os",[342,742,743,748],{},[357,744,745],{},[238,746,747],{},"timezone",[357,749,750],{},[238,751,752],{},"tz",[342,754,755,759],{},[357,756,757],{},[238,758,297],{},[357,760,761,487,764],{},[238,762,763],{},"device_type",[238,765,766],{},"deviceType",[342,768,769,774],{},[357,770,771],{},[238,772,773],{},"version",[357,775,776,487,779],{},[238,777,778],{},"os_version",[238,780,781],{},"osVersion",[194,783,784,785,487,787,789,790,793,794,796,797,789,799,802,803,806],{},"The browser SDK populates ",[238,786,708],{},[238,788,717],{}," (from ",[238,791,792],{},"navigator.language","), ",[238,795,747],{},", and ",[238,798,773],{},[238,800,801],{},"navigator.userAgent",") automatically when calling ",[238,804,805],{},"evaluate",". Server-side and non-browser SDKs may pass any subset; unknown keys silently fail to match.",[269,808,810],{"id":809},"rollouts-and-bucketing","Rollouts and Bucketing",[194,812,275,813,816,817,820,821,824,825,828],{},[209,814,815],{},"Rollout"," is a list of ",[238,818,819],{},"(variationKey, weight)"," pairs. Weights are unitless and normalized at evaluation time, so ",[238,822,823],{},"[(on, 9), (off, 1)]"," is a 90\u002F10 split, ",[238,826,827],{},"[(on, 1), (off, 1)]"," is 50\u002F50, and weights don't need to sum to 100.",[194,830,831],{},"Bucket assignment is deterministic per user:",[833,834,839],"pre",{"className":835,"code":837,"language":838},[836],"language-text","bucket = sha256(flagKey + \":\" + salt + \":\" + ruleId + \":\" + identifier) % totalWeight\n","text",[238,840,837],{"__ignoreMap":5},[194,842,843],{},"The hash inputs are deliberate:",[336,845,846,856],{},[339,847,848],{},[342,849,850,853],{},[345,851,852],{},"Input",[345,854,855],{},"Why it's there",[352,857,858,868,877,887],{},[342,859,860,865],{},[357,861,862],{},[238,863,864],{},"flagKey",[357,866,867],{},"Independent buckets across flags — being \"on\" for flag A doesn't correlate with flag B",[342,869,870,874],{},[357,871,872],{},[238,873,323],{},[357,875,876],{},"Per-flag random salt; regenerate to reshuffle every user",[342,878,879,884],{},[357,880,881],{},[238,882,883],{},"ruleId",[357,885,886],{},"Independent buckets across rules; each rule has its own bucket space",[342,888,889,894],{},[357,890,891],{},[238,892,893],{},"identifier",[357,895,896],{},"Authenticated principal id (if present), otherwise installation id; the only user input",[194,898,899,900,903],{},"What's intentionally ",[209,901,902],{},"not"," in the hash:",[203,905,906,916],{},[206,907,908,910,911,915],{},[209,909,559],{}," — two users who match the same rule's conditions are bucketed identically based on their identifier alone, not based on ",[912,913,914],"em",{},"why"," they qualified.",[206,917,918,921],{},[209,919,920],{},"Variation weights"," — increasing one variation's weight absorbs users from adjacent ranges rather than reshuffling everyone. This is what enables smooth gradual rollouts.",[643,923,925],{"id":924},"bucket-range-stability","Bucket-range stability",[194,927,928,929,932,933,936],{},"Variation weights are sorted alphabetically by key, then assigned contiguous bucket ranges. With sorted keys ",[238,930,931],{},"[off, on]"," and weights ",[238,934,935],{},"off=80, on=20",":",[203,938,939,948],{},[206,940,941,944,945],{},[238,942,943],{},"off"," gets bucket range ",[238,946,947],{},"0 – 0.80",[206,949,950,944,952],{},[238,951,486],{},[238,953,954],{},"0.80 – 1.00",[194,956,957,958,960],{},"Increase ",[238,959,486],{}," from 20 to 40:",[203,962,963,972],{},[206,964,965,967,968,971],{},[238,966,943],{}," gets ",[238,969,970],{},"0 – 0.667"," (80 \u002F (80+40))",[206,973,974,967,976],{},[238,975,486],{},[238,977,978],{},"0.667 – 1.00",[194,980,981,982,984,985,987,988,990,991,993,994,996,997,999,1000,1003],{},"A user whose hash falls at 0.5 was in ",[238,983,943],{}," before AND is still in ",[238,986,943],{}," after — no movement. A user at 0.72 was in ",[238,989,943],{}," (0.72 \u003C 0.80) and is now in ",[238,992,486],{}," (0.72 > 0.667) — moves on next eval. A user at 0.95 was in ",[238,995,486],{}," and stays in ",[238,998,486],{},". ",[209,1001,1002],{},"Only users in the newly-extended range move; everyone else keeps their bucket."," This is what makes gradual rollouts safe to widen.",[194,1005,1006],{},"Regenerating the per-flag salt is the explicit \"I want fresh assignments\" knob and reshuffles every user.",[198,1008,1010],{"id":1009},"conditions-and-bucketing-are-two-separate-steps","Conditions and Bucketing Are Two Separate Steps",[194,1012,1013],{},"A common confusion: conditions and bucketing both determine what variation a user gets, but they do completely different things.",[833,1015,1018],{"className":1016,"code":1017,"language":838},[836],"for each rule in rules:\n    if rule.conditions ALL match the user:        ← condition gate\n        bucket the user against rule.rollout      ← bucket assignment\n        return the resulting variation\nreturn defaultVariation\n",[238,1019,1017],{"__ignoreMap":5},[194,1021,1022,1025,1026,1029],{},[209,1023,1024],{},"Conditions are the gate."," They decide ",[912,1027,1028],{},"whether the rule applies to this user",". They're a hard yes\u002Fno filter — all conditions on a rule must match (AND), each can be negated, and any one failure skips the rule entirely. The user is never bucketed against a rule whose conditions they don't satisfy.",[194,1031,1032,1035],{},[209,1033,1034],{},"Bucketing is the within-rule assignment."," Once conditions pass, the deterministic hash above decides which of the rule's variations the user falls into. Conditions are not part of the hash, so two users who happen to qualify for the same rule are bucketed identically against that rule's space.",[194,1037,1038],{},"Practical implications:",[203,1040,1041,1052,1069],{},[206,1042,1043,1046,1047,1049,1050,508],{},[209,1044,1045],{},"A user moving in or out of a segment changes which rule they match"," but doesn't reshuffle their bucket within either rule. If they were in ",[238,1048,486],{}," under rule 1 and they leave the segment, rule 1 stops matching for them and they fall through to rule 2 — rule 2 will assign them an independent bucket based on its own ",[238,1051,883],{},[206,1053,1054,1061,1062,1064,1065,1068],{},[209,1055,1056,1057,1060],{},"The identifier is principal ",[912,1058,1059],{},"or"," installation, not both."," If an anonymous user logs in mid-session, their identifier flips from ",[238,1063,678],{}," to ",[238,1066,1067],{},"principalId.toString()"," and they're re-bucketed across every rule and every flag. This is a known property of identifier-based bucketing — there's no built-in consolidation between anonymous and authenticated identities.",[206,1070,1071,1074,1075,1077,1078,1080],{},[209,1072,1073],{},"Conditions are evaluated per call."," The server doesn't cache \"does this user match this rule's conditions\"; it walks the conditions on every ",[238,1076,805],{},". Add a user to a segment and the next ",[238,1079,805],{}," sees the new membership and re-routes them.",[198,1082,1084],{"id":1083},"live-distribution","Live Distribution",[194,1086,1087,1088,1090,1091,1094,1095,1098,1099,1101],{},"Every ",[238,1089,805],{}," call updates a per-",[238,1092,1093],{},"(flag, user)"," row in the ",[238,1096,1097],{},"flag_exposures"," table recording the variation that user is currently bucketed into. The flag detail page shows this as a ",[209,1100,1084],{}," panel with two side-by-side views:",[269,1103,1105],{"id":1104},"actual","Actual",[194,1107,1108],{},"Aggregated counts of users currently active in each variation, scoped to a date range picked from the same date-range picker as the analytics dashboards. The window is what makes the panel honest about dormant users:",[1110,1111,1112],"blockquote",{},[194,1113,1114,1115,1118],{},"A user who hasn't evaluated the flag since a rollout change isn't really in any bucket ",[912,1116,1117],{},"right now"," — they're a historical record. The window filter excludes them by construction, so the panel shows \"users actively being served each variant during this period\" instead of \"every user we've ever bucketed\".",[194,1120,1121],{},"After a rollout edit, the actual distribution converges to the new weights as users re-evaluate within the window.",[269,1123,1125],{"id":1124},"configured","Configured",[194,1127,1128,1129,1132],{},"Computed client-side from the flag's targeting rules and default-variation key. For each rule, normalized rollout weights as percentages; for the default path, a single 100% entry pointing at the default variation. This is the answer to ",[912,1130,1131],{},"\"if every user evaluated right now, what would the rollout configuration produce?\""," — independent of who's actually active.",[194,1134,1135,1136,1139,1140,1143],{},"Comparing the two surfaces drift between intent and reality. If the configured panel says rule 1 is ",[238,1137,1138],{},"40\u002F60"," but the actual panel shows ",[238,1141,1142],{},"50\u002F50",", traffic isn't matching the intent — maybe the rule isn't matching as many users as expected, or the default path is absorbing more than intended.",[269,1145,1147],{"id":1146},"lifecycle-hooks","Lifecycle hooks",[194,1149,1150],{},"The exposure table self-heals when flag state changes:",[203,1152,1153,1167,1181,1187],{},[206,1154,1155,1158,1159,1162,1163,1166],{},[209,1156,1157],{},"Variation value change"," (",[238,1160,1161],{},"on.value: true → false","): no effect on the exposure rows. They reference variation ",[912,1164,1165],{},"keys",", not values.",[206,1168,1169,1172,1173,1176,1177,1180],{},[209,1170,1171],{},"Variation key rename or removal",": the ",[238,1174,1175],{},"edit"," mutation prunes orphaned rows whose ",[238,1178,1179],{},"variation_key"," is no longer in the palette.",[206,1182,1183,1186],{},[209,1184,1185],{},"Salt regeneration"," (\"Reshuffle Buckets\"): wipes all exposure rows for the flag — the operator's explicit intent is fresh assignments, so leaving stale counts would be misleading. The table refills naturally as users re-evaluate.",[206,1188,1189,1192],{},[209,1190,1191],{},"Rule weight change",": no immediate write to exposures. Users move between variations gradually as they re-evaluate, with bucket-range stability preserving most existing assignments.",[198,1194,1196],{"id":1195},"experiments","Experiments",[194,1198,1199,1200,1203],{},"An ",[209,1201,1202],{},"Experiment"," is observation and statistics attached to a single targeting rule on a feature flag. It does not own its own values — those come from the rule's rollout. An experiment's only job is to:",[1205,1206,1207,1210,1213],"ol",{},[206,1208,1209],{},"Record assignments when users hit the attached rule.",[206,1211,1212],{},"Aggregate per-variation metrics from the analytics events table.",[206,1214,1215],{},"Optionally produce an AI summary of the result.",[269,1217,1219],{"id":1218},"experiment-status","Experiment Status",[336,1221,1222,1230],{},[339,1223,1224],{},[342,1225,1226,1228],{},[345,1227,413],{},[345,1229,416],{},[352,1231,1232,1241,1251,1261,1271],{},[342,1233,1234,1238],{},[357,1235,1236],{},[238,1237,425],{},[357,1239,1240],{},"Being configured; assignments and aggregation are not running",[342,1242,1243,1248],{},[357,1244,1245],{},[238,1246,1247],{},"RUNNING",[357,1249,1250],{},"Assignments are persisted on every evaluation that hits the attached rule",[342,1252,1253,1258],{},[357,1254,1255],{},[238,1256,1257],{},"PAUSED",[357,1259,1260],{},"Temporarily stopped; existing assignments remain but no new ones land",[342,1262,1263,1268],{},[357,1264,1265],{},[238,1266,1267],{},"COMPLETED",[357,1269,1270],{},"Finalized; results are frozen",[342,1272,1273,1277],{},[357,1274,1275],{},[238,1276,455],{},[357,1278,1279],{},"Hidden from the main UI",[194,1281,1282,1288,1289,1291,1292,1294,1295,1298,1299,508],{},[209,1283,1284,1285,1287],{},"An experiment must be ",[238,1286,1247],{}," for assignments to be persisted."," A common pitfall: leaving the experiment in ",[238,1290,425],{},", calling ",[238,1293,805],{},", seeing the right variation come back, and then wondering why the impressions count is zero. Click ",[209,1296,1297],{},"Start"," in the experiment detail page to enter ",[238,1300,1247],{},[269,1302,1304],{"id":1303},"conversion-goals","Conversion Goals",[194,1306,275,1307,1310],{},[209,1308,1309],{},"ConversionGoal"," defines what counts as a \"success\" for an experiment. Each goal has:",[203,1312,1313,1318,1343,1355,1361,1371],{},[206,1314,275,1315,1317],{},[209,1316,500],{}," for display.",[206,1319,504,1320,1323,1324,487,1327,487,1330,487,1333,487,1336,487,1339,1342],{},[209,1321,1322],{},"eventType"," filter (one of ",[238,1325,1326],{},"Session",[238,1328,1329],{},"Interaction",[238,1331,1332],{},"Impression",[238,1334,1335],{},"Completion",[238,1337,1338],{},"Installation",[238,1340,1341],{},"Error","). Null matches any event type — useful for \"did total activity per user grow?\" metrics.",[206,1344,504,1345,1348,1349,487,1352,292],{},[209,1346,1347],{},"elementType"," filter (e.g. ",[238,1350,1351],{},"button",[238,1353,1354],{},"click",[206,1356,504,1357,1360],{},[209,1358,1359],{},"elementId"," filter.",[206,1362,504,1363,1366,1367,1370],{},[209,1364,1365],{},"pagePath"," filter matched against ",[238,1368,1369],{},"page.path"," on the analytics events table.",[206,1372,275,1373,1376],{},[209,1374,1375],{},"metricType"," selecting how the goal is analyzed.",[643,1378,1380],{"id":1379},"metric-types","Metric Types",[336,1382,1383,1396],{},[339,1384,1385],{},[342,1386,1387,1390,1393],{},[345,1388,1389],{},"Metric",[345,1391,1392],{},"What it answers",[345,1394,1395],{},"Statistical test",[352,1397,1398,1410],{},[342,1399,1400,1404,1407],{},[357,1401,1402],{},[238,1403,240],{},[357,1405,1406],{},"\"Did the user do this at all?\"",[357,1408,1409],{},"Chi-squared",[342,1411,1412,1416,1419],{},[357,1413,1414],{},[238,1415,244],{},[357,1417,1418],{},"\"How many times per user did they do it?\"",[357,1420,1421],{},"Welch's t-test",[194,1423,1424,1426,1427,999,1431,1433],{},[238,1425,240],{}," counts each converting user at most once (regardless of how many matching events they emitted) and reports a proportion in ",[1428,1429,1430],"span",{},"0, 1",[238,1432,244],{}," totals matching events per user, reports a per-user mean and variance, and uses Welch's t-test to detect a difference of means. Picking the wrong metric type for the wrong question will quietly produce misleading results — chi-squared on a count metric isn't even mathematically valid because the proportion can exceed 1.",[269,1435,1437],{"id":1436},"aggregation","Aggregation",[194,1439,1440,1441,1444,1445,1448,1449,1452],{},"The ",[209,1442,1443],{},"Aggregate"," action enqueues a background job that recomputes the ",[238,1446,1447],{},"experiment_results"," table for the experiment. For each ",[238,1450,1451],{},"(variation, goal)"," combination, it:",[1205,1454,1455,1458,1465,1471,1476],{},[206,1456,1457],{},"Counts impressions from the assignments table.",[206,1459,1460,1461,1464],{},"Queries the analytics events table for matching events, joins each converting ",[238,1462,1463],{},"client_id"," against the assignments table to attribute it to a variation.",[206,1466,1467,1468,1470],{},"For ",[238,1469,240],{}," goals, computes conversion rate, chi-squared confidence, and percentage lift over the control variation.",[206,1472,1467,1473,1475],{},[238,1474,244],{}," goals, computes per-user mean, variance via Bessel-corrected sample variance, Welch's t-test confidence, and lift over the control mean.",[206,1477,1478,1479,1481,1482,508],{},"Upserts one row per ",[238,1480,1451],{}," into ",[238,1483,1447],{},[194,1485,1486],{},"The control variation is the alphabetically first variation key — the same convention bucket assignment uses, so the \"control\" is stable across runs.",[194,1488,1489,1490,1492],{},"The job is gated on ",[238,1491,1247],{}," and skips goals whose Trino query fails (e.g., a missing column in the warehouse) without overwriting prior good results with zeros.",[269,1494,1496],{"id":1495},"mutual-exclusion-layers","Mutual Exclusion Layers",[194,1498,1199,1499,1502],{},[209,1500,1501],{},"ExclusionLayer"," prevents a single user from being assigned to two conflicting experiments at the same time. When an experiment is attached to a layer, the assignment write checks whether the user already has an assignment in any other experiment in that layer; if so, the new assignment is rejected. Useful for preventing interaction effects between simultaneous tests on related surfaces.",[198,1504,1506],{"id":1505},"putting-it-together","Putting It Together",[194,1508,1509],{},"A typical end-to-end flow:",[1205,1511,1512,1518,1527,1538,1544,1552,1558],{},[206,1513,1514,1517],{},[209,1515,1516],{},"Create a flag."," Pick a type, define the variation palette, set the default variation. Leave the targeting rules empty for now.",[206,1519,1520,1523,1524,1526],{},[209,1521,1522],{},"Add a targeting rule."," Set its conditions (if any) and its rollout weights. Single-variation rollouts (\"everyone matching the rule gets ",[238,1525,486],{},"\") are fine for plain feature gating.",[206,1528,1529,1534,1535,1537],{},[209,1530,1531,1532,508],{},"Set status to ",[238,1533,435],{}," Clients calling ",[238,1536,805],{}," will now bucket users against the rule.",[206,1539,1540,1543],{},[209,1541,1542],{},"Watch the Live Distribution panel"," to confirm the actual split is matching the configured intent.",[206,1545,1546,1549,1550,508],{},[209,1547,1548],{},"(Optional) Attach an experiment"," to the rule for A\u002FB observation. Add conversion goals scoped to the events you want to count. Set status to ",[238,1551,1247],{},[206,1553,1554,1557],{},[209,1555,1556],{},"Click Aggregate"," after enough time has passed for events to land. The Results section shows per-variation impressions, conversions\u002Fmean, confidence, and lift.",[206,1559,1560,1563],{},[209,1561,1562],{},"Click Analyze"," for an AI summary of the result and a recommendation.",[198,1565,1567],{"id":1566},"common-pitfalls","Common Pitfalls",[203,1569,1570,1591,1609,1615,1621,1630],{},[206,1571,1572,1580,1581,1583,1584,1586,1587,1590],{},[209,1573,1574,1575,1577,1578,508],{},"Experiment is in ",[238,1576,425],{}," or ",[238,1579,1257],{}," Assignments are only persisted while the experiment is ",[238,1582,1247],{},". The ",[238,1585,805],{}," response will still return the correct variation, but no row lands in ",[238,1588,1589],{},"assignments"," and aggregation reports zero impressions.",[206,1592,1593,1596,1597,1600,1601,1604,1605,1608],{},[209,1594,1595],{},"Goal filter is too tight."," A goal with ",[238,1598,1599],{},"elementType: button"," won't match events the SDK emits with ",[238,1602,1603],{},"element.type: click",". The simplest sanity-check goal has ",[238,1606,1607],{},"eventType: Interaction"," and no other filters.",[206,1610,1611,1614],{},[209,1612,1613],{},"Targeting rule has a Segment condition that the test devices aren't in."," The rule won't match, evaluation falls through to the default path, and no assignments land against the experiment. For testing, use a rule with no conditions or add the test devices to the segment.",[206,1616,1617,1620],{},[209,1618,1619],{},"Anonymous user becomes authenticated mid-session."," Their identifier flips from installation id to principal id, and they're re-bucketed across every flag and rule. The exposure row is updated in place.",[206,1622,1623,1626,1627,1629],{},[209,1624,1625],{},"Variation key was renamed without realising."," Old assignments and exposures still reference the old key for users who haven't re-evaluated. The exposure table prunes orphans on ",[238,1628,1175],{},", but the experiment_results table doesn't — re-aggregate to refresh.",[206,1631,1632,1635],{},[209,1633,1634],{},"Salt was regenerated mid-experiment."," Every user is reshuffled. The exposure table is wiped (intentional). Existing assignments are kept (they don't cascade-clear), but new evaluations land against new buckets, which can muddy in-flight experiments.",[198,1637,1639],{"id":1638},"for-developers","For developers",[194,1641,1642],{},"Related modules:",[203,1644,1645,1651,1657,1663,1669,1675],{},[206,1646,1647,1648],{},"Core interfaces: ",[238,1649,1650],{},"backend\u002Fframework\u002Fcore-experimentation",[206,1652,1653,1654],{},"Implementation: ",[238,1655,1656],{},"backend\u002Fframework\u002Fexperimentation",[206,1658,1659,1660],{},"GraphQL schema: ",[238,1661,1662],{},"backend\u002Fframework\u002Fexperimentation\u002Fsrc\u002Fmain\u002Fresources\u002Fgraphql\u002Fexperimentation.graphqls",[206,1664,1665,1666],{},"Migrations: ",[238,1667,1668],{},"backend\u002Fframework\u002Fexperimentation\u002Fsrc\u002Fmain\u002Fresources\u002Fdb\u002Fmigrations\u002FV1__experimentation.sql",[206,1670,1671,1672],{},"Aggregation job: ",[238,1673,1674],{},"backend\u002Fframework\u002Fexperimentation\u002Fsrc\u002Fmain\u002Fkotlin\u002Fbosca\u002Fexperimentation\u002Fjobs\u002FExperimentResultAggregation.kt",[206,1676,1677,1678,1681,1682],{},"Bucket assignment: ",[238,1679,1680],{},"ExperimentServiceImpl.bucketRollout"," in ",[238,1683,1684],{},"backend\u002Fframework\u002Fexperimentation\u002Fsrc\u002Fmain\u002Fkotlin\u002Fbosca\u002Fexperimentation\u002Fservice\u002FExperimentServiceImpl.kt",[194,1686,1687],{},"Related docs:",[203,1689,1690,1699,1704,1714],{},[206,1691,1692,1693,1695,1696],{},"Segments referenced by ",[238,1694,583],{}," conditions: ",[1697,1698,173],"a",{"href":174},[206,1700,1701,1702],{},"Analytics events the conversion goals match against: ",[1697,1703,109],{"href":110},[206,1705,1706,1707,1709,1710,1695,1712],{},"Typed ",[238,1708,626],{}," class used by ",[238,1711,620],{},[1697,1713,177],{"href":178},[206,1715,1716,1717,1719,1720],{},"Profiles backing ",[238,1718,608],{}," conditions and segment membership: ",[1697,1721,70],{"href":71},{"title":5,"searchDepth":1723,"depth":1723,"links":1724},2,[1725,1726,1736,1737,1742,1748,1749,1750],{"id":200,"depth":1723,"text":201},{"id":266,"depth":1723,"text":267,"children":1727},[1728,1730,1731,1732,1733,1734,1735],{"id":271,"depth":1729,"text":272},3,{"id":333,"depth":1729,"text":334},{"id":403,"depth":1729,"text":404},{"id":469,"depth":1729,"text":470},{"id":517,"depth":1729,"text":518},{"id":546,"depth":1729,"text":559},{"id":809,"depth":1729,"text":810},{"id":1009,"depth":1723,"text":1010},{"id":1083,"depth":1723,"text":1084,"children":1738},[1739,1740,1741],{"id":1104,"depth":1729,"text":1105},{"id":1124,"depth":1729,"text":1125},{"id":1146,"depth":1729,"text":1147},{"id":1195,"depth":1723,"text":1196,"children":1743},[1744,1745,1746,1747],{"id":1218,"depth":1729,"text":1219},{"id":1303,"depth":1729,"text":1304},{"id":1436,"depth":1729,"text":1437},{"id":1495,"depth":1729,"text":1496},{"id":1505,"depth":1723,"text":1506},{"id":1566,"depth":1723,"text":1567},{"id":1638,"depth":1723,"text":1639},"Roll out features safely with targeted feature flags, A\u002FB test changes with conversion goals, and observe live distribution and statistical lift.","md",{},true,{"title":181,"description":1751},"2CfjFv4CpVRY9_860KSCSRcfBlEh10mE_bbutXscuBI",[1758,1760],{"title":177,"path":178,"stem":179,"description":1759,"children":-1},"Register client devices and deliver push notifications across iOS, Android, web, and desktop platforms.",{"title":185,"path":186,"stem":187,"description":1761,"children":-1},"Deliver personalized content recommendations powered by analytics, audience segments, and machine learning.",1778032440968]