feat(rust): the permission manager — the site owns the whole store (D160-D163, D188-D198)
All checks were successful
PR Checks / client-build (pull_request) Successful in 21s
PR Checks / frozen-manifest (pull_request) Successful in 43s
PR Checks / server-tests (pull_request) Successful in 7m58s

PLAN_REDESIGNS section 1.

- Every sync reads the store (perm.inventory), reconciles it against the
  site's record and its ledger, and pushes. A change made in the game is
  settled by the server's policy (D161): auto-adopt (default), adopt, or
  revoke. The first read of a server imports everything (D198).
- Groups belong to one server unless an admin shares them (D189), in new
  id-keyed tables; the old ones are copied once at boot and left unread.
  Holders may be a Steam account nobody linked (D188).
- An in-game change affects that server only (D190): a grant that reaches
  further gains an exception, a shared group is split.
- Never judged: a permission the server does not register right now (an
  unloaded plugin is not a revocation), and a pair an event lease holds.
- A new admin API (server view, grant/revoke with everywhere-or-here,
  groups by id, share/split, members, drift answers) and a screen on
  PermissionsManager's flow with a state on every toggle (D162, D163, U-1).
- The announcement voice names a group by id; old name settings still read.

Walked on both rigs against the walk core: import on an existing install,
auto-adopt of a grant and a revoke, a fleet grant's exception, Kits
unloaded without loss, a shared group split, adopt and revoke policies.
Server 420/420, client 58/58, swagger, imports and route manifest current.

Refs #21

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-28 06:58:59 -05:00
parent 77c90db338
commit e0d13e73db
24 changed files with 5873 additions and 2589 deletions

View File

@@ -19,6 +19,17 @@
-- it knows this module registered, because it is the side that knows which
-- registrant owned what.
-- The permission manager, rebuilt (PLAN_REDESIGNS §1). Children before
-- `rust_permgroups`, which they reference.
DROP TABLE IF EXISTS rust_perm_exceptions;
DROP TABLE IF EXISTS rust_perm_steam_grants;
DROP TABLE IF EXISTS rust_permgroup_chat;
DROP TABLE IF EXISTS rust_permgroup_steam_members;
DROP TABLE IF EXISTS rust_permgroup_members;
DROP TABLE IF EXISTS rust_permgroup_permissions;
DROP TABLE IF EXISTS rust_permgroup_servers;
DROP TABLE IF EXISTS rust_permgroups;
-- Phase 17. `rust_perm_group_chat` before `rust_perm_groups`, which it
-- references; the rest of this phase is columns, which go with their tables.
DROP TABLE IF EXISTS rust_perm_group_chat;

View File

