feat(rust): the rewards — tally, kit reward, chat and the news leg (phase 13b, protocol 10)

Four event verbs and the announce leg, per PLAN.md §29:

- rust.participation.open / .collect: the plugin counts who takes part
  (seconds, kills or both, in a zone this run opened or the whole server)
  and collect files them as the run's participants, keyed by Steam id.
- rust.kit.entitle: the five recipient modes (D101), rows in the new
  rust_perm_run_grants (D84) unioned into the permission push, one extra
  use of the kit per reward as site-held credits on perm.sync (D103),
  and the rust.kit.entitled notice deferred from phase 10 (D64).
- rust.announce: one server or every server (D105).
- rust.chat announce leg, speaking only on servers whose new news switch
  is on (D104) - a card on Admin -> Rust visibility (D106).

Budgets rust.grants and rust.announcements; the kit source and four
fixed-choice sources (core has no enum param type). rust_perm_run_grants
carries core's idempotency key so a revert of a lost answer can find its
rows. Protocol 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-24 07:00:44 -05:00
parent 9753dda4af
commit cc185db26b
27 changed files with 2096 additions and 45 deletions

View File

@@ -19,6 +19,9 @@
-- it knows this module registered, because it is the side that knows which
-- registrant owned what.
-- Phase 13b.
DROP TABLE IF EXISTS rust_perm_run_grants;
-- Phase 7b.
DROP TABLE IF EXISTS rust_clan_boards;
DROP TABLE IF EXISTS rust_clan_members;

View File

@@ -792,3 +792,51 @@ CREATE TABLE IF NOT EXISTS rust_clan_boards (
CONSTRAINT fk_rust_clan_boards_server
FOREIGN KEY (server_id) REFERENCES rust_servers (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── What an event granted (phase 13b, protocol 10) ────────────────────────
--
-- `rust.kit.entitle`'s ledger: one row per run, step, website user and
-- permission (PLAN.md §29, D84). It is a table of its own, and not rows in
-- `rust_perm_grants`, because that table is UNIQUE on (user, permission,
-- scope): an admin grant of the same kit would collide with an event's, and a
-- revert deleting "the" row would take the admin's grant with it. The push reads
-- the UNION of the two, so a permission held both ways survives either being
-- withdrawn.
--
-- The grant reaches every account the user has linked (D28), like any other.
-- The CREDIT does not: `steam_id` is the account that took part, and one win is
-- one extra use of the kit on that account (D103). `permission` is empty for a
-- kit anybody may redeem — the credit is then the whole reward.
--
-- `idem_key` is core's idempotency key for the step. It is what a revert has
-- when core lost the answer and holds no resource: without it the rows a lost
-- answer wrote would be a grant nothing could ever withdraw.
--
-- `server_id` is the kit's server and the only one the grant reaches (D102).
-- There is no foreign key to `rust_servers`: a server deleted mid-event must not
-- delete the ledger a revert needs to find.
CREATE TABLE IF NOT EXISTS rust_perm_run_grants (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
run_id VARCHAR(64) NOT NULL,
step_id VARCHAR(64) NOT NULL,
idem_key VARCHAR(190) NOT NULL DEFAULT '',
user_id INT NOT NULL,
server_id VARCHAR(64) NOT NULL,
steam_id VARCHAR(32) NOT NULL,
permission VARCHAR(128) NOT NULL DEFAULT '',
kit VARCHAR(128) NOT NULL,
credit TINYINT(1) NOT NULL DEFAULT 1,
granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_rust_perm_run_grant (run_id, step_id, user_id, permission),
KEY idx_rust_perm_run_grant_server (server_id),
KEY idx_rust_perm_run_grant_key (run_id, idem_key),
CONSTRAINT fk_rust_perm_run_grants_user
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- The news switch (D104): whether a published news post is also said in this
-- server's chat. Off, because core enqueues every registered leg for every
-- post, and without it the day this module updates every post would start
-- appearing in every server's chat.
ALTER TABLE rust_servers ADD COLUMN IF NOT EXISTS announce_news TINYINT(1) NOT NULL DEFAULT 0;