v1.7 — Admin Evolution
Promote the standalone admin dashboard from "MVP that ships" to a multi-role platform with throttled access, a self-health probe, role-aware nav, GDPR rails, and end-to-end observability.
Summary
The standalone admin dashboard (shipped in v1.3) was deliberately minimal — a single super-admin role, no rate limiting, no observability, and no role-based UI. v1.7 evolved it into a platform suitable for delegating support, moderation, and review work to non-engineers.
The milestone introduced a real role system (super_admin, admin, moderator, support) backed by an authorize_admin_role! helper, Relay-style cursor pagination across six admin queries, Rack::Attack throttles per admin identity, a me and pendingCounts query so the UI can hide queues a role cannot access, a system_health probe surfacing four backend dependencies, Sentry + PostHog wired into the admin frontend with surface-tagged events, and a route-driven nav with breadcrumbs, environment badges, role badges, and PermissionGuard wrappers on destructive buttons.
It shipped on its own branch (gsd/v1.7-admin-evolution) and landed in master on 2026-04-18.
Goal
Take the v1.3 admin dashboard MVP and harden it into a delegatable platform: roles, pagination, rate limiting, self-health, observability, and role-aware UI — so support and moderation work can be handed off without expanding super-admin access.
Scope — What Shipped
Backend (Phases 26–28)
- Pagination & search —
pg_trgmGIN indexes onemail,username, andfirst_name||last_name; six admin queries (users,searchUsers,adminActions,moderationQueue,aiEmployees,pendingArtifacts) converted to Relay connections withmax_page_size: 200clamping;Admin::UserSearchrewritten onsimilarity(). - Rate limiting —
rack-attack ~> 6.7integrated with admin-identity throttles (120 queries/min, 30 mutations/min peradmin.public_id), usingGraphqlOperationInspector.classifyto distinguish queries from mutations andAdminIdentityResolver.callfor identity. 429 response shape{ error, retry_after }. - Caching —
AdminStatsService.callwrapped inRails.cache.fetchwith keyadmin_stats:v1:{days_back}and 10-minute TTL. - Roles —
authorize_admin_role!(role)helper added to theAuthorizationconcern;mequery returnscurrentUser;pendingCountsreturns role-scoped queue counts (returnsnilfor queues the caller's role cannot see, so the UI hides the badge). - System health —
SystemHealthServicedispatches probes against DB, Redis, Sidekiq, and LiteLLM (each swallows errors →false, never raises);SystemHealthTypeexposes booleans +deploy_revision/deploy_timestampmetadata;system_healthGraphQL field gated byrequire_admin!. - GDPR rails —
gdpr_requeststable introduced with role-scoped intake/processing flow;pending_counts.gdpr_countprobestable_exists?and returns0until the table lands.
Frontend (Phases 26–31)
- Apollo migration to connections —
admin_queries.ts,ai_workforce_queries.ts,dashboard_queries.ts,user_queries.tsrewritten toedges/node/cursor/pageInfo; all 5 admin views (Dashboard, UsersView, AIWorkforceView, ModerationView, ReviewQueueView) updated to mapedges.map(e => e.node). - Observability —
@sentry/vue@^10.45.0andposthog-js@^1.364.7installed;initAdminSentry(50% traces, 100% replay on error,surface:admintag) andinitAdminPostHog(surface:adminsuper-property) wired intomain.ts;App.vuewatches[isSignedIn, user]to set/clear identity on Sentry + PostHog;ErrorBoundary.vuecaptures uncaught errors and shows a fallback card with copyable Sentry event ID. - Apollo error link — added with Sentry forwarding and PII scrubbing on outgoing payloads.
- Role-aware shell —
useRolescomposable exposes reactiveroles,hasRole,canPerform,isSuperAdmin,isLoading;ACTION_REQUIREMENTSmaps 10 canonical actions to required roles;RoleBadge,EnvironmentBadge,AppBreadcrumbs,PermissionGuard(slot wrapper that disables children withpointer-events-none+ tooltip whencanPerformis false), andUnauthorizedViewshipped. - Router guard —
router.beforeEachrunsapolloClient.query(ME_QUERY)cache-first; redirects to/unauthorized?attempted=<fullPath>on role mismatch; falls back to/sign-inon query failure (expired Clerk session). - Self-health view —
/system/self-healthroute renders a 10-probe grid (5 frontend probes: build SHA, deploy timestamp, Sentry, PostHog, Clerk session; 5 backend probes: DB, Redis, Sidekiq, LiteLLM, GraphQL latency client-measured) with refresh button, PASS/WARN/FAIL pills, and a footer showing truncatedpublic_id+ role pills. - Action telemetry —
Admin::Loggedinteraction emitsadmin_action_loggedPostHog events viaPosthogNotifier.
UI Polish
- Topbar layout:
AppBreadcrumbs | spacer | EnvironmentBadge | RoleBadge | UserButton. - Sidebar nav filtered by
useRolesso a moderator sees only permitted items. - Unread count badges on moderation + review queue with
99+cap and zero-hide;PENDING_COUNTS_QUERYpolled every 30s, stopped on unmount. - Destructive buttons (Edit Roles, Save Roles, Approve Artifact, Reject Artifact, Hide Content, Dismiss Flag) wrapped in
PermissionGuardwith role-specific tooltips.
Phases
| Phase | Name | Status | Highlights |
|---|---|---|---|
| 26 | Pagination, indexes, rate limiting, observability scaffolding | Shipped | pg_trgm, Relay connections, Rack::Attack, Sentry+PostHog wired |
| 27 | Roles + role-aware shell | Shipped | authorize_admin_role!, me, pendingCounts, useRoles, PermissionGuard |
| 28 | GDPR intake | Shipped | gdpr_requests table, role-scoped queue, frontend intake |
| 29 | System health probe | Shipped | SystemHealthService + /system/self-health 10-probe grid |
| 30 | Apollo error link + admin telemetry | Shipped | PII-scrubbed errors, admin_action_logged events |
| 31 | Final polish + cross-cuts | Shipped | breadcrumbs, environment badges, polished filters |
Key Decisions
- Roles via a separate
admin_rolestable, not a column on User. Lets a single user hold multiple roles and lets us add new roles without migrations tousers.User#has_role?proxies through the join. pendingCountsreturnsnilfor inaccessible queues, not0. The UI hides the badge entirely; rendering0for a queue the caller cannot see is misleading.- PostHog
surface:adminsuper-property. Lets PostHog separate admin behavior from user behavior in the same project without two PostHog instances. - Frontend role guard is cache-first, server is authoritative.
useRolesshort-circuits the UI;authorize_admin_role!on the backend is the only thing that actually denies an action. - Self-health probes swallow exceptions. A failing probe returns
false; the page renders for the operator instead of crashing on an upstream outage — exactly the moment the page is most needed.
Outcomes
Support and moderation work can now be delegated to non-engineers. Admin sessions are throttled per identity, paginated, and observable in Sentry + PostHog. The self-health view is the operator's first stop on every alert page. The dashboard ships under feature parity for v1.3's super-admin surface while opening four roles' worth of new delegatable surface.
Tech Debt
- GDPR processing UI ships in v1.7 with a manual operator workflow — automated export pipelines are deferred to a future milestone.
- Sentry tracesSampleRate is 50% in admin (vs. 10% in app) — appropriate for low-traffic admin sessions but flagged for cost review.
Related Artifacts
- Milestones index: .planning/MILESTONES.md
- PRD: Admin Evolution PRD
- Merge PR: #329
Related Commits
4df5a6c0— Gsd/v1.7 admin evolution (#329)4fc9d6df— fix(admin_action_type): realign with v1.7 model (unblocks Dashboard + demo actions queries) (#336)
Last updated: 2026-05-22