@@ -1011,3 +1011,155 @@ ALTER TABLE rust_perm_pushed ADD COLUMN IF NOT EXISTS value VARCHAR(255) NULL;
-- The value a changed field holds in the game, for a `chat-field` drift row.
-- NULL on every other kind: a foreign grant is its own description.
ALTER TABLE rust_perm_drift ADD COLUMN IF NOT EXISTS detail VARCHAR(255) NULL;
-- ── The permission manager, rebuilt (PLAN_REDESIGNS §1) ────────────────────
--
-- The site owns EVERY permission and group on a server now (D160), read from
-- the plugin's inventory and imported on first contact (D198). Three things the
-- tables above cannot hold made new ones necessary:
--
-- • A group belongs to ONE server unless an admin shares it (D189). The old
-- `rust_perm_groups` is keyed by name fleet-wide, so two servers' `vip`
-- groups with different contents could not both exist. A group is now a row
-- with its own id; the servers it is on are rows beside it.
-- • A holder may be a Steam account nobody has linked (D188). The authored
-- tables above are keyed by website user (D28), and most of a real store's
-- holders never link.
-- • An in-game change affects that server only (D190), even to a row that
-- reaches more servers — so a fleet-wide grant can carry exceptions.
--
-- The old group tables stay, unread: `permissions.db.migrateGroups` copies them
-- here once, and a downgrade still finds them as they were.
CREATE TABLE IF NOT EXISTS rust_permgroups (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
-- Verbatim, never trimmed: Carbon's own titles end in a space ("Default "),
-- and a trimmed copy would be "changed" by every sync.
title VARCHAR(120) NOT NULL DEFAULT '',
`rank` INT NOT NULL DEFAULT 0,
-- A group NAME on the same server, or ''. Both frameworks store it by name.
parent VARCHAR(64) NOT NULL DEFAULT '',
-- 1: on every server, including servers added later, except those
-- `rust_permgroup_servers` excludes. 0: on exactly the servers it includes.
all_servers TINYINT(1) NOT NULL DEFAULT 0,
-- `admin` (made on the site), `imported` (the first inventory, D198),
-- `adopted` (a later in-game change, D190), `split` (D190's copy),
-- `migrated` (copied from the old tables).
source VARCHAR(32) NOT NULL DEFAULT 'admin',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_rust_permgroups_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Which servers a group is on. `included` 1 names a server a group is on; 0
-- takes one server out of an all-servers group — the split D190 makes when that
-- server's copy changed in the game. The model refuses two groups of one name on
-- one server; a unique key cannot say it across `all_servers`.
CREATE TABLE IF NOT EXISTS rust_permgroup_servers (
group_id INT UNSIGNED NOT NULL,
server_id VARCHAR(64) NOT NULL,
included TINYINT(1) NOT NULL DEFAULT 1,
PRIMARY KEY (group_id, server_id),
KEY idx_rust_permgroup_servers_server (server_id),
CONSTRAINT fk_rust_permgroup_servers_group
FOREIGN KEY (group_id) REFERENCES rust_permgroups (id) ON DELETE CASCADE,
CONSTRAINT fk_rust_permgroup_servers_server
FOREIGN KEY (server_id) REFERENCES rust_servers (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- What a group carries, the same on every server it is on.
CREATE TABLE IF NOT EXISTS rust_permgroup_permissions (
group_id INT UNSIGNED NOT NULL,
permission VARCHAR(128) NOT NULL,
PRIMARY KEY (group_id, permission),
CONSTRAINT fk_rust_permgroup_permissions_group
FOREIGN KEY (group_id) REFERENCES rust_permgroups (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Members who are website accounts, reaching every Steam account they link (D28).
CREATE TABLE IF NOT EXISTS rust_permgroup_members (
group_id INT UNSIGNED NOT NULL,
user_id INT NOT NULL,
added_by INT NULL,
added_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id, user_id),
KEY idx_rust_permgroup_members_user (user_id),
CONSTRAINT fk_rust_permgroup_members_group
FOREIGN KEY (group_id) REFERENCES rust_permgroups (id) ON DELETE CASCADE,
CONSTRAINT fk_rust_permgroup_members_user
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Members who are one Steam account, linked or not (D188).
CREATE TABLE IF NOT EXISTS rust_permgroup_steam_members (
group_id INT UNSIGNED NOT NULL,
steam_id VARCHAR(32) NOT NULL,
source VARCHAR(32) NOT NULL DEFAULT 'admin',
added_by INT NULL,
added_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id, steam_id),
KEY idx_rust_permgroup_steam_members_steam (steam_id),
CONSTRAINT fk_rust_permgroup_steam_members_group
FOREIGN KEY (group_id) REFERENCES rust_permgroups (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- A group's BetterChat style (D138), per group row rather than per name: one
-- server's own `vip` may be styled differently from another server's.
CREATE TABLE IF NOT EXISTS rust_permgroup_chat (
group_id INT UNSIGNED NOT NULL,
field VARCHAR(32) NOT NULL,
value VARCHAR(255) NOT NULL,
PRIMARY KEY (group_id, field),
CONSTRAINT fk_rust_permgroup_chat_group
FOREIGN KEY (group_id) REFERENCES rust_permgroups (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- A permission held by one Steam account, linked or not (D188). The import and
-- auto-adopt write these, and so does a site toggle for an UNLINKED player.
CREATE TABLE IF NOT EXISTS rust_perm_steam_grants (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
steam_id VARCHAR(32) NOT NULL,
permission VARCHAR(128) NOT NULL,
scope VARCHAR(64) NOT NULL DEFAULT '*',
source VARCHAR(32) NOT NULL DEFAULT 'admin',
note VARCHAR(255) NULL,
granted_by INT NULL,
granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_rust_perm_steam_grant (steam_id, permission, scope),
KEY idx_rust_perm_steam_grant_steam (steam_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- "Everywhere except here" (D190): a grant that reaches more than one server,
-- removed on one of them in the game, keeps reaching the others, including
-- servers added later, which a rewrite into per-server rows would lose.
-- `holder` says which grants table `grant_id` is in: `user` or `steam`. No
-- foreign key can name two tables, so deleting a grant deletes its exceptions
-- in the same model call.
CREATE TABLE IF NOT EXISTS rust_perm_exceptions (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
holder VARCHAR(8) NOT NULL,
grant_id INT UNSIGNED NOT NULL,
server_id VARCHAR(64) NOT NULL,
created_by INT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_rust_perm_exception (holder, grant_id, server_id),
CONSTRAINT fk_rust_perm_exceptions_server
FOREIGN KEY (server_id) REFERENCES rust_servers (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- The registering plugin (PLAN_REDESIGNS §0.1), from the inventory. NULL for a
-- name no plugin owns; Carbon's built-in modules register theirs that way.
ALTER TABLE rust_perm_catalogue ADD COLUMN IF NOT EXISTS owner VARCHAR(64) NULL;
-- When this server's store was first imported (D160, D198). NULL until the first
-- inventory completes, and until then every sync imports.
ALTER TABLE rust_perm_sync ADD COLUMN IF NOT EXISTS imported_at DATETIME NULL;
-- What a change made in the game becomes (D161): `auto-adopt` (the default),
-- `adopt` (a person answers each one), or `revoke` (the site's set wins).
ALTER TABLE rust_servers ADD COLUMN IF NOT EXISTS perm_policy VARCHAR(16) NOT NULL DEFAULT 'auto-adopt';
-- Which way a "needs a person" row went: `added` or `removed` in the game, or
-- `split` (D190 gave a server its own copy of a shared group, and says so in
-- case the change was meant for every server).
ALTER TABLE rust_perm_drift ADD COLUMN IF NOT EXISTS direction VARCHAR(16) NOT NULL DEFAULT 'added';