Admin & Audit¶
Status: ✅ Shipped — the full RBAC scope chain is live:
AdminScopeGrantmodel + 4 migrations (e2a7f3c8b5d1phase A1 tables,a7c4e91b2d08phase E audit-review seed,9c46d8ea0c11system-health-view seed,f3a8c2d91e47operational-scopes expansion), per-endpoint scope checks wired across 32 route files, andUser.is_adminis now Phase-C3 grant-derived — a correlatedEXISTSexpression (user.py), not the plain settable column this page previously described. The bootstrap superadmin seed and theAdminActionLogaudit trail are also live. (impl 2026-08-04, status-flip — this was the stalest claim found in a full-repo sweep tonight)
Admin authentication is a side table on User (AdminCredentials). Authorization is scope-based RBAC per ADR-0058 A-F2, greenlit as the RBAC foundation chain by ADR-0093 item 19: each admin capability is one of the canonical scopes, granted per-admin via the AdminScopeGrant join table, with every admin action recorded in AdminActionLog. User.is_admin is Phase-C3 grant-derived — a correlated EXISTS against active scope grants (user.py) — superseding the historical flat Boolean gate from ADR-0027. Admin routes check the required scope (403 names the missing scope); grant/revoke APIs and ScopesManager at /scopes are live (see ../OPERATIONS/admin-ui.md; the old PermissionsDashboard scaffolding was deleted). Beyond the scope gate, granular moderation state already lives inline on the resource being administered (e.g., MarketTransaction.flagged_suspicious / reviewed_by, CombatLog.admin_resolved / admin_reviewed, Message.flagged / moderated_by, PriceAlert.acknowledged_by).
User (admin flag)¶
Source: services/gameserver/src/models/user.py (is_admin hybrid property)
User.is_admin is grant-derived: Python and SQL reads use EXISTS (active AdminScopeGrant WHERE revoked_at IS NULL). The physical users.is_admin column remains as a denormalized cache that grant/revoke still sync — it is not the live authorization gate and was not dropped in Phase C3. See ./player.md for the full User schema. An admin user is one with at least one active scope grant (and typically a populated AdminCredentials row for the admin login path). Capability checks read scopes from AdminScopeGrant, not the flat cache column.
AdminCredentials¶
Source: services/gameserver/src/models/admin_credentials.py
Purpose: Separate password hash for the admin login path, isolated from the OAuth/MFA flow used by ordinary users.
Fields:
| name | type | constraints | notes |
|---|---|---|---|
| user_id | UUID FK users.id | PK, CASCADE | 1:1 with User |
| password_hash | String(255) | not null | bcrypt/argon2 |
| last_password_change | DateTime | server default now | for rotation policy |
Relationships: user (1:1, back-pop User.admin_credentials).
AdminScopeGrant¶
Source: services/gameserver/src/models/admin_scope_grant.py — ✅ shipped, greenlit as ADR-0093 item 19 (2026-07-10).
Purpose: The live authorization gate. One row per (admin user, scope) grant. An admin's capabilities are exactly the set of scopes they currently hold (revoked_at IS NULL). Capabilities are drawn from the canonical scope set defined in ADR-0058 A-F2 — families admin.players.*, admin.subscriptions.*, admin.webhooks.*, admin.regions.*, admin.aria.audit, admin.multi_account.review, admin.bang.regenerate, admin.scopes.*, admin.audit.view.
Full field schema (id, user_id, scope, granted_by, granted_at, revoked_at, revoked_by) and indexes are in ./gameplay.md#adminscopegrant. The bootstrap superadmin is granted admin.scopes.grant + admin.scopes.revoke + admin.audit.view via a one-time migration.
User.is_admin is the derived view EXISTS (SELECT 1 FROM admin_scope_grants WHERE user_id = User.id AND revoked_at IS NULL) — see User (admin flag).
AdminActionLog¶
Source: services/gameserver/src/models/admin_action_log.py — ✅ shipped with the ADR-0093 item 19 RBAC foundation chain.
Purpose: Append-only record of every scope-authorized admin action, carrying the scope_used that authorized it. Drives the Admin UI /audit ledger and HIGH_IMPACT retrospective review queue (AdminActionLogPage) that surfaces high-impact actions (admin.subscriptions.*, admin.webhooks.replay, admin.regions.terminate, admin.scopes.*) for acknowledgement by another admin holding admin.audit.view / admin.audit.review. Distinct from the catch-all HTTP AuditLog below: AuditLog is the request-trail emitted by middleware; AdminActionLog is the live scope-keyed action record.
Full field schema (admin_user_id, scope_used, action, target_type, target_id, payload_snapshot, result, failure_reason, reviewed_by, reviewed_at, at) and indexes are in ./gameplay.md#adminactionlog.
AuditLog¶
Source: services/gameserver/src/models/audit_log.py
Purpose: Catch-all request/audit trail emitted by middleware. Used for security/compliance review and admin search.
Fields:
| name | type | constraints | notes |
|---|---|---|---|
| id | UUID | PK | |
| timestamp | DateTime | default utcnow, indexed | |
| method | String(10) | not null | HTTP verb |
| path | String(255) | not null, indexed | |
| status_code | Integer | nullable | |
| duration_ms | Integer | nullable | |
| user_id | UUID | nullable, indexed | no FK constraint (logs survive user deletes) |
| user_type | String(20) | nullable | admin/player/anonymous |
| client_ip | String(45) | not null | IPv6-safe |
| user_agent | Text | nullable | |
| action | String(100) | indexed | login/logout/economy_intervention/… |
| resource_type | String(50) | nullable | auth/ship/economy/message/… |
| resource_id | UUID | nullable | affected resource id |
| query_params, request_body, security_flags | JSON | nullable | request body should be sanitized of secrets |
| response_summary | Text | nullable | |
| violation_detected | String(100) | nullable | violation classifier |
Relationships: none (deliberately decoupled — no FK).
Per-resource admin flags¶
Several entities carry inline admin / moderation columns rather than living in a centralized permissions table. These are the surfaces an admin UI hits:
| Entity | Admin columns | Source |
|---|---|---|
MarketTransaction (enhanced_market_transactions) |
admin_notes, flagged_suspicious, reviewed_by (FK users.id) |
market_transaction.py |
CombatLog |
admin_notes, admin_resolved, admin_resolved_at, disputed, resolved, admin_reviewed |
combat_log.py |
Message |
flagged, flagged_reason, moderated_at, moderated_by (FK users.id) |
message.py |
PriceAlert |
acknowledged_by (FK users.id), acknowledged_at, resolved_at |
market_transaction.py |
Reputation |
is_locked, lock_reason, lock_expires, special_status |
reputation.py |
Indexes on audit_logs.timestamp, audit_logs.path, audit_logs.user_id, and audit_logs.action cover the common admin-search queries. Composite indexes for user-activity searches are defined inline.
Authorization surface¶
The live gate is fine-grained scopes, not roles, per ADR-0058 A-F2, greenlit by ADR-0093 item 19. Middleware/routes gate each admin endpoint on a specific scope (e.g., admin.regions.terminate), returning 403 with the missing scope name in the body when a request lacks it. Scope membership is read from active AdminScopeGrant rows — operators dial each admin's capabilities up or down one scope at a time, with no role hierarchy. User.is_admin is the grant-derived EXISTS view used where a Boolean admin check remains; it is not the historical flat column gate from ADR-0027 (kept as history only). Per-resource moderation flags (the inline columns above) sit alongside the scope set: scopes gate who may invoke a capability, while the inline flags carry per-record moderation state for the entity being administered.