fix(admin): apply audit logs and side effects to bulk actions (#2758)

This commit is contained in:
Hampus
2026-09-14 14:34:43 +02:00
committed by GitHub
parent a9dc74a520
commit 91a2604e9e
42 changed files with 1988 additions and 916 deletions
@@ -420,9 +420,9 @@ Every hash is written with the category `manual`, the severity `2`, and a null c
### Side effects
Each hash is lowercased and replaces any existing entry. Changes become visible across the instance when the job completes. If it is cancelled, changes already made can remain unapplied on other nodes until a later blocklist update or the twelve-hour feed sync. No Gateway Dispatch is emitted.
Each hash is lowercased and replaces any existing entry. Changes become visible across the instance when the job stops, whether it ran to the end or was cancelled, so a cancelled job still enforces the hashes it already wrote. No Gateway Dispatch is emitted.
The job records one aggregate [Admin audit entry](/admin-api/#admin-audit-entry-object) under the action `bulk_ban_file_shas`, with the submitted, successful, and failed counts. A cancelled job records none.
The job records one [Admin audit entry](/admin-api/#admin-audit-entry-object) per written hash under the action `ban_file_sha`, with that hash in its metadata, exactly as [Add blocklist entry](#add-blocklist-entry) does. It then records one aggregate entry under the action `bulk_ban_file_shas`, with the submitted, processed, successful, and failed counts. A cancelled job records the entries for the hashes it wrote and an aggregate entry marked `cancelled`.
### Rate limit
@@ -150,21 +150,19 @@ Entities are processed in the submitted order. [Cancel job](/admin-api/jobs/#can
Progress updates arrive before work starts, after every 25 entities, and at completion. `schedule_user_deletion` updates after every 10 accounts instead. The final message includes successful and failed counts.
Every task writes one summary Admin audit entry when it finishes, with the action `bulk_update_user_flags`, `bulk_update_suspicious_activity_flags`, `bulk_update_guild_features`, `bulk_add_guild_members`, or `bulk_schedule_deletion`. The summary has the audit reason, the entity count, the operation-specific parameters, and the successful and failed counts. Its `target_id` is the guild for `add_guild_members` and `0` for every other task. A cancelled or failed job writes no summary entry.
Every task writes one summary Admin audit entry when it finishes, with the action `bulk_update_user_flags`, `bulk_update_suspicious_activity_flags`, `bulk_update_guild_features`, `bulk_add_guild_members`, `bulk_schedule_deletion`, `bulk_ban_file_shas`, or `bulk_delete_user_messages`. The summary has the audit reason, the entity count, the operation-specific parameters, the job identifier, and the processed, successful, and failed counts. Its `target_type` is `bulk_job` and its `target_id` is the job identifier, except for `add_guild_members`, which targets the guild. A failed job writes no summary entry. A cancelled job writes one, marked `cancelled`, covering the entities it processed before it stopped.
`update_user_flags` writes one `update_flags` entry for each account and dispatches [User Update](/gateway/events/#user-update) to the account's sessions. A change to a publicly visible flag also dispatches [Guild Member Update](/gateway/events/#guild-member-update) to every guild the account is in.
`update_suspicious_activity_flags` rewrites each account's verification requirements and dispatches [User Update](/gateway/events/#user-update). No [Guild Member Update](/gateway/events/#guild-member-update) follows. The task writes no per-account audit entry.
`update_suspicious_activity_flags` rewrites each account's verification requirements and dispatches [User Update](/gateway/events/#user-update). No [Guild Member Update](/gateway/events/#guild-member-update) follows. The task writes one `update_suspicious_activity_flags` entry for each account, with the audit reason, and records a risk outcome when the requirements become non-empty. An unknown flag name fails the job before any account is changed.
`update_guild_features` writes one `update_features` entry for each guild, dispatches [Guild Update](/gateway/events/#guild-update), and reindexes the guild for search. Fluxer reconciles a guild that already has a discovery application record against the new feature set, so gaining `DISCOVERABLE` approves the record and losing it marks the record removed. A guild with no discovery record is left alone.
`add_guild_members` bypasses the ban check and the risk gate. The task suppresses the join system message, records the join source as an Admin force add, and dispatches [Guild Member Add](/gateway/events/#guild-member-add) to the guild and [Guild Create](/gateway/events/#guild-create) to the added account's sessions. The task still enforces the per-account guild cap and the guild member cap, so an account at either ceiling is counted as failed. An account that is already a member is left unchanged and counted as successful, with no second membership and no Dispatch. Adding a bot account also records a `BOT_ADD` guild audit log entry attributed to the acting Admin.
`schedule_user_deletion` marks each account deleted. The task stores the reason code, public reason, and audit reason on the account, and reschedules its pending deletion. It dispatches [User Update](/gateway/events/#user-update), writes one `schedule_deletion` entry for each account, and emails the account holder when an address is on file. A failed email is logged and does not fail the entity.
`delete_user_messages` deletes every message each account wrote, across every channel. It writes one `delete_all_user_messages` entry for each account, with the audit reason, the account, and the deleted message count, and reports progress after every account.
:::caution[Bulk scheduling is narrower than the single-account operation]
The worker does not terminate sessions, cancel or refund a Stripe subscription, ban the account's identifiers, or resolve pending reports. [Schedule user deletion](/admin-api/users/#schedule-user-deletion) does each of those for one account, the last two only when the reason is not `USER_REQUESTED`.
:::
`schedule_user_deletion` marks each account deleted. The task runs the same steps as [Schedule user deletion](/admin-api/users/#schedule-user-deletion) for one account. It stores the reason code, public reason, and audit reason on the account, reschedules its pending deletion, terminates its sessions, cancels and refunds its Stripe subscription when one is on file, dispatches [User Update](/gateway/events/#user-update), writes one `schedule_deletion` entry for each account with the audit reason and the reason code, and emails the account holder when an address is on file. A failed email is logged and does not fail the entity. When the reason is not `USER_REQUESTED`, the task also bans the account's identifiers and resolves the pending reports against it.
### Rate limit
@@ -10,7 +10,7 @@ Admin discovery is the review side of the public guild directory. An Admin decid
Every operation except [Remove discovery listing](#remove-discovery-listing) requires `discovery:review`, which covers the reads, [Review discovery application](#review-discovery-application), [Move discovery listings to a category](#move-discovery-listings-to-a-category), and [Update discovery listing](#update-discovery-listing). [Remove discovery listing](#remove-discovery-listing) requires `discovery:remove` instead. Neither implies the other.
No operation here reads `X-Audit-Log-Reason` or records an Admin audit entry. An operation that stores a reason takes it in its request body.
[Move discovery listings to a category](#move-discovery-listings-to-a-category) reads `X-Audit-Log-Reason` and records one `update_discovery_categories` Admin audit entry with the audit reason, the category, and the requested, updated, and failed counts. No other operation here reads that header or records an Admin audit entry. An operation that stores a reason takes it in its request body.
:::note[Review runs regardless of the discovery setting]
An operator can disable discovery for the whole instance. Only the public [Discovery](/http-api/discovery/) routes read that state, and they return 400 `DISCOVERY_DISABLED` while it is off, so an application can be reviewed into a directory no account can see.
@@ -281,7 +281,7 @@ Every reloaded guild process fires one [Guild Update](/gateway/events/#guild-upd
### Side effects
A guild whose owner node cannot be resolved is not counted. Reloads can still be in progress when the response arrives. No guild data is changed and no Admin audit entry is recorded.
A guild whose owner node cannot be resolved is not counted. Reloads can still be in progress when the response arrives. No guild data is changed. The operation records one `reload_guilds` Admin audit entry with the audit reason, the requested guild count, and the reloaded count.
### Rate limit
@@ -77,7 +77,7 @@ Each code is 32 characters drawn from the uppercase letters, the lowercase lette
A failed request can leave some codes redeemable without returning them. Retrying can therefore create additional codes.
The operation records no Admin audit entry and emits no Gateway Dispatch.
The operation records one `generate_gift_codes` Admin audit entry with the audit reason, the code count, and the requested duration. The codes themselves are not recorded. It emits no Gateway Dispatch.
### Rate limit