Documentation
¶
Overview ¶
Package flag is feature flags for smolanalytics — boolean and multivariate, with property targeting and percentage rollouts, evaluated deterministically so the same user always lands in the same bucket. What makes it deeper than a plain flag console (a later increment): a flag flip is auto-recorded as a deploy marker, so the existing deploy-impact engine answers "did flag X move activation?" from your editor, provably. This file is the pure engine — types + evaluation — with no I/O, so it's trivially testable and shared verbatim with any SDK that copies the bucketing.
Index ¶
Constants ¶
const ( ExposureEvent = "$feature_flag_called" PropFlag = "$feature_flag" PropVariant = "$feature_flag_response" )
Exposure/response property keys, mirroring PostHog's $feature_flag_called convention. The "$" prefix means the tracking-plan drift gate treats these as system events, not unplanned ones.
const SRMAlpha = 0.001
Sample Ratio Mismatch: the users actually exposed to each arm do not match the split that was configured. A 50/50 experiment that delivered 5,200 / 4,800 did not randomize the way it said it did, and once that is true nothing downstream can be trusted — the arms differ by whatever mechanism broke the split, not by the change being tested.
This is the single most valuable check an experiment tool can run, because without it a broken experiment is indistinguishable from a successful one. It reports a confident number either way. Every serious platform runs it; the difference between them and a homegrown A/B feature is usually this test and nothing else.
The threshold is deliberately far stricter than the usual 0.05. Experiments are checked constantly and by many people, so a 1-in-20 false-alarm rate would cry wolf until the warning is ignored — which is worse than not having it. GrowthBook fires at 0.001 and Microsoft at 0.0005; 0.001 matches the former.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Flag ¶
type Flag struct {
Key string `json:"key"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled"`
Variants []Variant `json:"variants,omitempty"`
Rules []Rule `json:"rules,omitempty"`
Measured bool `json:"measured,omitempty"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
Flag is a saved feature flag. Variants empty = a boolean flag (served variant is "on"). Rules empty = on for everyone when Enabled. Measured opts this flag into exposure logging (a later increment) so it can be A/B-analysed without every flag inflating the event count.
func (Flag) Evaluate ¶
Evaluate resolves the flag for one user, given their context properties. Returns the served variant ("on" for a boolean flag, "" when off) and whether the flag is on. Deterministic: the same key + distinct_id always yields the same result, computed only from a stable hash — no randomness, no state — so a client SDK that copies this bucketing agrees byte-for-byte.
type Report ¶ added in v0.9.7
type Report struct {
Flag string `json:"flag"`
Goal string `json:"goal"`
Days int `json:"days"`
Control string `json:"control"`
Variants []VariantResult `json:"variants"`
Note string `json:"note"`
}
Report is the A/B read for one measured flag: for each variant, how many exposed users converted on the goal event AFTER their first exposure, and whether the lift over the control arm is statistically significant. Pure + deterministic (same events → same report), so it is pinnable MCP==API by an agreement test, the same contract as every other report.
func Measure ¶ added in v0.9.7
Measure computes the report from raw events. An exposure is a $feature_flag_called event tagging the user's variant for this flag; a conversion is the user doing `goal` at or after their first exposure (so we never credit behavior that predates the experiment). Only events within the last `days` (0 = all) are considered.
type Rule ¶
type Rule struct {
Filters []query.Filter `json:"filters,omitempty"`
RolloutPct int `json:"rollout_pct"`
}
Rule is one ordered targeting clause: the user's context must pass all Filters (empty = every user), and RolloutPct (0..100) is the deterministic share of matched users served. Rules are evaluated in order, first match wins.
type SRMResult ¶ added in v0.23.0
type SRMResult struct {
Checked bool `json:"checked"`
Detected bool `json:"detected"`
PValue float64 `json:"p_value"`
ChiSquare float64 `json:"chi_square"`
Observed map[string]int `json:"observed"`
Expected map[string]float64 `json:"expected"`
Total int `json:"total"`
// Culprit names the segment whose split is most skewed, when one stands out. This is the
// difference between "your experiment is broken" and "your experiment is broken, and it is
// iOS users: 340 in control against 91 in test".
Culprit string `json:"culprit,omitempty"`
Verdict string `json:"verdict"`
}
SRMResult is the health check for one experiment's traffic split.
func CheckSRM ¶ added in v0.23.0
CheckSRM compares how many users were actually exposed to each arm against the flag's configured weights.
Only arms the flag actually declares are counted. An exposure tagged with a variant that no longer exists is itself a problem, but it is a different one — folding it into the chi-square would blame the split for a stale SDK.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists flags to a JSON file (atomic tmp+rename), same discipline as the cohort and deploy stores. Flags are keyed by their stable Key (e.g. "checkout_v2"), so Save is an upsert: creating or updating the flag with that key. Empty path = in-memory only.
func (*Store) Delete ¶
Delete removes a flag by key. found is true only when a flag actually went away, so callers can report "nothing was deleted" instead of implying a removal that never happened. Deleting a key that isn't there is not an error (retries are fine).
type Variant ¶
Variant is one arm of a multivariate flag; Weight is its relative share (need not sum to 100).
type VariantResult ¶ added in v0.9.7
type VariantResult struct {
Key string `json:"key"`
Exposed int `json:"exposed"`
Converted int `json:"converted"`
RatePct float64 `json:"rate_pct"`
DeltaPct float64 `json:"delta_pct"` // conversion-rate lift vs the control arm (0 for control)
Significant bool `json:"significant"` // 95% two-proportion z-test vs control
SmallSample bool `json:"small_sample"` // too few exposed to trust the rate
}
VariantResult is one arm of a measured flag's A/B read.