"""One line per refusal, into logs/app.log. The remediation series added ten ways to refuse a request — oversized bodies, rate limits, a missing captcha session, closed websockets, a spent STT budget — and recorded none of them, so a 403 and a 200 looked the same in `logs/`. The bucket key is hashed (`stt_budget.client_id`) by the caller before it gets here: the raw key is a captcha session token or a visitor address, and this file has to be safe to read. `refused()` does not hash for you, because it has no access to the secret — passing a raw token is the one mistake worth guarding against, so keep the hashing next to the key and pass the result. What this file is worth: `logs/` lives inside the container and the free HF CPU Basic tier has no persistent disk, so these lines cover the period since the last Space restart and no longer. """ import logging _log = logging.getLogger("ajaj.refuse") def refused(reason: str, route: str, bucket: str | None = None, **extra: object) -> None: """Record one refusal. Args: reason: Short slug for what refused, e.g. `rate_limited`. route: The path or websocket route the refusal happened on. bucket: Already-hashed client key, or None when no client caused it (a missing API key, an unavailable dependency). **extra: Extra `key=value` fields to append, e.g. `status=413`. """ line = f"{reason} route={route} bucket={bucket or '-'}" if extra: line += " " + " ".join(f"{k}={v}" for k, v in extra.items()) _log.warning(line)