FA-74001 / Feature flag rollout bucketing / Open access
Scheduled rollout ramp: Hold steps reset the rollout to zero · case 01
A schedule pause entry turns the feature off instead of holding the current percentage.
ROOT CAUSE
A None percent is coerced with "pct or 0" instead of being skipped.
VERIFIED REPAIR
Leave the current percentage unchanged when a step carries None.
Unsuccessful approach: Testing truthiness skips holds but also ignores an explicit rollback to 0 percent.
Case contract
steps is a possibly unsorted list of [epoch second, percent or None]. Order steps by time, breaking ties by list position (later entries apply later). Every step with time <= now is active in that order; a None percent is a hold that keeps the previous value; numeric percents are clamped to [0, 100]. Before the first step the percentage is 0. Return [percent, original list index of the last active step or -1].
Why this case matters
Scheduled ramps move production traffic unattended, so the percentage at any instant must be exact.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(steps, now):
order = sorted(range(len(steps)), key=lambda i: (steps[i][0], i))
current = 0
active = -1
for i in order:
at, pct = steps[i]
if at > now:
break
active = i
current = min(max(pct or 0, 0), 100)
return [current, active]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('step scheduled exactly now is active', [[[0, 5], [60, 25]], 60], [25, 1]),
('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 2', [[[20, 0], [30, 50]], 10], [0, -1]),
('ramp schedule sample 3', [[[30, 0], [0, None], [60, 100]], 25], [0, 1])],
[('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('ramp schedule sample 6', [[[60, 0], [30, 50], [60, 75]], 60], [75, 2]),
('ramp schedule sample 30', [[[20, 120], [10, 120], [0, 50], [20, None]], 20], [100, 3]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2])],
[('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('ramp schedule sample 11', [[[0, 5]], -5], [0, -1]),
('ramp schedule sample 43', [[[20, 5], [20, 0]], 20], [0, 1]),
('ramp schedule sample 58', [[[30, 25], [30, 120], [10, 120], [30, None]], 30], [100, 3])],
[('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 16', [[[60, 120]], 60], [100, 0]),
('ramp schedule sample 17', [[[45, 5], [20, 120], [20, 50], [10, 100], [45, 75]], 20], [50, 2])],
[('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('active index refers to original position', [[[20, 30], [10, 10]], 15], [10, 1]),
('ramp schedule sample 21', [[], 0], [0, -1]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2]),
('ramp schedule sample 46', [[[30, 100], [20, 25], [30, -10], [30, 50], [30, None]], 30], [50, 4])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| step scheduled exactly now is active | [25, 1] | [25, 1] | Passed |
| unsorted schedule is ordered by time | [25, 2] | [25, 2] | Passed |
| later entry wins a timestamp tie | [20, 2] | [20, 2] | Passed |
| hold step keeps previous percentage | [0, 1] | [25, 1] | Failed |
| explicit rollback to zero percent | [0, 1] | [0, 1] | Passed |
| ramp schedule sample 1 | [0, 5] | [25, 5] | Failed |
| ramp schedule sample 2 | [0, -1] | [0, -1] | Passed |
| ramp schedule sample 3 | [0, 1] | [0, 1] | Passed |
SHA-256 / 30a569f74403ffb5d8b811a695e066d46479af39814ce8447f5325ed46cef18c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(steps, now):
order = sorted(range(len(steps)), key=lambda i: (steps[i][0], i))
current = 0
active = -1
for i in order:
at, pct = steps[i]
if at > now:
break
active = i
if pct:
current = min(max(pct, 0), 100)
return [current, active]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('step scheduled exactly now is active', [[[0, 5], [60, 25]], 60], [25, 1]),
('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 2', [[[20, 0], [30, 50]], 10], [0, -1]),
('ramp schedule sample 3', [[[30, 0], [0, None], [60, 100]], 25], [0, 1])],
[('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('ramp schedule sample 6', [[[60, 0], [30, 50], [60, 75]], 60], [75, 2]),
('ramp schedule sample 30', [[[20, 120], [10, 120], [0, 50], [20, None]], 20], [100, 3]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2])],
[('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('ramp schedule sample 11', [[[0, 5]], -5], [0, -1]),
('ramp schedule sample 43', [[[20, 5], [20, 0]], 20], [0, 1]),
('ramp schedule sample 58', [[[30, 25], [30, 120], [10, 120], [30, None]], 30], [100, 3])],
[('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 16', [[[60, 120]], 60], [100, 0]),
('ramp schedule sample 17', [[[45, 5], [20, 120], [20, 50], [10, 100], [45, 75]], 20], [50, 2])],
[('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('active index refers to original position', [[[20, 30], [10, 10]], 15], [10, 1]),
('ramp schedule sample 21', [[], 0], [0, -1]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2]),
('ramp schedule sample 46', [[[30, 100], [20, 25], [30, -10], [30, 50], [30, None]], 30], [50, 4])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| step scheduled exactly now is active | [25, 1] | [25, 1] | Passed |
| unsorted schedule is ordered by time | [25, 2] | [25, 2] | Passed |
| later entry wins a timestamp tie | [20, 2] | [20, 2] | Passed |
| hold step keeps previous percentage | [25, 1] | [25, 1] | Passed |
| explicit rollback to zero percent | [25, 1] | [0, 1] | Failed |
| ramp schedule sample 1 | [25, 5] | [25, 5] | Passed |
| ramp schedule sample 2 | [0, -1] | [0, -1] | Passed |
| ramp schedule sample 3 | [0, 1] | [0, 1] | Passed |
SHA-256 / 7292e23d4ddae82bb93db9acf1df12e686b1e1f2bedb8312fc5f74f449ec8a0a
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(steps, now):
order = sorted(range(len(steps)), key=lambda i: (steps[i][0], i))
current = 0
active = -1
for i in order:
at, pct = steps[i]
if at > now:
break
active = i
if pct is not None:
current = min(max(pct, 0), 100)
return [current, active]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('step scheduled exactly now is active', [[[0, 5], [60, 25]], 60], [25, 1]),
('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 2', [[[20, 0], [30, 50]], 10], [0, -1]),
('ramp schedule sample 3', [[[30, 0], [0, None], [60, 100]], 25], [0, 1])],
[('unsorted schedule is ordered by time', [[[60, 50], [0, 10], [30, 25]], 45], [25, 2]),
('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('ramp schedule sample 6', [[[60, 0], [30, 50], [60, 75]], 60], [75, 2]),
('ramp schedule sample 30', [[[20, 120], [10, 120], [0, 50], [20, None]], 20], [100, 3]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2])],
[('later entry wins a timestamp tie', [[[0, 5], [30, 50], [30, 20]], 40], [20, 2]),
('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('ramp schedule sample 11', [[[0, 5]], -5], [0, -1]),
('ramp schedule sample 43', [[[20, 5], [20, 0]], 20], [0, 1]),
('ramp schedule sample 58', [[[30, 25], [30, 120], [10, 120], [30, None]], 30], [100, 3])],
[('hold step keeps previous percentage', [[[0, 25], [30, None]], 40], [25, 1]),
('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('ramp schedule sample 1', [[[45, 0], [45, 5], [20, None], [45, 25], [60, -10], [45, None]], 45], [25, 5]),
('ramp schedule sample 16', [[[60, 120]], 60], [100, 0]),
('ramp schedule sample 17', [[[45, 5], [20, 120], [20, 50], [10, 100], [45, 75]], 20], [50, 2])],
[('explicit rollback to zero percent', [[[0, 25], [30, 0]], 40], [0, 1]),
('over-hundred percent is clamped', [[[0, 150]], 10], [100, 0]),
('negative percent is clamped to zero', [[[0, 10], [5, -20]], 10], [0, 1]),
('before first step is zero', [[[10, 50]], 5], [0, -1]),
('active index refers to original position', [[[20, 30], [10, 10]], 15], [10, 1]),
('ramp schedule sample 21', [[], 0], [0, -1]),
('ramp schedule sample 40', [[[60, 0], [20, 25], [60, None]], 60], [0, 2]),
('ramp schedule sample 46', [[[30, 100], [20, 25], [30, -10], [30, 50], [30, None]], 30], [50, 4])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| step scheduled exactly now is active | [25, 1] | [25, 1] | Passed |
| unsorted schedule is ordered by time | [25, 2] | [25, 2] | Passed |
| later entry wins a timestamp tie | [20, 2] | [20, 2] | Passed |
| hold step keeps previous percentage | [25, 1] | [25, 1] | Passed |
| explicit rollback to zero percent | [0, 1] | [0, 1] | Passed |
| ramp schedule sample 1 | [25, 5] | [25, 5] | Passed |
| ramp schedule sample 2 | [0, -1] | [0, -1] | Passed |
| ramp schedule sample 3 | [0, 1] | [0, 1] | Passed |
SHA-256 / 11c6cff9aa5bec1383a061c5dde644d748eaf9c63e816ccaddd7d55daccd6fc8
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:52.625626+00:00.
Case digest / e5d0b6b297230cfaf309d5ea2a84a40d88461381b19bf43d48b0d09d0ec0e931