Admin Evolution PRD — Operating a Hyper-Growth Platform with a Two-Person Team
Product: Objectuve (codename: Enkidu) Feature: admin_dashboard/ — evolution from launch-ready to scale-ready Status: Shipped — April 2026 Date: April 2026 Type: Infrastructure + Operations PRD Predecessor: Standalone Admin App PRD (Phase 0 largely shipped)
1. Context & Problem Statement
The standalone admin app exists. admin_dashboard/ is a working Vue 3 + Vite + Tailwind SPA with 11 functional views, Clerk auth, an AdminRole role model, an AdminAction audit log, Sentry + Lograge + PostHog telemetry, and an AI Workforce operations surface that already ships approved artifacts to GitHub, Mailtrap, and Slack. That investment — the "Phase 0 extraction" from the predecessor PRD — is done.
But the current tool was built for a pre-launch founder operating 0 users. Three things break the moment that founder opens it at 5K MAU, and all three break quietly — they degrade operator productivity and platform safety in ways that won't surface as errors until real damage has been done:
Everything paginates to nothing.
usersreturns.all.search_usershard-caps at 50.ai_employees,pending_artifacts,moderation_queue,feedback_statshave no cursor.admin_statsruns a 180-query uncached aggregate on every dashboard refresh. At 500 users each of these feels fine. At 5,000 they slow. At 50,000 they either timeout or lock the founder's Rails box while a check-in spike is landing.Roles exist in the database but not in the UI.
AdminRolehassuper_admin | admin | support | moderator. The backend uses them. The frontend does not read them, does not gate routes by them, and treats every authenticated admin as a super_admin. The first support hire will see the same impersonation buttons, hard-delete links, and role editor that the founder sees — and the backend will reject some of those calls with a generic GraphQL error that reads like a bug. A "moderator can't ban a user" conversation should be designed into the UI, not discovered at 11 PM.Compliance tooling is a rake task. There is no
gdpr_requeststable, no export wizard, no cascade-preview for hard-delete, no appeals queue, no webhook event log, no refund workflow, no feature flag UI. Every one of these is either a legal obligation the moment a user submits the form on our privacy page, or an operational need the moment a Stripe webhook fails. Today they would all be solved by a founder SSH'ing into the Rails container to run rake tasks or openStripe.api_key. That's a viable survival pattern. It is not a scaling pattern.
Separately, the admin app is operationally blind to its own behavior. It has no Sentry, no PostHog, no page-load tracking, no error boundary. When a support hire reports "the moderation queue didn't load," nobody can reconstruct what they did. When the GraphQL endpoint returns a 500 to the admin app because of a bad migration, the only signal is the support hire's screenshot.
The gap between what shipped and what a two-person team needs at 50K MAU is not large. It is specific. This PRD lists the specific augmentations, groups them into themes, phases them by the actual risk each unblocks, and explains why each one compounds into operator leverage rather than decoration.
What breaks if we don't close these gaps in 2026:
- The first 100-user weekend from a Product Hunt launch fills the moderation queue with no bulk-ops affordance. A two-hour triage session becomes a Saturday-long one.
- The first GDPR "delete my account" email creates 48 hours of panic code while we write a cascade-preview for
User + Goal + GoalEvent + Community + AiEmployeeMemory + AdminActiondeletions. - The first Stripe webhook failure takes out a Supporter subscription and the founder finds out via Discord message because there is no webhook log.
- The first moderator hire takes an hour of onboarding per week forever because nothing in the UI tells them which buttons they can actually press.
AdminStatsServicereturns 20-second response times around 30K MAU, and the dashboard stops loading for anyone.
What this PRD is not about: rebuilding anything that already works. Dashboard, AI Workforce, Review Queue, Moderation, Analytics, Monitoring, Feedback are all functional. The evolution layers on top — pagination, RBAC enforcement, compliance wizards, observability, scale-readiness — without disturbing shipped surfaces.
2. Current State Audit
Audit performed against commit 84b4e4676 (2026-04-16). Evidence paths linked inline.
2.1 What exists in the admin surface
| Area | Status | Evidence |
|---|---|---|
| Standalone Vue 3 + Vite + Tailwind app | ✅ Built | admin_dashboard/ |
| 11 routes implemented, no empty shells | ✅ Built | Dashboard, Users, Moderation, Analytics, Monitoring, Feedback, AI Workforce, Employee Detail, Run Detail, Review Queue, Sign-In |
| Clerk OAuth + fallback token | ✅ Built | admin_dashboard/src/views/SignIn.vue |
AdminRole model (4 roles) | ✅ Built | rails_api/app/models/admin_role.rb |
AdminAction audit log | ✅ Built | rails_api/app/models/admin_action.rb, rails_api/app/interactions/admin/logged.rb |
| AI Workforce ops surface | ✅ Built | admin_dashboard/src/views/AIWorkforceView.vue, admin_dashboard/src/views/ReviewQueueView.vue |
Backend search_users via ILIKE | ✅ Built (limited) | rails_api/app/interactions/admin/user_search.rb |
admin_stats service | ✅ Built (uncached) | rails_api/app/services/admin_stats_service.rb |
| AI usage cost breakdown (per-feature, per-model, daily) | ✅ Built | rails_api/app/services/admin_ai_usage_service.rb |
| Health endpoint (DB, Redis, Sidekiq, LiteLLM) | ✅ Built | rails_api/app/controllers/health_controller.rb |
| Sentry (backend + Ionic frontend) | ✅ Built | rails_api/config/initializers/sentry.rb, ionic_frontend/src/sentry.ts |
| PostHog (Ionic frontend only) | ✅ Built | ionic_frontend/src/posthog.ts |
| Lograge structured JSON logs | ✅ Built | rails_api/config/environments/production.rb:43-65 |
| Slack budget alert for AI | ✅ Built | rails_api/app/jobs/ai_metrics/ai_budget_alert_job.rb |
| Crono scheduled jobs | ✅ Built | rails_api/config/cronotab.rb |
| Incident response runbook | ✅ Built | docs/operations/incident-response.md, docs/operations/ai-runbook.md |
2.2 Gaps classified by severity
P0 — Will break at 5K-10K MAU (launch quarter):
| Gap | Evidence | Consequence |
|---|---|---|
| No pagination on any admin query | users, search_users capped at 50, ai_employees, pending_artifacts, moderation_queue, admin_actions in rails_api/app/graphql/types/query_type.rb | At 5K users, user search returns truncated; at 50K it times out |
AdminStatsService has no cache | rails_api/app/services/admin_stats_service.rb — 180 queries per request | Dashboard page load regresses linearly with MAU |
| No RBAC enforcement in frontend router | admin_dashboard/src/router.ts has no role guards | Moderator hire sees buttons they cannot use |
| No GDPR request queue or wizards | No gdpr_requests table, no export/delete interactions | First right-to-be-forgotten = panic rake task |
| No bulk moderation actions | review_content_flag is single-item only | Weekend traffic spike overwhelms one-at-a-time triage |
| Admin app has zero observability | No @sentry/vue in admin_dashboard/package.json | Support-hire errors are invisible |
P1 — Will break at 25K-50K MAU (year one):
| Gap | Evidence | Consequence |
|---|---|---|
| No Stripe webhook event log or replay | rails_api/app/controllers/webhooks/stripe_controller.rb logs to Rails logger only | First webhook failure = silent subscription corruption |
| No refund workflow | StripeService has no refund method | Refunds happen via Stripe Dashboard, not logged to AdminAction |
| No unified support inbox | Feedback posts, content reports, AI artifact rejections, appeals are separate surfaces | Operator alt-tabs through 4 views per ticket |
| No user 360° view | users/:id view doesn't exist in admin_dashboard | Diagnosing a user complaint means 6 GraphQL queries manually |
| No impersonation ("view as user") | No interaction, no banner component | Support cannot reproduce user's bug state |
| No feature flag UI | Flags live in config/settings/*.yml, require deploy to flip | Kill-switch takes 8+ minutes, not 8 seconds |
| No soft-delete restoration UI | acts_as_paranoid models (Goal, Community, GoalEvent, FeedbackPost, GoalMedia, UserDevice) all recoverable via rake only | User "I deleted by accident" = rake task |
| No Sidekiq web UI mounted | rails_api/config/routes.rb has no mount Sidekiq::Web | Job failures only visible via health endpoint stats |
| No command palette / global search | Layout has sidebar only, no Cmd+K | Every navigation is a click path |
| No breadcrumbs / env badge / unread counts | admin_dashboard/src/layouts/AdminLayout.vue | Production-vs-staging disaster risk |
| No pattern detection (repeat offender, flag rate spike) | No analytics over content_flags by user or time window | Abusive user creates 20 flags before any human notices |
P2 — Will limit the team to ~3 operators (year two):
| Gap | Evidence | Consequence |
|---|---|---|
| No saved searches / segments | No persistence on user filters | Every investigation rebuilds the query |
| No cohort retention / activation funnel surfaces | Not in admin_stats_type.rb | Growth decisions rely on PostHog, not admin data |
| No per-user AI cost attribution | AiUsageEvent lacks user_id filter in admin surface | Cannot investigate "why is this user costing $4/mo" |
| No webhook replay or retry visibility | Ingress webhooks (Stripe, AI Workforce, Mailtrap) do not persist events | Failed deliveries are invisible post-retry exhaustion |
| No scheduled digest to operator | Operator learns of queue depth by opening the app | Queue depth surprises on Monday morning |
| No break-glass / dual-control for super_admin ops | Only Clerk step-up is documented, not implemented | Single compromised admin = full blast radius |
| No E2E tests in admin_dashboard | admin_dashboard/tests/ is unit-only | Regressions from monorepo refactors not caught |
| No Cloud SQL read replica for admin queries | Config not in place | Analytics queries compete with user traffic at 50K+ |
3. Goals & Success Metrics
Primary goals
- Keep admin time-per-operator bounded at ≤ 45 min/day at 50K MAU. The anti-social philosophy applies to operators too. If the tool demands more, the tool is wrong.
- Eliminate "rake task panic mode" — any compliance, billing, or user-support action that today requires SSH must have a UI button by public launch (Sep 2026).
- Make roles real — moderator and support hires see only what they can use, with RBAC enforced at both router and mutation layers.
- Make the admin app observable to itself — Sentry + PostHog parity with the Ionic frontend, plus first-class tracking of admin actions as product events.
- Cap admin query performance degradation — p95 page load ≤ 1.5s through 100K MAU via pagination, caching, and (at 50K) a read replica.
Success metrics
Metrics are operator-efficiency and platform-health, never admin-engagement. Tracked quarterly.
| Metric | Today (Apr 2026) | Q4 2026 (10K MAU) | Q2 2027 (50K MAU) | Q4 2027 (150K MAU) |
|---|---|---|---|---|
| Operator minutes / 1K MAU / day | ~15 (estimated) | ≤ 12 | ≤ 6 | ≤ 3 |
| Time to triage one flag (p50) | ~90 s | ≤ 60 s | ≤ 30 s | ≤ 20 s |
| Time to run GDPR export (p95) | "panic rake task" | ≤ 2 h | ≤ 30 min | ≤ 10 min |
| Time to flip a feature flag | 8+ min (deploy) | ≤ 30 s (UI) | ≤ 10 s | ≤ 10 s |
% admin actions logged to AdminAction | ~60% (role ops only) | 100% | 100% | 100% |
| % admin actions visible to operator within 24 h | — | 100% | 100% | 100% |
| Dashboard p95 page load | ~2-3 s (uncached) | ≤ 1.5 s | ≤ 1.5 s | ≤ 1.5 s |
| Moderator onboarding time (to productive) | ~2 h (founder walkthrough) | ≤ 45 min | ≤ 30 min | ≤ 20 min |
| % destructive actions with typed confirmation | 0% | 100% | 100% | 100% |
| % destructive actions with 2FA / step-up | 0% | ≥ 50% (super_admin only) | 100% super_admin | 100% super_admin |
| Distinct active admin roles in production | 1 | 3 (super, mod, support) | 5 (add billing, read_only) | 6 (add team_admin) |
| Admin app has Sentry coverage | ❌ | ✅ | ✅ | ✅ |
| Stripe webhook events searchable last 30d | ❌ | ✅ | ✅ | ✅ |
| Admin runs against read replica | ❌ | ❌ | ✅ | ✅ |
Guardrails
- No admin action goes unlogged. Every mutation wraps
Admin::Logged. This is a code-review rule and a model validation. - No admin mutation runs without RBAC check. Every resolver calls
authorize_admin_role!(:role). Frontend gates are UX; backend gates are truth. - No admin query degrades user traffic. Any query touching > 1K rows must be paginated or cached or (phase 3) on the read replica.
- No admin bulk action without typed confirmation. Select-N + delete is the single worst failure mode; always require typing the count or an identifier.
- Operator telemetry is ops-only. PostHog events from admin origin go to a separate project group; they do not pollute user funnel analytics.
4. Strategic Rationale
Why evolve now, not at launch
The current admin is "pre-launch-founder-grade." It was designed for 0-500 users and a single operator. Public launch lands between June and August 2026, and post-launch timeline has three pivots that tighten the schedule:
- Public launch → 500-2K MAU in the first month. Moderation volume, support email, and GDPR requests all arrive at once. Pagination and bulk actions are no longer optional on day 7.
- First support/moderator hire in Q4 2026. Delegating a single button to a non-founder requires RBAC that is enforced in the UI, not just the backend. Making a hire productive in under a week requires the UI to hide what they cannot use.
- Phase 7 Teams launch April-June 2027. Billing console and team-scoped admin are prerequisites. Without a refund workflow and webhook log, Teams goes live with operator risk proportional to MRR.
Every month of delay compounds — founder time is spent on rake tasks, support productivity is capped, compliance risk grows.
Why this is an evolution, not a rewrite
The hard work is done. admin_dashboard/ works. AdminRole + AdminAction work. AI Workforce works. The remaining gaps are additive: add pagination, add wizards, add RBAC guards, add observability packages, add caching. No architectural rewrite is required.
Phasing is therefore not "stand up the shell and port features" (the predecessor PRD's plan) but "fill specific named gaps in the order that each gap starts hurting."
Why observability for the admin app itself is non-negotiable
A tool for diagnosing user problems that cannot diagnose its own problems is a tool that costs operator time to investigate instead of saving it. Sentry + PostHog parity with ionic_frontend adds ~100 lines of code and costs < $5/mo at current scale. It pays back the first time a support hire reports a mystery error.
Why AI Workforce is the model for automation
The AI Workforce implementation proves the operator-leverage thesis in miniature. Five AI employees take scheduled runs, produce reviewable artifacts, and ship them to GitHub/Mailtrap/Slack after human approval. The admin's role is supervision, not doing the work. Every augmentation in this PRD should be framed the same way: the admin app should be the supervision surface for automations, not the place where humans do repetitive work.
Why not buy Retool or Forest Admin
Three reasons:
- The business logic lives in the
Interactionpattern. Retool/Forest wrap tables and expose columns; they don't know aboutAdmin::Logged,acts_as_paranoid, orPublicRecord. Replicating our audit + soft-delete + public_id conventions in a third-party tool is the same effort as extending what we have. - PBC transparency obligations require custom surfaces (impersonation banner, PBC impact report). Retool cannot build those.
- Admin mutations must hit the same GraphQL endpoint with the same authorization rails. Adding a REST escape hatch for Retool would break the security boundary.
The bespoke admin is cheaper at every horizon the PRD contemplates.
5. Proposed Evolution Scope (Module-by-Module)
Each subsection lists: what exists, the gap, the proposed evolution. Where a feature already has a predecessor PRD section, it's referenced rather than re-specified.
5.1 Scale-Readiness Foundation (universal prerequisites)
Exists: Backend enforces admin. Queries return full result sets or short hardcoded limits.
Gap: Nothing paginates. AdminStatsService runs uncached. No read replica. No rate limit on admin queries.
Evolution:
- Cursor pagination on every list query:
users,search_users,ai_employees,pending_artifacts,moderation_queue,admin_actions,feedback_stats.top_posts. Pattern:(first, after)argument, returns{ edges, pageInfo { endCursor, hasNextPage } }. Usecreated_at + idtiebreaker (matchesGoalEventfeed pattern). - Fragment caching in
AdminStatsServiceusingRails.cache.fetch("admin_stats:#{days_back}", expires_in: 10.minutes)— stops the 180-query-per-request pattern immediately. Invalidate cache on new signup (optional; 10-min staleness is acceptable for operator dashboards). pg_trgmindexes onusers.email,users.username,users.display_name(migration already specified in predecessor PRD §6c, never applied).- Rack::Attack rules for admin origin: 120 queries/min per admin user, 30 mutations/min.
- Read replica for admin analytics (phase 3, 50K MAU trigger). Rails multi-DB
connected_to(role: :reading)middleware on GraphQL requests with admin origin header. - Background export: any admin query likely to scan > 10K rows (e.g., full audit log export) produces an
AdminExportrecord and enqueues a Sidekiq job that writes to GCS with a signed URL.
5.2 RBAC Frontend Enforcement
Exists: AdminRole model. Backend authorize_admin!. search_users returns admin, adminRoles fields on User.
Gap: Frontend treats all admins as super_admin. No route guards. No button-level role checks.
Evolution:
useRoles()composable reading the signed-in admin'sadminRolesfrom a cachedmequery; exposeshasRole(role),canPerform(action).- Route guards in
admin_dashboard/src/router.tsusing ameta: { requiresRole: ['moderator', 'admin'] }pattern; fallback view renders "Not authorized" with a contact path. - Sidebar nav filtering — items the current role cannot access are hidden, not disabled.
- Button-level guards — destructive actions show disabled state with tooltip "requires super_admin" if the role lacks permission, rather than a GraphQL error.
- Backend helper rename —
authorize_admin_role!(role_symbol)inQueryType/MutationTypethat checks specific roles, not the genericadmin?. Keeprequire_admin!as a derivedany_admin_role?shim. - Role badge in topbar — admin sees their current role in the header at all times, avoiding "wait, which account am I signed in as" mistakes during dual-role debugging.
5.3 Observability-for-the-Admin-App
Exists: Backend Sentry, frontend Sentry on Ionic app, PostHog on Ionic app, Lograge on Rails.
Gap: Admin app has none of these. Errors invisible, navigation untracked, performance unmeasured.
Evolution:
@sentry/vuein admin_dashboard with a dedicated Sentry project (objectuve-admin). Vue router integration,browserTracingIntegrationat 50% sample rate (admin traffic is low-volume), replay on errors at 100% sample.- PostHog with admin property tag — same API key as Ionic is acceptable, but all events are tagged
surface: "admin"so they can be filtered out of user funnels. Track page views, critical button clicks (approve artifact, ban user, grant role), and timing metrics for the six heaviest views. - Admin app error boundary — a top-level
<ErrorBoundary>component that captures render errors, logs to Sentry, and shows a "something broke, try again" fallback with a Sentry event ID for the operator to quote. - Apollo error link logs every GraphQL error to Sentry with the operation name as tag and the admin user ID as user context. Today errors are
console.warn()only. - Frontend RUM — page load timing, Apollo query latency (p50/p95), cache hit ratio in a
/system/healthview in the admin itself. - Admin-action-to-PostHog bridge — every successful
AdminActionrow triggers a PostHog event (backend side, not frontend). This gives a unified "what admins did" timeline searchable by actor, action, target in PostHog without needing a dedicated admin-search UI for audit queries.
5.4 Compliance: GDPR + Privacy + Appeals
Exists: User has acts_as_paranoid soft-delete. That is the totality of GDPR tooling.
Gap: No request queue, no export wizard, no cascade-preview for hard-delete, no PII redaction policy for audit logs, no appeals surface.
Evolution (direct references to predecessor PRD §5k, §6e where aligned):
gdpr_requeststable withrequest_type ∈ {export, deletion, rectification},status ∈ {received, processing, fulfilled, failed},due_by,export_file_url,fulfilled_by.- GDPR intake endpoint on user-facing app —
/privacy/requestposts agdpr_requestrecord. Rails confirms receipt within 72h (EU requirement). - Export wizard in admin — reviews request, shows all tables containing the user's data (pre-scanned list), generates signed GCS URL for JSON+CSV bundle, auto-expires in 30 days, logs to
AdminAction. - Deletion wizard — shows cascade preview (which records delete, which soft-delete, which PII-redact in retained audit logs), requires typed email confirmation + super_admin step-up, runs in a transaction with a transaction-level audit note.
- Post-deletion audit handling —
AdminActionrecords touching a hard-deleted user retaintarget_user_hash(SHA256 of email) and strip PII frombefore_state/after_state. This preserves operator accountability without retaining user PII (predecessor PRD §9.8). - Appeals queue — a user-facing surface for appealing a moderation decision posts an
ContentAppealrecord. Admin appeals view is a filtered variant of the moderation queue.
5.5 Pagination + Filters + Bulk for Moderation
Exists: moderation_queue returns pending flags + reports limited to 50 each. review_content_flag / review_content_report operate on single items.
Gap: No filters (severity, age, flagged user, content type). No bulk actions. No pattern detection. No appeals.
Evolution:
- Filter bar on
admin_dashboard/src/views/ModerationView.vue: severity, source (profanity_filter/ai_screen/user_report), content type, age bucket, flagged user. Filters sync to URL params for shareability. bulk_review_content_flags(flag_ids, action, reason)mutation — processes up to 50 flags atomically, each generating its ownAdminAction. Typed confirmation required for > 20.- Pattern detection view (
/moderation/patterns) — top N users by flag count in a rolling 7-day window; chart of flags/day; ability to open a "user flag cluster" detail that shows every flag against or from that user. - Moderation rules (predecessor PRD §6d) —
ModerationRuletable withpattern_type ∈ {regex, substring, ai_score_threshold}, action, severity. Editable via admin UI; applied by existing moderation interactions. - Appeals —
ContentAppealmodel; appeals queue is a tab on ModerationView.
5.6 User Management — 360° View, Impersonation, Lifecycle
Exists: SEARCH_USERS query, UPDATE_USER_ROLES mutation, admin_dashboard/src/views/UsersView.vue with role editor modal.
Gap: No user detail page. No timeline. No impersonation. No ban / unban / soft-delete / restore. No subscription panel.
Evolution:
/users/:iddetail view in admin, with tabs:- Profile — basics, Clerk link, signup source, device count, last active.
- Activity Timeline — merged stream of goals, events, mood logs, AI interactions, flags,
AdminActionentries targeting this user. Paginated. - AI History — per-user Coach interaction log with cost attribution (depends on 5.7).
- Flags & Reports — flags filed against them, flags they filed.
- Subscription — plan, Stripe customer link, last payment, supporter_until, refund history (depends on 5.8).
- Roles — current admin roles, grant/revoke form with reason (super_admin only).
- Audit — every
AdminActionwhere this user was target.
ban_user(user_id, reason, duration_days),unban_user(user_id, reason),soft_delete_user,restore_user,hard_delete_user(user_id, gdpr_request_id, typed_confirmation)interactions, all wrappingAdmin::Logged.- Impersonation —
start_impersonation(user_id, reason)mints a short-lived impersonation JWT, returns an app.objectuve.com URL in a new tab. User-side banner "an operator is viewing as you" (PBC transparency). Audit entries on start and end. Read-only in phase 2; write-enabled in phase 4 for support flows (predecessor PRD §9.7). - Soft-delete restoration UI — one-click restore for every
acts_as_paranoidmodel from the user detail view, with reason required.
5.7 AI Workforce — From Shipped to Scale
Exists: Full shipped PRD. Five AI employees, approval workflow, artifact delivery to GitHub / Mailtrap / Slack, edit-distance tracking, autonomy promotion gates, monthly budget alerts.
Gap: No pagination. No filters on employee list or pending artifacts. No per-user or per-artifact cost attribution. No kill-switch UI. No prompt version comparison. No employee "retirement" / archive flow.
Evolution:
- Pagination + filters on
ai_employees(by status, autonomy, role_key) andpending_artifacts(by employee, kind, age). - Per-artifact cost surfacing in
RunDetailView(today it's per-run only) — shows tokens and cents per artifact within the run, useful for expensive multi-artifact runs. - Per-user Coach cost view at
/users/:id/ai-history— sumsAiUsageEventby user, filtered by feature; flags users whose monthly AI cost exceeds a configurable outlier threshold. - Kill-switch UI —
/ai-ops/kill-switchespage with toggles forai.coaching_enabled,ai.moderation_enabled,ai.batch_enabled,ai_workforce.runs_enabled. Wire to either aFeatureFlagmodel (see 5.9) or direct Settings write with audit. Currently requires a deploy to flip (AI runbook). - Prompt version diff —
coach_prompt_versionsquery returning prompt template history per feature; side-by-side diff viewer in UI. - Employee archive — mutation to retire an employee (sets
active=falsepermanently, preserves history, removes from default views).
5.8 Billing & Stripe Admin
Exists: StripeService with checkout, subscription cancel, portal URL. PaymentRecord model. Stripe webhook controller logs to Rails logger.
Gap: No webhook event log persisted. No replay. No refund workflow. No subscription inspection in admin. No MRR/churn dashboard. No manual reconciliation tool.
Evolution:
stripe_eventstable —stripe_event_id, type, payload jsonb, received_at, processed_at, status, error. Webhook controller persists every event before processing. Unique onstripe_event_id.- Webhook event log view at
/billing/webhooks— paginated, filterable by type + status; detail view shows raw payload, processing result, replay button. Replay is areplay_stripe_event(event_id, reason)mutation that re-invokes the processor. - Subscription list + detail —
billing_overview(period)(MRR, active subs, churn),subscription_detail(user_id)returning current + historical subscription state. - Refund workflow —
create_refund(subscription_id, amount_cents, reason, typed_confirmation)interaction. CallsStripe::Refund.create. Refunds > $100 require step-up. Logs toAdminAction. - Reconciliation tool —
reconcile_stripe_subscription(id, reason)compares Stripe state to localPaymentRecord/User.supporter_untiland corrects drift with audit entry.
5.9 Feature Flags (Runtime)
Exists: Settings.ai.coaching_enabled style flags in config/settings/*.yml, flipped by commit + CI deploy.
Gap: No runtime override. No audit. No per-user targeting. Deploy-to-flip cycle is 8+ minutes.
Evolution:
feature_flagstable —key, enabled, conditions jsonb (user IDs, roles, cohorts), updated_by, reason, updated_at.FeatureFlag.active_for?(key, user)helper that first checks DB (cache-backed) then falls through toSettings. Gives runtime override with Settings as compile-time default.- Admin UI at
/system/flags— list, toggle, edit conditions, log every change toAdminAction. Typed confirmation for kill-switch-category flags. Step-up forsuper_admin_requiredflags. - Change event bus — on flag flip, broadcast to Sidekiq to invalidate caches.
5.10 Support Inbox (Unified Triage)
Exists: Feedback posts (with status enum), content reports, AI artifact rejections are separate views.
Gap: Operator must rotate between views. No concept of "assignment." No reply templates. No ticket status beyond the source-specific enum.
Evolution:
support_ticketstable (predecessor PRD §6f) withsource,source_id,status,priority,assignee_id.- Ingestion — feedback posts, content reports, appeals, GDPR requests, direct-email (phase 3) auto-create tickets.
- Unified inbox view at
/support— paginated, filterable by assignee + status + source + priority. Detail view shows source context inline. - Assignment + status mutations —
assign_ticket(id, assignee_id),update_ticket_status(id, status, reason),reply_to_ticket(id, template_id?, body). - Reply templates —
support_templatesCRUD. Start with 5-10 templates for the most common issues.
5.11 System Operations
Exists: /health endpoint. Crono jobs in code. Sidekiq stats in health response.
Gap: No Sidekiq web. No Crono status view. No deploy history. No environment-variable inspector (redacted).
Evolution:
- Mount Sidekiq web at
/admin_api/sidekiqwith same admin JWT gate. Or extract stats to GraphQL (sidekiq_stats,sidekiq_dead_jobs,sidekiq_retry_set) for a Vue-native view. crono_jobsquery — returns each job's last run, next run, last status, last duration.trigger_crono_job(job_id)for on-demand runs (super_admin only)./system/deploysview — reads Cloud Run revision history via GCP API (service account withrun.revisions.list). Shows current + previous + rollback trigger button. Rollback requires step-up./system/envview — environment variable viewer, values redacted by regex (/key|secret|token|password|dsn/i→[REDACTED — ${length} chars]). Read-only.- Slow query surface — enable
pg_stat_statementsextension, expose top-20 queries by mean_exec_time in/system/queries.
5.12 Analytics — Cohorts, Funnels, North-Star
Exists: admin_stats returns top-level counts + daily growth + goal category breakdown. Ionic frontend has PostHog.
Gap: No cohort retention. No activation funnel computed server-side. No PBC impact report generator. No north-star metric overlay on the dashboard.
Evolution:
- North-star card on dashboard home — cumulative goals completed trending toward the 2.5K → 1.5M → 10M overlay. First card operator sees each morning.
cohort_retention(cohort_month, resolution, weeks)— returns D1/D7/D30/D90 retention for a signup cohort, materialized nightly via Crono into acohort_metricstable.activation_funnel(cohort_month)— steps: signup → sync_user → first goal → first goal event → first streak → first supporter conversion. Materialized nightly.pbc_impact_report(year)— year-in-review report with goals completed, unique users benefited, community-goal weeks achieved, moderation transparency metrics.- CSV export on every analytics surface (via
AdminExportpattern from 5.1).
5.13 Layout / Navigation Improvements
Exists: Sidebar + topbar with Clerk avatar. Responsive.
Gap: No breadcrumbs. No env badge. No unread counters. No Cmd+K. No notification bell.
Evolution:
- Environment badge in topbar — "production" (red), "staging" (yellow), "development" (gray). Driven by
VITE_ENV. - Unread counters on sidebar items — moderation queue, review queue, support inbox. Driven by poll-every-30s
pending_countsquery (single query, not N). - Cmd+K command palette — global search across users, communities, flags, tickets; quick navigation; quick actions. Modeled on Linear.
- Breadcrumb component — derived from router metadata.
- Notification bell — triggered by
NotifyOperatorJobpushing to aoperator_notificationstable (kill-switch events, new hard-delete, large refund, failing webhook, etc.). PostHog event emitted for every notification fired.
6. Data Model Changes
New migrations, in the order they'd be applied:
# gdpr_requests — 5.4
create_table :gdpr_requests do |t|
t.references :user, foreign_key: true
t.string :request_type, null: false # export | deletion | rectification
t.string :status, null: false, default: 'received'
t.string :requestor_email, null: false
t.datetime :received_at, null: false
t.datetime :due_by, null: false
t.datetime :fulfilled_at
t.references :fulfilled_by, foreign_key: { to_table: :users }
t.string :export_file_url
t.datetime :export_expires_at
t.text :notes
t.timestamps
end
# stripe_events — 5.8
create_table :stripe_events do |t|
t.string :stripe_event_id, null: false, index: { unique: true }
t.string :event_type, null: false, index: true
t.jsonb :payload, null: false
t.string :status, null: false, default: 'received' # received | processing | processed | failed
t.datetime :received_at, null: false
t.datetime :processed_at
t.text :error
t.integer :replay_count, default: 0
t.timestamps
end
# feature_flags — 5.9
create_table :feature_flags do |t|
t.string :key, null: false, index: { unique: true }
t.boolean :enabled, null: false, default: false
t.jsonb :conditions, default: {}
t.references :updated_by, foreign_key: { to_table: :users }
t.text :reason
t.timestamps
end
# support_tickets — 5.10
create_table :support_tickets do |t|
t.references :user, foreign_key: true
t.references :assignee, foreign_key: { to_table: :users }
t.string :source, null: false # feedback_post | content_report | appeal | gdpr_request | direct
t.string :source_type
t.bigint :source_id
t.string :status, null: false, default: 'new' # new | in_progress | waiting_user | resolved
t.integer :priority, default: 3 # 1-5
t.text :last_message
t.datetime :resolved_at
t.timestamps
end
# content_appeals — 5.5
create_table :content_appeals do |t|
t.references :user, null: false, foreign_key: true
t.references :original_flag, foreign_key: { to_table: :content_flags }
t.references :original_report, foreign_key: { to_table: :content_reports }
t.text :appeal_reason, null: false
t.string :status, null: false, default: 'pending'
t.references :reviewed_by, foreign_key: { to_table: :users }
t.datetime :reviewed_at
t.text :review_notes
t.timestamps
end
# moderation_rules — 5.5
create_table :moderation_rules do |t|
t.string :name, null: false
t.string :pattern_type, null: false # regex | substring | ai_score_threshold
t.text :pattern
t.string :action, null: false # flag | auto_reject | notify
t.string :severity, null: false
t.references :created_by, foreign_key: { to_table: :users }
t.boolean :enabled, default: true
t.timestamps
end
# cohort_metrics — 5.12
create_table :cohort_metrics do |t|
t.date :cohort_month, null: false, index: true
t.integer :day_offset, null: false
t.integer :users_active
t.integer :users_retained
t.float :retention_rate
t.datetime :computed_at, null: false
end
# admin_exports — 5.1
create_table :admin_exports do |t|
t.references :admin_user, null: false, foreign_key: { to_table: :users }
t.string :export_type, null: false
t.jsonb :parameters, default: {}
t.string :status, null: false, default: 'queued'
t.string :file_url
t.datetime :file_expires_at
t.integer :row_count
t.datetime :completed_at
t.text :error
t.timestamps
end
# operator_notifications — 5.13
create_table :operator_notifications do |t|
t.references :admin_user, null: false, foreign_key: { to_table: :users }
t.string :category, null: false
t.string :severity, null: false # info | warning | critical
t.string :title, null: false
t.text :body
t.string :action_url
t.datetime :read_at
t.timestamps
end
# pg_trgm indexes — 5.1
enable_extension "pg_trgm"
add_index :users, "lower(email) gin_trgm_ops", using: :gin
add_index :users, "lower(username) gin_trgm_ops", using: :gin
add_index :users, "lower(display_name) gin_trgm_ops", using: :gin
# pg_stat_statements extension — 5.11
enable_extension "pg_stat_statements"Schema-level shims:
AdminAction.before_state/after_state— add application-level PII-scrub helper on User deletion (keeps column, sanitizes).User.adminboolean — derived methoddef admin?; admin_roles.active.exists?; endalready in place; keep as deprecated shim.AiUsageEvent— adduser_idindex for per-user cost attribution (5.7).
7. Phased Implementation
Phasing prioritizes each gap by when it starts to hurt, not by module coherence.
Phase A — Launch-Quarter Hardening (May-Jun 2026, 3-4 weeks)
Goal: Do not ship public launch without these.
- 5.1 Scale foundation: pagination on all 6 core queries,
AdminStatsServicecaching,pg_trgmindexes, Rack::Attack. - 5.2 RBAC frontend:
useRoles(), router guards, sidebar filtering, role badge. - 5.3 Observability for admin: Sentry + PostHog + error boundary in admin_dashboard, Apollo error link, admin-action-to-PostHog bridge.
- 5.4 GDPR minimum:
gdpr_requeststable, export wizard, deletion wizard, PII-scrub on audit log. - 5.5 Moderation bulk + filters (no pattern detection or rules yet).
- 5.13 Layout: env badge, unread counters, breadcrumbs.
Exit criterion: public launch proceeds without rake-task fallback for any supported user action; first moderator hire can be onboarded in < 1h.
Phase B — First-90-Days Scale (Jul-Sep 2026, 4 weeks)
Goal: Operate a 500-2K MAU platform with bulk support requests arriving.
- 5.6 User 360° view:
/users/:idwith all 6 tabs, ban/unban/soft-delete/restore mutations, impersonation (read-only). - 5.10 Support inbox:
support_ticketstable, unified view, assignment, 10 reply templates. - 5.11 System ops: Sidekiq web or stats query, Crono status, deploy history, env var viewer, slow query surface.
- 5.13 Cmd+K command palette. Notification bell.
Exit criterion: first support hire is productive in their scoped role without super_admin access.
Phase C — Teams Launch Readiness (Oct-Dec 2026, 4-5 weeks)
Goal: Billing console + feature flags + advanced moderation before Teams GA.
- 5.8 Billing:
stripe_eventslog, replay, refund workflow, reconciliation. - 5.9 Feature flags: runtime table, admin UI, kill-switch category.
- 5.5 Moderation patterns + rules.
- 5.12 Cohort retention + activation funnel (materialized).
- 5.7 AI Workforce: pagination + per-artifact cost + kill-switch UI.
Exit criterion: Teams launches with full billing and flag-management visibility.
Phase D — 50K MAU (Jan-Jun 2027)
Goal: Scale operational tax to stay bounded at ≤ 6 min / 1K MAU / day.
- 5.1 Cloud SQL read replica + admin traffic routed.
- 5.1 Background export for large data operations.
- 5.4 Appeals queue.
- 5.6 Impersonation write-enabled with confirmation.
- 5.12 PBC impact report generator v1.
- 5.7 Prompt version diff, employee archive.
- E2E test coverage for GDPR, refund, bulk moderation, impersonation flows in Cypress.
Exit criterion: admin surface handles 50K MAU with < 1.5s p95 page load and < 6 min operator time per 1K MAU per day.
Phase E — 100K+ MAU (Jul 2027-onward)
Goal: Automation — the admin supervises rather than performs.
- AI-assisted moderation (classifier inference + human confirmation; follows AI Workforce pattern).
- Anomaly detection (flag-rate spike, cost anomaly, retention cliff) surfaced as operator notifications.
- Federated SSO (SAML/OIDC) for enterprise team admins.
- IP allowlist for super_admin actions (Cloud Armor).
- Audit log archival to GCS cold storage after 90 days.
- Multi-region / EU data residency (if needed for GDPR scale).
8. Observability & Scale Readiness (Cross-Cutting)
This section is intentionally separate because observability + performance cut across every module.
8.1 Admin app observability baseline (Phase A exit)
- Sentry — dedicated
objectuve-adminproject, 50% traces, 100% replay on error, Vue router integration, user context = admin public_id + role list. - PostHog — admin events tagged
surface: "admin"in a separate project group; track page views, critical actions, operation latencies. - Every admin mutation emits a PostHog event via
Admin::Logged→PosthogNotifier.capture_admin_action. This is the unified audit-event stream across front and back. - Apollo error link — sends operation name, variables (PII-scrubbed), admin actor to Sentry.
- Operator notifications —
operator_notificationstable + sidebar bell + optional Slack webhook (phase B) forseverity=critical.
8.2 Scale targets and triggers
| Trigger | Action |
|---|---|
| Any list query returns > 200 rows | Add pagination in next sprint |
| Any service method > 1s p95 | Add cache in next sprint |
| Admin queries > 10% of total DB load | Add read replica |
| Audit log > 10GB | Archive to GCS cold storage |
| Sidekiq queue latency > 60s | Add Sidekiq worker concurrency |
| LiteLLM p95 > 2s | Investigate before next AI Workforce run |
8.3 Alerting evolution
Existing alerts (Sentry error-rate, GCP uptime, AI budget) stay. Add:
- Failed admin auth attempts — Sentry event when > 5 failures/10min from single IP.
hard_delete_useraction — Slack page to #ops.super_adminrole grant — Slack page to #ops.- Kill-switch flag flip — Slack page to #ops.
- Stripe webhook failure — Slack page if > 3 failures / 10 min.
- Admin app page load p95 > 3s — Sentry performance alert.
8.4 Runbook attachment
Every alert channel message embeds a link to the specific runbook section:
- Slack budget alert → ai-runbook.md#kill-switch
- Sentry error rate → incident-response.md#diagnosis-flow
- Stripe webhook failure → billing-runbook.md (not yet created)
- GDPR request received → gdpr-runbook.md (not yet created)
8.5 Admin app self-health view
A new /system/self-health page in the admin_dashboard that renders:
- Admin app build SHA + deploy timestamp
- GraphQL endpoint reachable (current latency)
- Sentry DSN valid + last event sent
- PostHog endpoint reachable + last event sent
- Clerk session valid + time-to-expiry
- Admin user's role + permissions (for debugging own access)
Single place to answer "is my tool broken or is my target broken."
9. Security Considerations
Inherits the predecessor PRD §10 security model. Additions specific to this evolution:
- Per-role rate limiting: Rack::Attack rules differ by role (moderator gets higher mutation rate, billing gets lower).
- Step-up on hard-delete / refund / role grant / kill-switch: Clerk step-up or a dedicated
stepUpTokenparameter validated server-side. - Dual-control for super_admin critical actions (phase E): hard-delete of another admin, role revoke of super_admin, deploy rollback. Two super_admins must co-sign within 5 minutes.
- Admin PostHog events PII-scrubbed: never send user email, password, session tokens, raw payload. Send public_ids + metadata keys.
- Impersonation JWT scope: includes
impersonated_byclaim; backend logs every mutation performed during impersonation toAdminActionwithimpersonation_context: {operator_id, session_id}. User-facing banner rendersimpersonated_byin plain text. - Feature flag changes are audited + irreversible without audit: flipping off a user-facing flag on a live platform is a production change; log every change with reason + actor.
10. Cost Analysis
Incremental to predecessor PRD cost baseline.
| Item | Phase A-B | Phase C-D | Phase E |
|---|---|---|---|
| Sentry admin project | free tier | free tier | ~$26/mo |
| PostHog admin events | free tier | < $10/mo | ~$50/mo |
| Postgres storage (new tables) | +200MB | +2GB | +10GB |
| Cloud SQL read replica | $0 | ~$40/mo (D-custom-2-7680) | ~$80/mo |
| GCS cold-storage audit archive | $0 | $0 | < $5/mo |
| Background export queue (GCS bandwidth) | $0 | < $5/mo | < $20/mo |
| Incremental total | < $10/mo | ~$60/mo | ~$180/mo |
Engineering cost (solo-with-Claude-pair):
- Phase A: 3-4 weeks
- Phase B: 4 weeks
- Phase C: 4-5 weeks
- Phase D: ongoing quarterly investment
- Phase E: driven by ML + compliance needs, bounded by ROI per feature
The full evolution through Phase D is 12-16 weeks of work spread across 9-12 months. Compared to the cost of not doing it (rake-task firefighting, operator burnout, compliance risk, support churn), the ratio is overwhelmingly positive.
11. Edge Cases & Open Questions
- GDPR deletion + AiEmployeeMemory. AI employees have memory records that may reference users. Before hard-delete, cascade-preview must include
AiEmployeeMemoryrows; scrub vs delete policy TBD — proposal: delete rather than scrub since these are not accountability records. - Impersonation + AI Workforce. Should an operator impersonating a user be able to trigger AI Workforce runs on their behalf? Phase D decision; propose read-only impersonation for AI surfaces.
- Feature flags vs Settings precedence. If
FeatureFlag('ai.coaching_enabled').enabled?conflicts withSettings.ai.coaching_enabled, which wins? Proposal: DB wins for safety (can always disable); Settings is the compile-time default and is used only when DB has no row. - Stripe event replay idempotency. Stripe webhook handler must be idempotent since replay will re-invoke it. Confirm existing
stripe_controller.rbis idempotent before enabling replay; if not, addprocessed_event_idsdeduplication. - PostHog admin events retention. PostHog charges by event volume. Admin events are low-volume but we should set a 90-day retention policy in a separate project to bound cost.
- Crono job "trigger now" race conditions. If an operator triggers
AiBudgetAlertJobmanually while its scheduled run is also firing, dedup is viaRails.cachealready. Confirm this extends to manual triggers too. - Read replica read-your-writes. Admin actions that immediately re-read their own effect need to bypass the replica. Implement via opt-in query flag:
.connected_to(role: :writing) { ... }on the read-your-writes queries. - Command palette search performance at 100K users. Fuzzy-search 100K users client-side is not viable. Palette must proxy to backend
searchUserswith debounced input. - Notification bell overflow. At high notification volume, bell UI needs mark-all-read, filtering, and a "yesterday" / "last week" grouping rather than an unbounded list.
- Admin app bundle size. Adding Sentry, PostHog, command palette, chart libraries will push bundle > 500KB gz. Mitigate via route-level lazy loading and dynamic imports for heavy modules (Storybook, chart.js already lazy).
12. Verification
How we know each phase succeeded:
Phase A verification:
- Run Lighthouse against each admin page: p95 load < 2s locally.
- Seed 100K test users, search for one by partial email; response < 200ms.
- Trigger
admin_stats100 times in a minute; no DB spike past baseline. - Sign in as a
moderatorrole user;/usersand/system/flagsrender "not authorized." Sidebar shows 6 items (dashboard, moderation, review queue, support, AI workforce, analytics); hides users, system, billing. - Fire a throw in
DashboardView.vue; error boundary renders fallback + Sentry event ID appears in admin Sentry project inbox. - Submit a test GDPR export request; export wizard produces a JSON bundle within 2 minutes; download link expires after 30 days.
- Select 20 flags in moderation; click bulk reject with reason; all 20 generate
AdminActionentries; each entry hasbefore_state+after_state+ reason.
Phase B verification:
- Impersonate a test user; banner renders "an operator is viewing as you" with operator's public_id; mutation attempts return 403 read-only error.
- Hire-simulation: a new user with
supportrole signs in; without founder's help, within 30 minutes they can: find a user by email, view their profile, respond to a support ticket from a template, mark it resolved. - Trigger Crono
AiBudgetAlertJobmanually from/system/crono; log entry appears within 5 seconds; result visible in admin without tailing Rails logs.
Phase C verification:
- Replay a Stripe webhook event via UI; local payment record updates correctly; replay counted in
stripe_events.replay_count. - Flip
ai.coaching_enabledoff via/system/flags; user Coach requests return kill-switch response within 10 seconds; audit log shows who and why. - Run cohort retention query for Jun 2026 cohort; D30 retention matches manual PostHog calculation within 2% tolerance.
Phase D verification:
- Read replica handles 90% of admin query volume; primary DB CPU does not spike during admin analytics loads.
- Run GDPR deletion of a test user; cascade preview matches actual row counts deleted/scrubbed;
AdminActionretainstarget_user_hashbut no PII. - Cypress E2E suite runs GDPR export → review → fulfill → expire pipeline without manual intervention.
13. Critical Files to Modify / Create
New Rails migrations (phased)
rails_api/db/migrate/{ts}_add_admin_query_trgm_indexes.rbrails_api/db/migrate/{ts}_create_gdpr_requests.rbrails_api/db/migrate/{ts}_create_stripe_events.rbrails_api/db/migrate/{ts}_create_feature_flags.rbrails_api/db/migrate/{ts}_create_support_tickets.rbrails_api/db/migrate/{ts}_create_content_appeals.rbrails_api/db/migrate/{ts}_create_moderation_rules.rbrails_api/db/migrate/{ts}_create_cohort_metrics.rbrails_api/db/migrate/{ts}_create_admin_exports.rbrails_api/db/migrate/{ts}_create_operator_notifications.rbrails_api/db/migrate/{ts}_enable_pg_stat_statements.rb
New Rails models
rails_api/app/models/gdpr_request.rbrails_api/app/models/stripe_event.rbrails_api/app/models/feature_flag.rbrails_api/app/models/support_ticket.rbrails_api/app/models/content_appeal.rbrails_api/app/models/moderation_rule.rbrails_api/app/models/cohort_metric.rbrails_api/app/models/admin_export.rbrails_api/app/models/operator_notification.rb
New Rails interactions (all wrapping Admin::Logged)
rails_api/app/interactions/admin/gdpr/export.rbrails_api/app/interactions/admin/gdpr/delete_user.rbrails_api/app/interactions/admin/ban_user.rbrails_api/app/interactions/admin/unban_user.rbrails_api/app/interactions/admin/impersonate_user.rbrails_api/app/interactions/admin/restore_soft_deleted.rbrails_api/app/interactions/admin/bulk_review_content_flags.rbrails_api/app/interactions/billing/create_refund.rbrails_api/app/interactions/billing/reconcile_subscription.rbrails_api/app/interactions/billing/replay_stripe_event.rbrails_api/app/interactions/admin/flags/set_feature_flag.rbrails_api/app/interactions/support/assign_ticket.rbrails_api/app/interactions/support/reply_to_ticket.rb
Rails files to modify
rails_api/app/graphql/types/query_type.rb— add pagination,authorize_admin_role!(role), 20+ new fields, replace all.allwith cursor queriesrails_api/app/graphql/types/mutation_type.rb— register all new mutationsrails_api/app/services/admin_stats_service.rb— cache layerrails_api/app/services/admin_ai_usage_service.rb— cache layer +user_idfilterrails_api/app/interactions/admin/logged.rb— emit PostHog event on successrails_api/app/controllers/webhooks/stripe_controller.rb— persist events tostripe_eventsbefore processingrails_api/config/initializers/sidekiq.rb— add dead-letter queuerails_api/config/initializers/rack_attack.rb— new file, admin rate rulesrails_api/config/cronotab.rb— add nightlyAnalytics::MaterializeCohortsJob,Admin::ArchiveOldAuditsJobrails_api/config/routes.rb— mount Sidekiq web with admin JWT gate (optional) or expose via GraphQL
New admin_dashboard files
admin_dashboard/src/views/UserDetailView.vueadmin_dashboard/src/views/gdpr/GdprQueueView.vueadmin_dashboard/src/views/gdpr/GdprExportWizard.vueadmin_dashboard/src/views/gdpr/GdprDeletionWizard.vueadmin_dashboard/src/views/billing/BillingOverview.vueadmin_dashboard/src/views/billing/WebhookEventsView.vueadmin_dashboard/src/views/billing/RefundView.vueadmin_dashboard/src/views/system/FeatureFlagsView.vueadmin_dashboard/src/views/system/DeployHistoryView.vueadmin_dashboard/src/views/system/EnvVarsView.vueadmin_dashboard/src/views/system/CronoStatusView.vueadmin_dashboard/src/views/system/SlowQueriesView.vueadmin_dashboard/src/views/system/SelfHealthView.vueadmin_dashboard/src/views/support/SupportInboxView.vueadmin_dashboard/src/views/analytics/NorthStarView.vueadmin_dashboard/src/views/analytics/CohortRetentionView.vueadmin_dashboard/src/views/moderation/PatternsView.vueadmin_dashboard/src/composables/useRoles.tsadmin_dashboard/src/composables/usePendingCounts.tsadmin_dashboard/src/composables/useOperatorNotifications.tsadmin_dashboard/src/components/layout/CommandPalette.vueadmin_dashboard/src/components/layout/EnvironmentBadge.vueadmin_dashboard/src/components/layout/NotificationBell.vueadmin_dashboard/src/components/layout/Breadcrumbs.vueadmin_dashboard/src/components/ErrorBoundary.vueadmin_dashboard/src/sentry.ts— dedicated admin Sentry initadmin_dashboard/src/posthog.ts— admin-tagged PostHog init
admin_dashboard files to modify
admin_dashboard/src/router.ts— addmeta.requiresRole, guards usinguseRoles()admin_dashboard/src/layouts/AdminLayout.vue— env badge, unread counters, notification bell, role badge, breadcrumbsadmin_dashboard/src/apollo-client.ts— error link to Sentryadmin_dashboard/src/views/UsersView.vue— cursor pagination, link to/users/:idadmin_dashboard/src/views/ModerationView.vue— filter bar, bulk-select, bulk-action buttonadmin_dashboard/src/main.ts— mount Sentry, PostHog, ErrorBoundaryadmin_dashboard/package.json— add@sentry/vue,posthog-js
Documentation
docs/operations/billing-runbook.md(new)docs/operations/gdpr-runbook.md(new)docs/operations/rbac-roles.md(new) — canonical role definitionsdocs/features/admin-dashboard.md(update) — reflect new capabilitiesdocs/product/standalone-admin-app-prd.md(update) — mark Phase 0 as "shipped, see admin-evolution-prd.md"docs/product/prd-index.md(update) — add this PRDCLAUDE.md— add env vars for admin Sentry DSN and admin PostHog key
14. How This PRD Ladders Up to the North Star
Per docs/product/north-star.md, the metric is cumulative goals completed. This PRD does not create goal completions directly. It protects them:
- Operator time saved → founder time preserved for product work. Every minute the founder spends in moderation bulk-review instead of per-flag triage is a minute spent shipping the next retention feature.
- Compliance reliability → trust preserved. A user whose GDPR deletion takes 72 hours instead of 2 weeks is a user who stays. A user who thinks we dropped the ball is not.
- Platform safety → feed quality preserved. Faster moderation + pattern detection → healthier communities → higher goal-event engagement → more goals completed.
- Operator headcount leverage. One moderator and one support hire managing 50K MAU beats 5 operators managing the same load. Every hour we don't burn on operations is an hour we can invest back into the north-star.
The north-star card on the admin dashboard home stays the single most prominent piece of UI. It is the reminder — every time an operator opens this tool — of what all this plumbing is for.
15. Related PRDs and References
- Standalone Admin App PRD — Phase 0 predecessor
- AI Workforce PRD — automation pattern this PRD doubles down on (shipped April 2026)
- Phase 7 Monetization & Teams PRD — consumer of 5.7 (billing)
- Privacy Controls PRD — GDPR adjacency
- Auth Audit & PRD — RBAC foundation (shipped April 2026)
- North Star — the metric
- Incident Response — existing runbook this PRD extends
- AI Runbook — existing runbook this PRD extends
- Admin Dashboard — feature doc for the shipped implementation
Objectuve Softworks, LLC (Delaware Public Benefit LLC; principal office in Chicago, IL)Date: 2026-04-16
Last updated: 2026-07-16