FA-73816 / Rate limiter algorithms / Open access
Combined per-second and per-minute fixed windows: minute limit reported first · case 01
Clients exhausted on both limits are told to wait for the minute reset instead of the next second.
ROOT CAUSE
The minute check runs before the second check.
THE FAILURE
The minute check runs before the second check.
Unsuccessful approach: Choosing the reason by comparing counters is not the contract precedence.
Case contract
Input {per_second, per_minute, requests [t_ms]} with nondecreasing t. Second windows are t div 1000 and minute windows t div 60000, each resetting its own counter only when its own window changes. The second limit is checked first ("deny-second"), then the minute limit ("deny-minute"); only admitted requests are counted, in both windows. Return [decisions, second count, minute count].
Why this case matters
Public APIs commonly publish several simultaneous limits; coupling their resets or charging denied requests to one of them breaks the published contract.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
ls, lm = x['per_second'], x['per_minute']
sw = mw = None
sc = mc = 0
out = []
for t in x['requests']:
s, m = t // 1000, t // 60000
if s != sw:
sw, sc = s, 0
if m != mw:
mw, mc = m, 0
if mc >= lm:
out.append('deny-minute')
elif sc >= ls:
out.append('deny-second')
else:
sc += 1
mc += 1
out.append('allow')
return [out, sc, mc]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3001, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1001, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60001, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1001, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2003, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [1062,
8418,
9273,
34515,
47777,
65888,
67598,
89351,
103730,
110167,
110678,
120637,
122553,
128547]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow'],
1,
3]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3002, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1002, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60002, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1002, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2004, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [4406,
11048,
15610,
37272,
37553,
45326,
49676,
64905,
66877,
80916,
104479,
105515,
113188,
115762]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute'],
0,
6]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3003, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1003, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60003, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1003, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2005, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [12496,
13001,
16785,
17435,
41410,
44765,
45872,
51289,
69195,
73570,
101381,
106192,
110461,
126847]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow'],
1,
1]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3004, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1004, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60004, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1004, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2006, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [35872,
47171,
57795,
66404,
69658,
83185,
86836,
87855,
88307,
96707,
97802,
102613,
119928,
129412]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'deny-minute',
'allow'],
1,
1]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3005, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1005, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60005, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1005, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2007, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [2584,
8339,
26804,
29473,
39448,
51111,
57796,
65951,
70268,
73982,
76067,
80339,
81264,
102821]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute'],
0,
6]]]]
for label, args, expected in cases[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 |
|---|---|---|---|
| minute budget spans seconds | [['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 5] | [['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 5] | Passed |
| second limit inside minute | [['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2] | [['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2] | Passed |
| minute rollover | [['allow', 'allow', 'allow', 'deny-minute', 'allow', 'allow'], 2, 2] | [['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2] | Failed |
| both exhausted reports second | [['allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 1] | [['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1] | Failed |
| minute denials keep second budget | [['allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute', 'allow', 'allow', 'deny-minute'], 2, 2] | [['allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute', 'allow', 'allow', 'deny-second'], 2, 2] | Failed |
| random traffic | [['allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow'], 1, 3] | [['allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow'], 1, 3] | Passed |
SHA-256 / 8a6bf212ddef3be681e70d77c09e22b324d6a57ada3f6a6cfcc0e290b749f48c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
ls, lm = x['per_second'], x['per_minute']
sw = mw = None
sc = mc = 0
out = []
for t in x['requests']:
s, m = t // 1000, t // 60000
if s != sw:
sw, sc = s, 0
if m != mw:
mw, mc = m, 0
if sc >= ls or mc >= lm:
out.append('deny-second' if sc > mc else 'deny-minute')
else:
sc += 1
mc += 1
out.append('allow')
return [out, sc, mc]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3001, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1001, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60001, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1001, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2003, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [1062,
8418,
9273,
34515,
47777,
65888,
67598,
89351,
103730,
110167,
110678,
120637,
122553,
128547]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow'],
1,
3]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3002, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1002, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60002, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1002, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2004, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [4406,
11048,
15610,
37272,
37553,
45326,
49676,
64905,
66877,
80916,
104479,
105515,
113188,
115762]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute'],
0,
6]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3003, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1003, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60003, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1003, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2005, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [12496,
13001,
16785,
17435,
41410,
44765,
45872,
51289,
69195,
73570,
101381,
106192,
110461,
126847]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow'],
1,
1]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3004, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1004, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60004, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1004, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2006, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [35872,
47171,
57795,
66404,
69658,
83185,
86836,
87855,
88307,
96707,
97802,
102613,
119928,
129412]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'deny-minute',
'allow'],
1,
1]]],
[['minute budget spans seconds',
{'per_minute': 5, 'per_second': 2, 'requests': [0, 1, 1000, 1001, 2000, 2001, 3005, 59999]},
[['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'],
0,
5]],
['second limit inside minute',
{'per_minute': 10, 'per_second': 1, 'requests': [0, 10, 20, 1005, 1010]},
[['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2]],
['minute rollover',
{'per_minute': 3, 'per_second': 3, 'requests': [59000, 59001, 59002, 59500, 60005, 60001]},
[['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2]],
['both exhausted reports second',
{'per_minute': 1, 'per_second': 1, 'requests': [0, 1, 1005, 1001]},
[['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1]],
['minute denials keep second budget',
{'per_minute': 2, 'per_second': 2, 'requests': [0, 1000, 2000, 2001, 2007, 60000, 60001, 60002]},
[['allow',
'allow',
'deny-minute',
'deny-minute',
'deny-minute',
'allow',
'allow',
'deny-second'],
2,
2]],
['random traffic',
{'per_minute': 6,
'per_second': 2,
'requests': [2584,
8339,
26804,
29473,
39448,
51111,
57796,
65951,
70268,
73982,
76067,
80339,
81264,
102821]},
[['allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute',
'allow',
'allow',
'allow',
'allow',
'allow',
'allow',
'deny-minute'],
0,
6]]]]
for label, args, expected in cases[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 |
|---|---|---|---|
| minute budget spans seconds | [['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 5] | [['allow', 'allow', 'allow', 'allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 5] | Passed |
| second limit inside minute | [['allow', 'deny-minute', 'deny-minute', 'allow', 'deny-minute'], 1, 2] | [['allow', 'deny-second', 'deny-second', 'allow', 'deny-second'], 1, 2] | Failed |
| minute rollover | [['allow', 'allow', 'allow', 'deny-minute', 'allow', 'allow'], 2, 2] | [['allow', 'allow', 'allow', 'deny-second', 'allow', 'allow'], 2, 2] | Failed |
| both exhausted reports second | [['allow', 'deny-minute', 'deny-minute', 'deny-minute'], 0, 1] | [['allow', 'deny-second', 'deny-minute', 'deny-minute'], 0, 1] | Failed |
| minute denials keep second budget | [['allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute', 'allow', 'allow', 'deny-minute'], 2, 2] | [['allow', 'allow', 'deny-minute', 'deny-minute', 'deny-minute', 'allow', 'allow', 'deny-second'], 2, 2] | Failed |
| random traffic | [['allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow'], 1, 3] | [['allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow', 'allow'], 1, 3] | Passed |
SHA-256 / a5983e3656ad0fe0d31c29acf6dfd3e1e54e139ffa93524e5e404129749dc568
HELD IN THE MEMBER ARCHIVE
The verified repair and its recorded checks are member-only.
This mechanism has 6 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, bounded teaching model with stipulated constants and pre-hashed or explicitly hashed inputs; it is not a production implementation and makes no claim of conformance to any library or paper beyond the stated contract. 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.086429+00:00.
Case digest / f8bf85d00ea5c1bc6420de94195264be598987b01cb528b46bb2c873cd56e9a3