FAILURE MAP
← Case archive

FA-73841 / Feature flag rollout bucketing / Open access

Percentage rollout gate: User and flag swap places in the hash key · case 01

Users land in different buckets than every other SDK computes for the same flag.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The hashed string is built as salt.user.flag instead of salt.flag.user.

THE FAILURE

The hashed string is built as salt.user.flag instead of salt.flag.user.

Unsuccessful approach: Dropping the separators keeps the flag before the user but still hashes a different string.

Case contract

bucket = int(first 8 hex digits of sha1(salt + "." + flag + "." + user), 16) % 10000; the rollout percentage pct has at most two decimals and becomes a basis-point limit round(pct * 100); the user is enabled iff bucket < limit. Return [bucket, enabled].

Why this case matters

Percentage gates must be reproducible across services so the same user sees the same state everywhere.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import hashlib
N = 1
observations = []
def solve(flag, user, salt, pct):
    limit = round(pct * 100)
    digest = hashlib.sha1((salt + '.' + user + '.' + flag).encode()).hexdigest()
    bucket = int(digest[:8], 16) % 10000
    return [bucket, bucket < limit]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('bucket exactly at limit is excluded (alice)', ['new-checkout', 'alice', 'prod', 66.65], [6665, False]),
  ('bucket one below limit is included (alice)', ['new-checkout', 'alice', 'prod', 66.66], [6665, True]),
  ('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('half rollout control', ['search-v2', 'grace', 'ramp-7', 50], [3372, True]),
  ('hashed user sample 1', ['search-v2', 'u55140', 's1', 100], [5052, True]),
  ('hashed user sample 2', ['new-checkout', 'u28082', 's1', 50], [928, True])],
 [('bucket one below limit is included (alice)', ['new-checkout', 'alice', 'prod', 66.66], [6665, True]),
  ('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('half rollout control', ['search-v2', 'grace', 'ramp-7', 50], [3372, True]),
  ('hashed user sample 6', ['new-checkout', 'u58886', 'ramp-7', 50.31], [3873, True])],
 [('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('hashed user sample 4', ['fast-pay', 'u43705', 'ramp-7', 32.8], [187, True]),
  ('hashed user sample 11', ['search-v2', 'u91686', 'prod', 100], [5233, True]),
  ('hashed user sample 12', ['search-v2', 'u3012', 'prod', 62.02], [6201, True])],
 [('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('hashed user sample 11', ['search-v2', 'u91686', 'prod', 100], [5233, True]),
  ('hashed user sample 16', ['new-checkout', 'u25854', 'prod', 0.57], [5842, False]),
  ('hashed user sample 17', ['search-v2', 'u90753', 'prod', 0], [9423, False])],
 [('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('full rollout enables everyone', ['dark-mode', 'erin', 's1', 100], [9606, True]),
  ('hashed user sample 18', ['dark-mode', 'u52498', 'prod', 39.33], [2698, True]),
  ('hashed user sample 21', ['new-checkout', 'u7954', 'prod', 59.72], [196, True]),
  ('hashed user sample 22', ['new-checkout', 'u52965', 'ramp-7', 70.89], [7088, True])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
bucket exactly at limit is excluded (alice)[2177, True][6665, False]Failed
bucket one below limit is included (alice)[2177, True][6665, True]Failed
bucket exactly at limit is excluded (bob)[1447, True][2755, False]Failed
bucket one below limit is included (bob)[1447, True][2755, True]Failed
bucket exactly at limit is excluded (carol)[7258, False][5745, False]Failed
half rollout control[3565, True][3372, True]Failed
hashed user sample 1[5720, True][5052, True]Failed
hashed user sample 2[7578, False][928, True]Failed

SHA-256 / d78f7485b7a1094ded598d47996ba5eabc8e6182f6eb7a1d51dcc17cf721f58b

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import hashlib
N = 1
observations = []
def solve(flag, user, salt, pct):
    limit = round(pct * 100)
    digest = hashlib.sha1((salt + flag + user).encode()).hexdigest()
    bucket = int(digest[:8], 16) % 10000
    return [bucket, bucket < limit]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('bucket exactly at limit is excluded (alice)', ['new-checkout', 'alice', 'prod', 66.65], [6665, False]),
  ('bucket one below limit is included (alice)', ['new-checkout', 'alice', 'prod', 66.66], [6665, True]),
  ('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('half rollout control', ['search-v2', 'grace', 'ramp-7', 50], [3372, True]),
  ('hashed user sample 1', ['search-v2', 'u55140', 's1', 100], [5052, True]),
  ('hashed user sample 2', ['new-checkout', 'u28082', 's1', 50], [928, True])],
 [('bucket one below limit is included (alice)', ['new-checkout', 'alice', 'prod', 66.66], [6665, True]),
  ('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('half rollout control', ['search-v2', 'grace', 'ramp-7', 50], [3372, True]),
  ('hashed user sample 6', ['new-checkout', 'u58886', 'ramp-7', 50.31], [3873, True])],
 [('bucket exactly at limit is excluded (bob)', ['dark-mode', 'bob', 's1', 27.55], [2755, False]),
  ('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('hashed user sample 4', ['fast-pay', 'u43705', 'ramp-7', 32.8], [187, True]),
  ('hashed user sample 11', ['search-v2', 'u91686', 'prod', 100], [5233, True]),
  ('hashed user sample 12', ['search-v2', 'u3012', 'prod', 62.02], [6201, True])],
 [('bucket one below limit is included (bob)', ['dark-mode', 'bob', 's1', 27.56], [2755, True]),
  ('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('hashed user sample 11', ['search-v2', 'u91686', 'prod', 100], [5233, True]),
  ('hashed user sample 16', ['new-checkout', 'u25854', 'prod', 0.57], [5842, False]),
  ('hashed user sample 17', ['search-v2', 'u90753', 'prod', 0], [9423, False])],
 [('bucket exactly at limit is excluded (carol)', ['search-v2', 'carol', 'prod', 57.45], [5745, False]),
  ('bucket one below limit is included (carol)', ['search-v2', 'carol', 'prod', 57.46], [5745, True]),
  ('float truncation regression at 65.82 percent', ['fast-pay', 'member13', 'prod', 65.82], [6581, True]),
  ('zero percent disables everyone', ['dark-mode', 'dave', 's1', 0], [4167, False]),
  ('full rollout enables everyone', ['dark-mode', 'erin', 's1', 100], [9606, True]),
  ('hashed user sample 18', ['dark-mode', 'u52498', 'prod', 39.33], [2698, True]),
  ('hashed user sample 21', ['new-checkout', 'u7954', 'prod', 59.72], [196, True]),
  ('hashed user sample 22', ['new-checkout', 'u52965', 'ramp-7', 70.89], [7088, True])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
bucket exactly at limit is excluded (alice)[564, True][6665, False]Failed
bucket one below limit is included (alice)[564, True][6665, True]Failed
bucket exactly at limit is excluded (bob)[5311, False][2755, False]Failed
bucket one below limit is included (bob)[5311, False][2755, True]Failed
bucket exactly at limit is excluded (carol)[9588, False][5745, False]Failed
half rollout control[3776, True][3372, True]Failed
hashed user sample 1[15, True][5052, True]Failed
hashed user sample 2[3822, True][928, True]Failed

SHA-256 / 1a045c3b6dd24695b6ccd8ccc1c39aedd01c9484253725c1898dd9ca115e8532

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

A deterministic toy flag-evaluation model with a stipulated contract; it does not reproduce any vendor SDK byte for byte. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:48:51.329641+00:00.

Case digest / b03bb0d0e04a509c3f8738ffd923ccd9c329432a183705c91a306f41405cde54