FA-60486 / Loyalty points ledgers / Open access
Pending points hold period: posting day boundary · case 01
Points stay pending one day longer than the published hold period.
ROOT CAUSE
Posting requires the day to be strictly after earn day + hold.
VERIFIED REPAIR
Points post on day >= earn day + hold.
Unsuccessful approach: Posting when day - earn >= hold - 1 releases points a day early.
Case contract
Events: ["earn",day,order,pts] starts pending; points post (become available) on day >= earn day + hold. ["refund",day,order] cancels a live earn if day < earn day + hold ("cancelled"), otherwise claws it back ("clawed"; available may go negative); unknown or already refunded orders are "ignored". ["redeem",day,pts] spends available (posted - spent) points or returns "insufficient". ["query",day] returns [pending, available]. Return results list.
Why this case matters
Member point balances, statements and the program liability depend on this ledger rule.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
earns = {}
spent = 0
out = []
posted = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day > e[0] + x['hold'])
pending = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day < e[0] + x['hold'])
for ev in x['events']:
k, day = ev[0], ev[1]
if k == 'earn':
earns[ev[2]] = [day, ev[3], 'live']
out.append('pending')
elif k == 'refund':
e = earns.get(ev[2])
if e is None or e[2] != 'live':
out.append('ignored')
continue
if day < e[0] + x['hold']:
e[2] = 'cancelled'
else:
e[2] = 'clawed'
out.append(e[2])
elif k == 'redeem':
if ev[2] > posted(day) - spent:
out.append('insufficient')
continue
spent += ev[2]
out.append('ok')
else:
out.append([pending(day), posted(day) - spent])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 32]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 41], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 41]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 23]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 31], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 151], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 12], ['query', 5], ['query', 12]]}, ['pending', [12, 0], [0, 12]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 26, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 33]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 42], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 42]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 24]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 32], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 152], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 13], ['query', 5], ['query', 12]]}, ['pending', [13, 0], [0, 13]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 27, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 34]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 43], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 43]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 25]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 33], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 153], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 14], ['query', 5], ['query', 12]]}, ['pending', [14, 0], [0, 14]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 28, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 35]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 44], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 44]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 26]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 34], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 154], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 15], ['query', 5], ['query', 12]]}, ['pending', [15, 0], [0, 15]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 29, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 36]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 45], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 45]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 27]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 35], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 155], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 16], ['query', 5], ['query', 12]]}, ['pending', [16, 0], [0, 16]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 25, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]]]
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 |
|---|---|---|---|
| posts after hold | ['pending', [100, 0], [0, 0], [0, 100]] | ['pending', [100, 0], [0, 100], [0, 100]] | Failed |
| refund in hold | ['pending', 'pending', 'cancelled', [0, 41]] | ['pending', 'pending', 'cancelled', [0, 41]] | Passed |
| refund after post | ['pending', 'ok', 'clawed', [0, -50]] | ['pending', 'ok', 'clawed', [0, -50]] | Passed |
| double refund | ['pending', 'cancelled', 'ignored', 'ignored'] | ['pending', 'cancelled', 'ignored', 'ignored'] | Passed |
| redeem pending | ['pending', 'pending', 'insufficient', 'ok'] | ['pending', 'pending', 'insufficient', 'ok'] | Passed |
| same day earn query | ['pending', [12, 0], [0, 0]] | ['pending', [12, 0], [0, 12]] | Failed |
| refund near posting | ['pending', 'cancelled', [0, 0]] | ['pending', 'cancelled', [0, 0]] | Passed |
SHA-256 / f755cc4ad81f8451f994957005b41bcf0ba5ebf55f3cb1abf135b78099f03c71
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
earns = {}
spent = 0
out = []
posted = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day - e[0] >= x['hold'] - 1)
pending = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day < e[0] + x['hold'])
for ev in x['events']:
k, day = ev[0], ev[1]
if k == 'earn':
earns[ev[2]] = [day, ev[3], 'live']
out.append('pending')
elif k == 'refund':
e = earns.get(ev[2])
if e is None or e[2] != 'live':
out.append('ignored')
continue
if day < e[0] + x['hold']:
e[2] = 'cancelled'
else:
e[2] = 'clawed'
out.append(e[2])
elif k == 'redeem':
if ev[2] > posted(day) - spent:
out.append('insufficient')
continue
spent += ev[2]
out.append('ok')
else:
out.append([pending(day), posted(day) - spent])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 32]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 41], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 41]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 23]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 31], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 151], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 12], ['query', 5], ['query', 12]]}, ['pending', [12, 0], [0, 12]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 26, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 33]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 42], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 42]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 24]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 32], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 152], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 13], ['query', 5], ['query', 12]]}, ['pending', [13, 0], [0, 13]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 27, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 34]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 43], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 43]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 25]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 33], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 153], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 14], ['query', 5], ['query', 12]]}, ['pending', [14, 0], [0, 14]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 28, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 35]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 44], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 44]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 26]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 34], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 154], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 15], ['query', 5], ['query', 12]]}, ['pending', [15, 0], [0, 15]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 29, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 36]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 45], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 45]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 27]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 35], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 155], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 16], ['query', 5], ['query', 12]]}, ['pending', [16, 0], [0, 16]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 25, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]]]
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 |
|---|---|---|---|
| posts after hold | ['pending', [100, 100], [0, 100], [0, 100]] | ['pending', [100, 0], [0, 100], [0, 100]] | Failed |
| refund in hold | ['pending', 'pending', 'cancelled', [0, 41]] | ['pending', 'pending', 'cancelled', [0, 41]] | Passed |
| refund after post | ['pending', 'ok', 'clawed', [0, -50]] | ['pending', 'ok', 'clawed', [0, -50]] | Passed |
| double refund | ['pending', 'cancelled', 'ignored', 'ignored'] | ['pending', 'cancelled', 'ignored', 'ignored'] | Passed |
| redeem pending | ['pending', 'pending', 'insufficient', 'ok'] | ['pending', 'pending', 'insufficient', 'ok'] | Passed |
| same day earn query | ['pending', [12, 0], [0, 12]] | ['pending', [12, 0], [0, 12]] | Passed |
| refund near posting | ['pending', 'cancelled', [0, 0]] | ['pending', 'cancelled', [0, 0]] | Passed |
SHA-256 / 0403b14542a1d3910eae9aae0ef60e47e73d8d31c5ea8387cb63845662dbfb51
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
earns = {}
spent = 0
out = []
posted = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day >= e[0] + x['hold'])
pending = lambda day: sum(e[1] for e in earns.values() if e[2] == 'live' and day < e[0] + x['hold'])
for ev in x['events']:
k, day = ev[0], ev[1]
if k == 'earn':
earns[ev[2]] = [day, ev[3], 'live']
out.append('pending')
elif k == 'refund':
e = earns.get(ev[2])
if e is None or e[2] != 'live':
out.append('ignored')
continue
if day < e[0] + x['hold']:
e[2] = 'cancelled'
else:
e[2] = 'clawed'
out.append(e[2])
elif k == 'redeem':
if ev[2] > posted(day) - spent:
out.append('insufficient')
continue
spent += ev[2]
out.append('ok')
else:
out.append([pending(day), posted(day) - spent])
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 32]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 41], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 41]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 23]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 31], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 151], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 12], ['query', 5], ['query', 12]]}, ['pending', [12, 0], [0, 12]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 26, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 33]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 42], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 42]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 24]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 32], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 152], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 13], ['query', 5], ['query', 12]]}, ['pending', [13, 0], [0, 13]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 27, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 34]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 43], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 43]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 25]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 33], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 153], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 14], ['query', 5], ['query', 12]]}, ['pending', [14, 0], [0, 14]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 28, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 35]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 44], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 44]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 26]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 34], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 154], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 15], ['query', 5], ['query', 12]]}, ['pending', [15, 0], [0, 15]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 29, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]], [['posts after hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['query', 29], ['query', 30], ['query', 36]]}, ['pending', [100, 0], [0, 100], [0, 100]]], ['refund in hold', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 2, 'o2', 45], ['refund', 10, 'o1'], ['query', 40]]}, ['pending', 'pending', 'cancelled', [0, 45]]], ['refund after post', {'hold': 14, 'events': [['earn', 0, 'o1', 70], ['redeem', 20, 50], ['refund', 21, 'o1'], ['query', 27]]}, ['pending', 'ok', 'clawed', [0, -50]]], ['double refund', {'hold': 10, 'events': [['earn', 0, 'o1', 35], ['refund', 3, 'o1'], ['refund', 4, 'o1'], ['refund', 5, 'zz']]}, ['pending', 'cancelled', 'ignored', 'ignored']], ['redeem pending', {'hold': 30, 'events': [['earn', 0, 'o1', 100], ['earn', 25, 'o2', 100], ['redeem', 40, 155], ['redeem', 40, 100]]}, ['pending', 'pending', 'insufficient', 'ok']], ['same day earn query', {'hold': 7, 'events': [['earn', 5, 'o1', 16], ['query', 5], ['query', 12]]}, ['pending', [16, 0], [0, 16]]], ['refund near posting', {'hold': 30, 'events': [['earn', 0, 'o1', 60], ['refund', 25, 'o1'], ['query', 60]]}, ['pending', 'cancelled', [0, 0]]]]]
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 |
|---|---|---|---|
| posts after hold | ['pending', [100, 0], [0, 100], [0, 100]] | ['pending', [100, 0], [0, 100], [0, 100]] | Passed |
| refund in hold | ['pending', 'pending', 'cancelled', [0, 41]] | ['pending', 'pending', 'cancelled', [0, 41]] | Passed |
| refund after post | ['pending', 'ok', 'clawed', [0, -50]] | ['pending', 'ok', 'clawed', [0, -50]] | Passed |
| double refund | ['pending', 'cancelled', 'ignored', 'ignored'] | ['pending', 'cancelled', 'ignored', 'ignored'] | Passed |
| redeem pending | ['pending', 'pending', 'insufficient', 'ok'] | ['pending', 'pending', 'insufficient', 'ok'] | Passed |
| same day earn query | ['pending', [12, 0], [0, 12]] | ['pending', [12, 0], [0, 12]] | Passed |
| refund near posting | ['pending', 'cancelled', [0, 0]] | ['pending', 'cancelled', [0, 0]] | Passed |
SHA-256 / 2868ebaecdf43582bde2d467978660153522d1cd3c03b37fc6f50a0cfbab5da9
Verification & scope
Stipulated bounded loyalty-program model with explicit toy rules; not any real program terms. 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:46:46.017881+00:00.
Case digest / 041e79f0ed2d16a0e67c637f6a09be772553a1e7c08abeff995e2c91fcac9ee7