Skip to content

feat(antibot): an unban pardons the caller until its evidence is gone - #446

Merged
raphoester merged 1 commit into
mainfrom
claude/map-unban-persistence-f87758
Oct 20, 2026
Merged

raphoester merged 1 commit into
mainfrom
claude/map-unban-persistence-f87758

Conversation

@raphoester

Copy link
Copy Markdown
Owner

Why

An unban lifted the ban and forgot its offence, but the watchdogs kept what they had read. A rule that still read the player guilty banned it again 5 minutes later (reflagInterval), as a first offence. An unban for "it was a mistake" did not hold, and nothing recorded the decision.

What changed

An unban now writes a pardon. It is a row of antibot.pardons (scope) or antibot.account_pardons (account), with when, the ban it lifted (flags, offence, banned_until) and when the pardon ends (expires_at). Every pardon is kept: one row per decision.

While a pardon runs:

  • the jury flags nothing on that key, however guilty it reads (Banner.flaggable, checked under the ban's lock);
  • a pardoned caller convicts nobody, not even the accounts it rotates with (Book.Pardoned, in jury.flag). A pardoned accomplice is skipped and the rest of the ring still falls;
  • an operator's BanPlayer still bans the key. The pardon only holds off the jury.

A pardon lasts antiBot.evidence.retention (72h). No evidence outlives retention, so when the pardon ends, every reading rests on play after the unban. The evidence is not touched: forgetting it would take a forget in each of the ten watchdogs.

Storage: bans.Record carries the latest pardon. postgres_ban_store reads it in the same query as the ban (a LEFT JOIN LATERAL) and writes it in the same transaction and advisory lock. The adapter upserts the pardon row by (key, pardoned_at); it does not decide anything.

API: UnbanPlayerResponse.pardoned_until (5) and InspectPlayerResponse.pardoned_until (18), both new fields. antibot.Pardon and Guard.Pardon are new names in the antibot API.

Backoffice: the unban result says "Pardoned until …", the inspection shows a "Pardoned" line while one runs, and the unban hint no longer says the player is banned again on its next click.

For the reviewer

  • During the 72h, no automatic ban can fall on that key, not even on new play. Only a manual ban can. That is the cost of having no new setting. A pardon duration in config is easy to add if you want a shorter one.
  • A guest needs two unbans to be fully pardoned, its account and its scope, as it already needed two unbans to be fully unbanned.
  • Rebased on feat(antibot): a ban is told, with its end and a link to the rules #445 (shadowban → bans, Bans → Book).
  • The frontend and the backoffice bindings were made with each app's own protoc-gen-es version (1.10.0 and 1.10.1). Each one only adds the two fields.
  • TestAnUnbanLeavesTheEvidenceSoTheJuryBansAgain is replaced by TestAnUnbanPardonsTheCallerUntilTheEvidenceThatBannedItIsGone: after the unban, an hour of the same play bans nothing; 72h later, the same play bans again, as a first offence.

Checks

  • go test -tags testing ./... (the pardon contract runs on the memory store and on postgres), make deadcode, make lint, make check-format
  • backoffice: npm test, npm run lint, npm run build
  • frontend: lint (pre-commit)

🤖 Generated with Claude Code

An unban lifted the ban and forgot its offence, but the watchdogs kept what
they had read. A rule that still read the caller guilty banned it again five
minutes later (reflagInterval), as a first offence.

UnbanPlayer now writes a pardon: a row of antibot.pardons or
antibot.account_pardons, with when, the ban it lifted (flags, offence, end)
and when the pardon ends. Every pardon is kept. While one runs:

- the jury flags nothing on that key, however guilty it reads;
- a pardoned caller convicts nobody, not even the accounts it rotates with;
- an operator's BanPlayer still bans it.

A pardon lasts antiBot.evidence.retention (72h). No evidence outlives
retention, so when the pardon ends every reading rests on play after the
unban. The evidence is not touched.

The bans Record carries the latest pardon, read in the same query and
written in the same transaction and lock as the ban.

UnbanPlayer and InspectPlayer answer pardoned_until, and the backoffice
shows it. Its unban hint no longer says the player is banned again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@raphoester
raphoester merged commit 603583d into main Oct 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant