FRD-012
Flags that reopen forever
A scan that reruns hourly will reopen the flag you just closed, because the lifetime total it measures is still over the line. Record the magnitude at close and only reopen on something materially worse.
You investigate a flagged account, decide it is fine, and close the flag. An hour later it is back. Close it again; back again.
Two causes compound. An upsert that writes resolved_at = NULL on conflict reopens the row and erases your decision. And the rule itself usually measures a lifetime total — claims ever, credits ever — which stays over the threshold permanently once crossed.
The fix records what the signal measured when you closed it. The flag reopens only if the new measurement is materially worse, by a percentage you set. And a mute closes it for good, for the tester or the known-good account that will always look odd.
/**
* Raise a flag, or leave a closed one closed unless it got materially worse.
* user_flags has UNIQUE (user_id, rule), plus magnitude and muted columns.
*/
function raise_flag(PDO $pdo, int $userId, string $rule, float $magnitude, string $detail): void
{
$st = $pdo->prepare('SELECT * FROM user_flags WHERE user_id = ? AND rule = ?');
$st->execute([$userId, $rule]);
$existing = $st->fetch(PDO::FETCH_ASSOC);
if ($existing) {
if ((int) $existing['muted'] === 1) {
return; // decided, permanently
}
if ($existing['resolved_at'] !== null) {
$threshold = (float) $existing['magnitude'] * (setting_int('flag_reraise_percent', 150) / 100);
if ($magnitude < $threshold) {
return; // closed, and not materially worse
}
}
// Worse than when it was closed, or still open: update, never resurrect blindly.
$pdo->prepare(
'UPDATE user_flags SET magnitude = ?, detail = ?, resolved_at = NULL, last_seen = NOW()
WHERE id = ?'
)->execute([$magnitude, $detail, (int) $existing['id']]);
return;
}
$pdo->prepare(
'INSERT INTO user_flags (user_id, rule, magnitude, detail, raised_at, last_seen)
VALUES (?, ?, ?, ?, NOW(), NOW())'
)->execute([$userId, $rule, $magnitude, $detail]);
}
function close_flag(PDO $pdo, int $flagId, bool $mute, int $adminId): void
{
// magnitude stays as it was: it is the baseline the re-raise compares against.
$pdo->prepare('UPDATE user_flags SET resolved_at = NOW(), resolved_by = ?, muted = ? WHERE id = ?')
->execute([$adminId, $mute ? 1 : 0, $flagId]);
}
Using it
Prefer rules measured over a window to lifetime totals. "Claims in the last seven days" can fall back under the line; "claims ever" cannot.
Show muted flags in a separate list with an unmute action, so a mute is visible rather than invisible.
Put the magnitude in the flag text. "Shared with 5 accounts (was 3 when closed)" answers the question before it is asked.
What bites people
ON DUPLICATE KEY UPDATE resolved_at = NULL is the bug. It silently discards every decision an operator has made.
A re-raise threshold of 150% is a starting point, not a law. Noisy rules want more, money-shaped rules want less.
Never let a scan delete flags. Closed history is how you tell a repeat offender from a first-time oddity.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.