diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b84e7b..567e936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Supabase-backed feature flag governance service with admin management UI, audit logging, and typed SDK instrumentation. +- Hardened Supabase RBAC with updated roles/profile_roles migrations, refreshed RLS policies, and the `rbac_hardening_v1` rollout flag. - **Alert Dialog Component**: Installed neo-brutalism styled alert-dialog component from neobrutalism.dev - **Confirmation Dialogs**: Implemented confirmation dialogs for all major destructive actions across the application - Post deletion and publishing in PostsTable (mobile and desktop views) @@ -27,6 +29,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated CommentsModeration to use AlertDialog for comment deletion - Updated UserAccountPanel to use AlertDialog for sign out confirmation - Removed `window.confirm` usage in favor of accessible AlertDialog components +- Updated admin dashboard to surface highest-role badges and gate role management via `rbac_hardening_v1` with new authz telemetry. + +### Fixed + +- Added the missing `rbac_hardening_v1` enum value to feature flag migrations to keep Supabase schema in sync with governance defaults. +- Locked the admin feature flag API (including PURGE) behind RBAC checks, emitting denial telemetry and documenting reversible down migrations. ### Planned - Library Feature - **User Library System**: Complete Medium-style library feature for saving and organizing content @@ -46,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Documentation - Added `docs/library-feature-implementation-plan.md` - Complete implementation roadmap - Added `docs/library-feature-summary.md` - Executive summary and feature overview +- Refreshed security, data model, release plan, and test strategy docs to capture SEC-001 RBAC changes and telemetry. - Added `docs/library-technical-spec.md` - Detailed technical specifications and database schema ## [1.12.9] - 2025-02-27 diff --git a/docs/00-audit-report.md b/docs/00-audit-report.md new file mode 100644 index 0000000..c71f0d9 --- /dev/null +++ b/docs/00-audit-report.md @@ -0,0 +1,72 @@ +# Phase 0 Audit Report + +## 1. Executive Summary +Syntax & Sips currently delivers a production-grade editorial and community publishing experience built on Next.js 15 (App Router) and Supabase Postgres/Auth. The platform already exposes public storytelling surfaces (blogs, tutorials, podcasts, changelog), gated admin tooling, gamification widgets, and Supabase edge functions for newsletters and AI summarization. However, it lacks the governance, extensibility, and documentation required to evolve into the Community Platform Fusion vision that blends long-form publishing, structured Q&A, discussion spaces, and events/commerce. Phase 0 exposes the gaps and codifies priorities for an incremental roadmap. + +## 2. System Inventory +### 2.1 Applications & Frontend +- **Next.js App Router** under `src/app` with feature-first routes (`/blogs`, `/tutorials`, `/videos`, `/community`, `/admin`). +- **Neo-brutalist component library** stored in `src/components`, `src/components/ui`, `src/components/magicui`, and themed via `tailwind.config.js` & `src/app/globals.css`. +- **Authentication middleware** (`src/middleware.ts`, `src/lib/supabase`) gating admin, account, and onboarding routes. +- **Client integrations** for analytics, newsletters, and gamification controls (e.g., `src/components/admin`, `src/components/auth`, `src/components/ui/NewSummarizeButton.tsx`). + +### 2.2 Backend & Data +- **Supabase Postgres** schema defined through migrations under `supabase/migrations`. Key tables: `posts`, `post_tags`, `categories`, `tags`, `profiles`, `roles`, `profile_roles`, `comments`, `newsletter_subscribers`, `site_settings`. +- **Supabase Functions** for newsletter opt-in/out and AI summarization inside `supabase/functions`. +- **Next.js API routes** within `src/app/api/**` handling CRUD for content, newsletter, gamification, and admin workflows. +- **Edge/Server components** performing server-side Supabase queries with caching hints and streaming responses. + +### 2.3 Tooling & Operations +- **Testing:** Vitest configuration (`vitest.config.ts`) and Playwright setup (`playwright.config.ts`, `tests/` directory) with partial coverage. +- **Linting & formatting:** ESLint (`eslint.config.mjs`), Tailwind/PostCSS configs, but no documented Prettier hook. +- **Scripts:** `scripts/` folder for build-time helpers (e.g., chunk sync) and deployment automation. +- **Observability:** No unified telemetry spec; ad-hoc logging via console. No dashboards or metric definitions in repo. + +### 2.4 Documentation +- Extensive marketing and program documentation in `/docs`, but missing the mandated artifacts for architecture, backlog, release plan, security posture, observability, and risk tracking. + +## 3. Current Data Flows +| Flow | Trigger | Path | Notes | +| --- | --- | --- | --- | +| Public content render | Anonymous visitor requests `/blogs/[slug]` | Next.js server component fetches from Supabase `posts` and `post_tags` tables, caches response per ISR settings | No feature flags; all users share same experience | +| Admin moderation | Authenticated admin visits `/admin` | Middleware validates Supabase session → client components fetch analytics/queues via API routes → updates persisted via Supabase | Audit logging limited to Supabase defaults | +| Newsletter opt-in | Visitor submits email form | API route validates input → Supabase function handles subscription + transactional email via Mailtrap | Limited error handling surfaced to UI | +| Gamified summarization | Reader clicks summarize button | Client component calls Supabase function / edge worker to generate summary | No usage caps; potential abuse risk | + +## 4. Dependencies & Integrations +- **Supabase services:** Auth, Postgres, Edge Functions, Storage (for media assets referenced in content components). +- **Mailtrap SMTP:** For newsletter confirmations (per README environment requirements). +- **Analytics:** References to dashboards in admin components but no documented provider (likely Supabase or bespoke). Needs confirmation. +- **Third-party assets:** Tabler icon CDN in README, fonts under `/fonts`. + +## 5. Known Gaps & Technical Debt (Prioritized) +| Priority | Gap / Debt | Impact | Recommendation | +| --- | --- | --- | --- | +| P0 | Missing governance documents & feature flag framework for new modules | Blocks compliant delivery of new capabilities | Produce documentation suite (this Phase 0), define flag utilities, integrate with release plan | +| P0 | No dedicated spaces/communities domain model | Prevents Spaces rollout | Design new schema (`spaces`, `space_members`, `space_rules`, `space_roles`) and APIs with feature flags | +| P0 | Observability & KPI metrics undefined | Cannot monitor KPIs or enforce SLOs | Establish telemetry spec (metrics, traces, logging) and dashboards before Phase 1 | +| P1 | Moderation tooling lacks audit logs & sanctions | Non-compliant with safety requirements | Introduce `audit_logs`, sanctions workflow, and mod action logging | +| P1 | Search experience limited to curated content pages | Does not meet taxonomy/search goals | Implement full-text search index, synonyms, and topic pages | +| P2 | Payments, donations, events, and bounties absent | Blocks monetization phases | Plan integrations (Stripe/Razorpay/UPI) with compliance and KYC flows | +| P2 | Reputation system incomplete | Privilege ladder not enforceable | Model `reputation_events`, scoring rules, and privilege gating | +| P3 | Accessibility baseline unverified | Risk of WCAG non-compliance | Add automated accessibility tests, manual audits, and design token checks | + +## 6. Risks & Constraints +- **Data Integrity:** Existing schema may lack foreign keys/indices for new relationships; migrations must be reversible. +- **AuthZ Complexity:** Role expansion (member → admin) will require new policy definitions in Supabase; current RLS coverage unknown. +- **Operational Load:** Supabase quotas and email providers need capacity review before launching events/donations. +- **Timeline Pressure:** Deliverables span product, engineering, and UX; cross-functional syncs required to avoid drift. + +## 7. Baseline Metrics & Gaps +- **Content publish latency:** No measurement instrumentation; must be added in Phase 1. +- **Search P95 latency:** Search not centralized; baseline currently uninstrumented pending unified search rollout. +- **Donation success rate:** Payments not implemented; baseline 0%. +- **Moderation queue age:** Admin views exist but no metric tracking; instrumentation required. +- **Event RSVP conversion:** Events not yet live. + +## 8. Recommendations for Phase 1 Kickoff +1. Adopt the documentation suite defined in this repo (target architecture, product spec, roadmap, backlog, release plan). +2. Stand up feature flag utilities (likely using Supabase `site_settings` or ConfigCat/LaunchDarkly) to gate new modules. +3. Define telemetry plan before building new features to avoid retrofitting instrumentation. +4. Align design system updates with forthcoming Spaces/Content templates to reduce rework. +5. Audit Supabase RLS policies and plan for expanded role matrix. diff --git a/docs/01-target-architecture.md b/docs/01-target-architecture.md new file mode 100644 index 0000000..3a9e326 --- /dev/null +++ b/docs/01-target-architecture.md @@ -0,0 +1,176 @@ +# Target Architecture Blueprint + +## 1. Context Diagram +```mermaid +flowchart LR + subgraph Users + Visitor[Visitor] + Member[Member] + Creator[Creator] + Moderator[Moderator] + Admin[Platform Admin] + Sponsor[Sponsor / Donor] + end + + Visitor -->|consume content| WebApp[Next.js Web Experience] + Member -->|publish / engage| WebApp + Creator -->|manage projects/events| WebApp + Moderator -->|review queues| WebApp + Admin -->|configure policies| WebApp + Sponsor -->|fund bounties/donations| WebApp + + WebApp -->|RLS-secured queries| Supabase[(Supabase Postgres + Auth)] + WebApp -->|Edge functions| SupaFunctions[Supabase Edge Functions] + WebApp -->|Payments API| Payments[Stripe / Razorpay / UPI] + WebApp -->|Video & Conferencing| Conferencing[Zoom / Google Meet] + WebApp -->|Email + Webhooks| Messaging[Mailer (SMTP) + Webhooks] + WebApp -->|Analytics events| Observability[Metrics & Tracing Pipeline] + + Supabase -->|Auth callbacks| WebApp + SupaFunctions -->|Automation| Supabase + Payments -->|webhooks| WebApp + Messaging -->|notifications| Users +``` + +## 2. Container Diagram +```mermaid +flowchart TB + subgraph Client + NextClient[Next.js Client Components] + end + subgraph Server + NextServer[Next.js Server Components & Route Handlers] + FeatureFlags[Feature Flag Service] + Worker[Background Workers (Jobs/Queues)] + end + subgraph DataPlane + SupabaseDB[(Supabase Postgres)] + Storage[(Supabase Storage)] + Search[Index & Vector Search] + end + subgraph External + PaymentAPI[Stripe/Razorpay/UPI] + ConferencingAPI[Zoom/Google Meet] + EmailAPI[SMTP Provider] + ObservabilityStack[Telemetry Collector → Dashboard] + end + + NextClient <--> NextServer + NextServer -->|SQL/RPC| SupabaseDB + NextServer --> Storage + NextServer -->|search queries| Search + NextServer --> FeatureFlags + NextServer --> EmailAPI + NextServer --> ObservabilityStack + NextServer -->|webhooks| Worker + Worker --> SupabaseDB + Worker --> ObservabilityStack + Worker --> PaymentAPI + Worker --> ConferencingAPI + PaymentAPI --> NextServer + ConferencingAPI --> NextServer + EmailAPI --> Users +``` + +## 3. Component Diagram (Core Modules) +```mermaid +flowchart LR + subgraph Presentation + AppRouter[App Router Layouts] + SpaceShell[Space Shell & Navigation] + ContentTemplates[Article/Discussion/Q&A/Event Templates] + ReputationWidgets[XP, Badges, Leaderboards] + NotificationsPanel[Notification Center] + end + + subgraph Domain Services + SpaceService[Spaces Service] + ContentService[Content Service] + TaxonomyService[Tags & Search Service] + ReputationService[Reputation & Privileges] + ModerationService[Moderation & Safety] + CommerceService[Donations/Bounties/Events] + MessagingService[Comments & Direct Messages] + NotificationService[Notifications & Webhooks] + end + + subgraph Data Access + SupabaseClient[Supabase Typed Client] + FeatureFlagClient[Feature Flag SDK] + TelemetryClient[Telemetry SDK] + end + + AppRouter --> SpaceShell + SpaceShell --> ContentTemplates + ContentTemplates --> SpaceService + ContentTemplates --> ContentService + ContentTemplates --> TaxonomyService + ContentTemplates --> MessagingService + ReputationWidgets --> ReputationService + NotificationsPanel --> NotificationService + + SpaceService --> SupabaseClient + ContentService --> SupabaseClient + TaxonomyService --> SupabaseClient + TaxonomyService --> SearchEngine[Search Index] + ReputationService --> SupabaseClient + ModerationService --> SupabaseClient + CommerceService --> SupabaseClient + CommerceService --> PaymentGateway[Payment Gateway SDK] + MessagingService --> SupabaseClient + NotificationService --> SupabaseClient + NotificationService --> EmailAPI + + SupabaseClient --> TelemetryClient + FeatureFlagClient --> AppRouter +``` + +## 4. Key Sequence Diagrams +### 4.1 Article Publication with Reputation & Notifications +```mermaid +sequenceDiagram + participant C as Creator + participant UI as Next.js Client + participant API as Route Handler (Content Service) + participant DB as Supabase Postgres + participant Rep as Reputation Service + participant Notif as Notification Service + + C->>UI: Draft article & click Publish + UI->>API: POST /api/content (draft_id, publish_at) + API->>DB: validate permissions + upsert posts, post_versions + DB-->>API: success + API->>Rep: record reputation_event(feature_flag="spaces_v1") + Rep->>DB: insert reputation event & update aggregate + API->>Notif: enqueue notifications (followers, space members) + Notif->>DB: insert notification rows & webhooks + Notif-->>API: queued + API-->>UI: 202 Created + publish metadata + UI->>C: Confirmation toast + redirect +``` + +### 4.2 Space Moderation Workflow +```mermaid +sequenceDiagram + participant Mod as Space Moderator + participant UI as Admin Console + participant API as Moderation Service + participant DB as Supabase + participant Audit as Audit Logger + + Mod->>UI: Review reported post + UI->>API: POST /api/moderation/resolve {action: "remove", flag_id} + API->>DB: update report status, post visibility + API->>Audit: log action with user, role, reason + Audit->>DB: insert audit_logs entry + API-->>UI: success response + UI->>Mod: Display resolution & next steps +``` + +## 5. Architectural Principles +1. **Feature-flag first:** Every new capability (Spaces, Q&A, Events, Commerce) ships behind env-configurable flags with safe defaults. +2. **Supabase as system of record:** Postgres tables capture canonical content, community, and commerce data with RLS enforcing role matrix. +3. **Modular domain services:** Route handlers delegate to typed service modules to keep business logic testable and reusable across server components and workers. +4. **Telemetry baked in:** Each service emits metrics and traces for the KPIs defined in `/docs/08-observability.md`. +5. **Reversible migrations:** SQL migrations include down scripts and backfill jobs with resume tokens. +6. **Zero-trust external integrations:** Payments, conferencing, and email providers communicate through signed webhooks and rotate secrets regularly. diff --git a/docs/02-product-spec.md b/docs/02-product-spec.md new file mode 100644 index 0000000..5df94a4 --- /dev/null +++ b/docs/02-product-spec.md @@ -0,0 +1,169 @@ +# Product Specification – Community Platform Fusion + +## 1. Vision & Goals +Deliver a unified community platform that fuses long-form publishing, structured Q&A, discussions, events/workshops, and funding mechanics. The platform must: +- Empower creators and spaces to run end-to-end programs (content, community, commerce) within one workflow. +- Provide guardrails for moderators and admins through reputation, audit trails, and sanctions. +- Scale globally with accessibility, internationalization, and observability baked in. +- Ship iteratively with feature flags that allow opt-in enablement per space. + +## 2. Personas +| Persona | Goals | Pain Points Today | +| --- | --- | --- | +| **Reader/Member** | Discover high-quality content, join spaces, attend events, support creators. | Fragmented navigation across blogs vs. community; no personalization or subscriptions. | +| **Contributor/Creator** | Publish articles, answer questions, host events, monetize expertise. | Limited content types, no drafts/scheduling, no funding tools. | +| **Organizer/Moderator** | Enforce rules, curate tags, manage spaces, approve events. | No audit logs, limited rule enforcement, manual communications. | +| **Admin** | Govern the platform, configure feature flags, monitor KPIs, handle escalations. | Lack of observability, inconsistent permissioning, no consolidated release plan. | +| **Sponsor/Donor** | Fund bounties, donate to spaces/projects, track impact. | No payment rails or transparency on fees/results. | + +## 3. User Stories +### 3.1 Spaces & Communities +- As an organizer, I can create a space with rules, custom flairs, tags, and templates so new content stays on-brand. +- As a member, I can request to join a private space and understand the rules before posting. +- As a moderator, I can escalate and log sanctions with immutable audit trails. +- As an admin, I can delegate space-level roles (member, contributor, organizer, moderator, admin) and manage permissions via UI. + +### 3.2 Content Types +- As a creator, I can draft an article with markdown, code blocks, media, and schedule it for publication. +- As a contributor, I can start a discussion with lightweight formatting and threaded comments. +- As a member, I can ask a question, mark an accepted answer, and offer reputation or currency bounties. +- As a project maintainer, I can manage a project page with funding links, issues intake, and releases. +- As a workshop host, I can publish a multi-session curriculum with materials and feedback loops. + +### 3.3 Tags, Taxonomy, Search +- As a moderator, I can merge duplicate tags, set synonyms, and edit tag wikis. +- As a reader, I can explore topic pages with trending content, events, and experts. +- As a user, I can search across posts, comments, events, and people with typo tolerance and filters. + +### 3.4 Feeds & Ranking +- As a member, I want my home feed to blend followed spaces with high-quality recommendations and anti-spam damping. +- As a space visitor, I can switch between hot/new/top tabs with transparent ranking signals. + +### 3.5 Reputation & Privileges +- As a contributor, I earn reputation from accepted answers, article quality scores, helpful flags, and event hosting. +- As an organizer, I set privilege thresholds (suggest edits, edit, manage tags, close duplicates, mod tools). +- As an admin, I enforce downvote costs and time-decay to keep leaderboards fresh. + +### 3.6 Moderation & Safety +- As a member, I can report content with categorized reasons. +- As a moderator, I process review queues (spam, quality, duplicate, off-topic) with bulk actions. +- As an admin, I configure automod rules (rate limits, banned domains, trust scores) per space. +- As compliance, I need immutable audit logs for every moderative action. + +### 3.7 Comments & Messaging +- As a reader, I engage in threaded comments with quote-reply and mentions. +- As a user, I can send direct messages with a request/accept workflow and abuse safeguards. + +### 3.8 Notifications & Subscriptions +- As a member, I manage notification preferences per space and content type (real-time, email digest). +- As an org, I subscribe to webhooks for publish, donation, and ticket sales. + +### 3.9 Donations, Payouts, Bounties +- As a fan, I tip creators once or subscribe with a recurring pledge, seeing the fee breakdown. +- As a creator, I configure payout preferences, pass KYC, and download invoices/receipts. +- As a sponsor, I escrow a bounty (currency or reputation) until an answer is accepted. +- As a moderator, I resolve bounty disputes with recorded decisions. + +### 3.10 Events & Workshops +- As an organizer, I schedule online/offline events with capacity, pricing, accessibility notes, and reminders. +- As a host, I manage check-in via QR codes and export attendance. +- As a learner, I enroll in workshops, track assignments, and earn completion badges. + +### 3.11 Analytics & Insights +- As a creator, I see reads, average dwell time, and donor trends per post. +- As a space owner, I monitor growth, retention, contribution mix, and moderation load. +- As an organizer, I track registrations, attendance, and post-event ratings. + +### 3.12 Admin & Platform Ops +- As an admin, I manage users, spaces, escalations, KYC approvals, and feature flags. +- As SRE, I monitor SLO dashboards, configure alerts, and run backups/restores. + +## 4. Feature Breakdown & Flows +### 4.1 Spaces +- **Creation Flow:** Organizer chooses visibility (public/private), selects templates, sets rules, invites moderators. Feature flag: `spaces_v1`. +- **Membership Flow:** Members request or auto-join; organizer approves, assigns role. Notifications triggered and logged. +- **Templates:** Article, Discussion, Q&A, Event, Workshop templates attach to space for quick drafts. +- **Moderation:** Queue per space with automod, manual review, audit logging. + +### 4.2 Content Lifecycle +- **Drafting:** Client-side rich editor, auto-save to `post_versions` with version history. +- **Publishing:** Validation ensures canonical URL, schedule support, optional cross-post to other spaces (with mod approval). +- **Revision:** Edits create new versions; diffs displayed; moderators approve high-risk edits. +- **Bounties:** Q&A posts allow attaching bounty currency/reputation, locked in escrow until acceptance. + +### 4.3 Events & Workshops +- **Event Creation:** Organizer selects online/offline, adds start/end, timezone, venue/accessibility. Online events attach meeting links and ICS generation. Offline events manage ticket tiers, coupon codes, waitlists, QR check-in. +- **Workshop Flow:** Multi-session schedule builder, prerequisites, materials locker (Supabase storage), assignments submission, instructor feedback, completion badges. +- **Reminders:** Automated notifications at T-24h/1h/10m; post-event follow-up with recording & survey. + +### 4.4 Commerce +- **Donations:** Modal or dedicated page with tip/pledge options, donor covers fees toggle, anonymity selection. +- **Payouts:** Creator onboarding wizard (KYC), payout method selection, job queue for payouts with retries, receipts archive. +- **Transactions:** Transparent fee summary, currency/localization handling, tax calculations (GST/VAT), dispute management. + +### 4.5 Reputation & Moderation +- **Reputation Events:** Accepted answers, article quality, helpful reports, successful events, project contributions, donations. +- **Privilege Ladder:** Thresholds stored per action; UI shows locked/unlocked privileges with tooltips. +- **Sanctions:** Soft-delete, removal, quarantines, shadow-bans, space/site bans; all actions recorded in `audit_logs` with actor role. +- **Automod:** Rate limits, first-post restrictions, link trust scores, banned terms/domains; alerts moderators and optionally auto-holds content. + +### 4.6 Notifications & Messaging +- **Notification Types:** Real-time in-app, email summary, mobile push (future), webhooks for orgs. +- **Controls:** Preferences matrix by space and content type; digest frequency selection; per-event reminders. +- **Direct Messages:** Request inbox, block/report options, anti-harassment rules defaulting to limited contact until accepted. + +### 4.7 Search & Discovery +- **Indexing:** Posts, comments, profiles, events, workshops, projects; includes tags, flairs, reputation signals. +- **Query Experience:** Typeahead with typo tolerance, filters (space, content type, tag, timeframe), ranking by relevance/recency/quality. +- **Topic Pages:** Tag landing pages with curated content, events, experts, FAQs, top contributors. + +### 4.8 Analytics & Insights +- **Creator Dashboard:** Content performance, donor trends, audience retention. +- **Space Analytics:** Growth funnel, moderation load, top tags, automod actions. +- **Organizer Insights:** Registrations vs. attendance, revenue, NPS/post-event ratings. +- **Admin Console:** Feature flag adoption, KPI tracking, SLO compliance. + +## 5. Edge Cases & Error States +- **Draft Conflicts:** Simultaneous edits resolved via version history and conflict warnings. +- **Bounty Expiry:** Auto-refund or extend when no accepted answer before deadline; notifications triggered. +- **Payment Failures:** Retry schedule with exponential backoff; UI shows status and offers update payment method. +- **Event Overcapacity:** Waitlist promotion and notifications; handle check-in with offline support when QR scanners fail. +- **Moderation Escalations:** Multiple moderators acting simultaneously—audit log ensures ordering; lock content during review. +- **Search Failures:** Graceful fallback with suggestions and cached trending content. +- **Notification Fatigue:** Enforce rate limits and digest bundling; allow per-space overrides. +- **Data Retention:** GDPR deletion requests propagate to analytics, notifications, and backups with tombstone records. + +## 6. Empty, Loading, and Error States +- **Content Lists:** Skeleton loaders for cards, empty illustrations with guidance to create/join spaces. +- **Space Dashboard:** Empty state prompting to configure rules and templates; tooltips for privilege thresholds. +- **Q&A:** Empty queue message encouraging first question; accepted answer highlight with confetti animation when available. +- **Events:** Loading spinners for map embeddings; offline fallback instructions when map provider blocked. +- **Notifications:** Loading shimmer, empty state with toggle shortcuts, error toast when delivery settings fail to save. + +## 7. Accessibility & Internationalization +- All interactive elements keyboard navigable (tab order, focus-visible styles). +- Provide ARIA labels for icons, semantic headings for content templates. +- Support locale-aware dates/times, timezone conversions, RTL support for languages like Arabic/Hebrew. +- Ensure color contrast meets WCAG AA, offer reduced motion settings, and screen reader announcements for real-time updates. + +## 8. Analytics & KPIs Mapping +| KPI | Measurement Plan | +| --- | --- | +| Content publish latency | Instrument API + worker timers; capture from draft save to publish complete. | +| Search P95 latency | Wrap search service with metrics; log query metadata, type, and latency. | +| Donation success rate | Payments webhook outcomes vs. initiated sessions. | +| Payout error rate | Job queue success/failure counts in observability stack. | +| Event RSVP → attendance | Track registrations, check-ins, attendance exports. | +| Moderation queue oldest age | Emit gauge from moderation queue poller. | +| Crash-free sessions | Integrate client crash reporting (Sentry/Segment). | + +## 9. Release Constraints +- Each module gated behind dedicated feature flags documented in `/docs/10-release-plan.md`. +- Reversible migrations with backfill scripts (Supabase SQL + worker jobs). +- Comprehensive test coverage per `/docs/09-test-strategy.md` before enabling flags. +- Staged rollout: internal staff → pilot spaces → GA. + +## 10. Open Questions (Tracked in `/docs/assumptions.md`) +- Preferred feature flag service (Supabase table vs. third-party)? +- Final payment processor availability by geography? +- Storage & CDN strategy for event recordings? diff --git a/docs/03-roadmap.md b/docs/03-roadmap.md new file mode 100644 index 0000000..b849eac --- /dev/null +++ b/docs/03-roadmap.md @@ -0,0 +1,105 @@ +# Roadmap – Community Platform Fusion + +## 1. Phasing Overview +| Phase | Focus | Primary Outcomes | Gate Criteria | +| --- | --- | --- | --- | +| Phase 0 – Audit | Inventory, documentation, governance baseline | Complete docs suite, ADR logged, assumptions & risks captured | Sign-off on audit + roadmap; feature flags approach agreed | +| Phase 1 – Foundations | Identity, permissions, design system, observability, test harness | Hardened authz, design tokens, telemetry stack, feature flag service | Security review passed; dashboards live; CI green with new harness | +| Phase 2 – Core Community | Spaces, content templates, tags/search, feeds, moderation, reputation | Spaces vertical slice behind flags, search baseline, reputation loop | End-to-end slice validated, accessibility checks, anti-abuse active | +| Phase 3 – Funding & Events | Donations, payouts, bounties, events/workshops, notifications | Commerce flows in sandbox+pilot, events ticketing & refunds | Payout sandbox+limited live run, ticket refund tested, dispute workflow | +| Phase 4 – Scale & Polish | Admin console, SEO, i18n, accessibility, API/webhooks | Admin consoles & automation, SEO compliance, locale support | SEO audit pass, WCAG AA verification, error budgets green | + +## 2. Milestones & Deliverables +### Phase 0 (Weeks 1–2) +- Complete audit report, product spec, architecture blueprint, release plan. +- Produce dependency-aware backlog and risk register. +- Align on feature flag implementation strategy (decision recorded in ADR if new vendor required). + +### Phase 1 (Weeks 3–6) +- Implement RBAC enhancements in Supabase (role matrix, policy review). +- Establish feature flag table/service with SDK usage in Next.js. +- Ship updated design tokens & navigation IA (see `/docs/05-ui-ux-delta.md`). +- Stand up observability stack (metrics, tracing, logging) and baseline dashboards. +- Expand automated testing harness (unit, integration, accessibility smoke). + +### Phase 2 (Weeks 7–14) +- Launch Spaces (creation, rules, flairs) and membership workflows behind `spaces_v1` flag. +- Deliver content templates (Article, Discussion, Q&A, Event, Workshop) integrated with version history. +- Introduce taxonomy features (tags with synonyms, topic pages) and full-text search with facets. +- Implement feed ranking (home, space hot/new/top) with anti-spam dampening. +- Roll out reputation events and privilege ladder gating moderation tools. +- Expand moderation & safety (reporting flows, automod rules, audit logs). + +### Phase 3 (Weeks 15–24) +- Integrate payments (Stripe/Razorpay/UPI), donation flows, donor wall, fee transparency. +- Build payout onboarding (KYC), payout job queue, receipts, error handling. +- Enable bounties/escrow for Q&A and project pages with dispute resolution UI. +- Develop events/workshops (online/offline ticketing, waitlists, reminders, materials locker). +- Enhance notifications (real-time, email digest, webhooks, granular controls). + +### Phase 4 (Weeks 25–32) +- Launch admin console expansions (user/space management, escalations, feature flags dashboard). +- Ship SEO improvements (sitemaps, canonical URLs, OG metadata), RSS feeds, newsletters. +- Deliver i18n & accessibility refinements (RTL support, localization, audits). +- Release public API & webhooks with OAuth, rate limiting, developer portal. +- Finalize observability maturity (SLO automation, incident runbooks, backup drills). + +## 3. KPIs per Phase +| Phase | KPI Focus | Target | +| --- | --- | --- | +| Phase 0 | Planning velocity | 100% of mandated docs approved | +| Phase 1 | Security & quality | 0 critical authz bugs; ≥80% automated test coverage; telemetry events available | +| Phase 2 | Community engagement | ≥3 pilot spaces active; content publish latency ≤5s P95; moderation queue oldest <30m | +| Phase 3 | Monetization & events | Donation success ≥95%; payout error rate <1%; event RSVP→attendance ≥60% | +| Phase 4 | Platform scale | SEO crawl error rate <1%; localization coverage 3 core locales; crash-free sessions ≥99% | + +## 4. Dependencies & Cross-Team Alignment +- Payments provider onboarding must begin during Phase 1 to unblock Phase 3. +- Observability stack (Phase 1) is prerequisite for KPIs in later phases. +- Design system updates must coordinate with product/UX to avoid rework when launching new templates. +- Legal/compliance review required before launching donations/bounties/events. +- SRE involvement needed for backup/restore drills and incident response definitions. + +## 5. Risk Mitigation Strategy +| Risk | Phase Impacted | Mitigation | +| --- | --- | --- | +| Payment processor approval delays | Phase 3 | Start vendor due diligence in Phase 1, prepare fallback provider, maintain feature flag to keep commerce dark if needed | +| Supabase RLS complexity | Phase 1–2 | Invest in integration tests for policy coverage, document auth matrix, use security review gate | +| Feature flag sprawl | All | Centralize flag management with naming convention & dashboard, include expiry dates | +| Accessibility regressions | Phase 2–4 | Automated axe scans + manual audits per major release, involve accessibility specialist | +| Observability noise | Phase 1+ | Define metric naming conventions, alert thresholds, and runbooks upfront | + +## 6. Release Train Cadence +- **Bi-weekly increments:** Each increment reviews telemetry, feature flag uptake, and outstanding risks. +- **Monthly stakeholder demo:** Showcase progress (Spaces slice, commerce flows) with feedback recorded in backlog. +- **Quarterly executive review:** Align on KPIs, funding, resource allocation, and potential roadmap adjustments. + +## 7. Communication Plan +- Weekly cross-functional standup (Product, Engineering, Design, SRE). +- Async status updates via shared doc referencing metrics and flag status. +- Incident and rollback communication templates defined in `/docs/10-release-plan.md`. + +## 8. Phase Gantt (Indicative) +```mermaid +gantt +dateFormat YYYY-MM-DD +title Community Platform Fusion Roadmap +section Phase 0 - Audit +Discovery & Documentation :done, p0, 2025-02-01, 2025-02-14 +section Phase 1 - Foundations +Authz Hardening & Flags :active, p1a, 2025-02-17, 2025-03-14 +Design System & Observability :p1b, 2025-02-24, 2025-03-28 +Test Harness Expansion :p1c, 2025-03-03, 2025-04-04 +section Phase 2 - Core Community +Spaces & Content Templates :p2a, 2025-04-07, 2025-05-30 +Taxonomy & Search :p2b, 2025-04-21, 2025-06-06 +Feeds, Moderation, Reputation :p2c, 2025-05-05, 2025-06-20 +section Phase 3 - Funding & Events +Commerce Stack :p3a, 2025-06-23, 2025-08-15 +Events & Workshops :p3b, 2025-07-07, 2025-08-29 +Notifications Expansion :p3c, 2025-07-21, 2025-09-05 +section Phase 4 - Scale & Polish +Admin Console & API :p4a, 2025-09-08, 2025-10-24 +i18n, Accessibility, SEO :p4b, 2025-09-22, 2025-11-07 +Observability Maturity :p4c, 2025-10-06, 2025-11-21 +``` diff --git a/docs/04-backlog.csv b/docs/04-backlog.csv new file mode 100644 index 0000000..b89281b --- /dev/null +++ b/docs/04-backlog.csv @@ -0,0 +1,14 @@ +id,title,module,type,priority,size,dependencies,acceptance_criteria,definition_of_done,risk,labels,estimate_hours +GOV-000,Finalize feature flag service and governance,Foundations,feature,P0,M,,"Feature flag storage provisioned with CRUD UI; SDK integrated into Next.js layers; Flag change audit log exposed","Green CI tests added; Docs updated with flag naming; Telemetry for flag evaluations; Feature flag default OFF",medium,"feature-flag|backend|frontend",40 +SEC-001,Harden Supabase RBAC for expanded role matrix,Security,feature,P0,L,GOV-000,"RLS enforces role matrix; Integration tests cover allow/deny; Admin console exposes role assignment","Green CI tests added; Docs updated auth matrix; Telemetry for authz failures; Feature flag default OFF",high,"backend|security",56 +UX-010,Refresh navigation IA and design tokens,Foundations,feature,P1,M,GOV-000,"Primary nav supports new hubs; Design tokens codified; Dark/light parity","Green CI tests added; Docs updated 05-ui-ux-delta; Telemetry for nav; Feature flag default OFF",medium,"frontend|design",48 +MOD-001,Implement Spaces with roles and rules,Spaces,feature,P0,L,"SEC-001|UX-010","CRUD spaces with rules/tags/flairs; Role matrix enforced; Audit log captured","Green CI tests added; Docs+screens updated; Telemetry dashboard links; Feature flag default OFF",medium,"backend|frontend|feature-flag",120 +CNT-020,Ship content templates with version history,Content,feature,P0,XL,MOD-001,"Templates for Article/Discussion/Q&A/Event/Workshop; Draft autosave+diff; Scheduling & canonical URL","Green CI tests added; Docs updated product spec; Telemetry draft/publish; Feature flag default OFF",high,"frontend|backend|editor",200 +SRCH-030,Implement taxonomy synonyms and unified search,Search,feature,P0,L,"MOD-001|CNT-020","Tags CRUD+synonyms; Search indexes multi-entity; Topic pages with filters","Green CI tests added; Docs updated taxonomy; Telemetry for search latency; Feature flag default OFF",medium,"search|backend|frontend",160 +REP-040,Launch reputation engine and privilege ladder,Reputation,feature,P1,L,"MOD-001|CNT-020","Reputation events captured; Privilege thresholds enforced; Time-decay leaderboards","Green CI tests added; Docs updated reputation; Telemetry for rep deltas; Feature flag default OFF",medium,"backend|analytics",140 +MOD-050,Moderation automation and sanctions system,Moderation,feature,P0,L,REP-040,"Report queues + bulk actions; Automod configurable per space; Sanctions with audit logs","Green CI tests added; Docs updated moderation; Telemetry for queue age; Feature flag default OFF",high,"safety|backend|frontend",180 +COM-060,Implement donations and recurring pledges,Commerce,feature,P0,L,"GOV-000|SEC-001","Tips and pledges with fee transparency; Donor covers fees toggle; Receipts generated","Green CI tests added; Docs updated commerce; Telemetry donation success; Feature flag default OFF",high,"payments|frontend|backend",180 +PAY-070,Payouts with KYC and job queue,Commerce,feature,P0,L,COM-060,"Creator onboarding with KYC; Payout queue retries; Receipts accessible","Green CI tests added; Docs updated payout SOP; Telemetry payout errors; Feature flag default OFF",high,"payments|backend",160 +EVT-080,Events and workshops platform,Events,feature,P0,XL,"MOD-001|COM-060","Events ticketing waitlists coupons; Reminders + recordings; Workshops multi-session assignments badges","Green CI tests added; Docs updated events playbook; Telemetry RSVP-attendance; Feature flag default OFF",high,"events|frontend|backend",220 +MSG-090,Threaded comments and direct messaging,Engagement,feature,P1,L,"MOD-001|REP-040","Threaded comments w/ quote/mentions; DM request/accept w/ abuse controls; Rate limiting + reporting","Green CI tests added; Docs updated messaging UX; Telemetry comment engagement; Feature flag default OFF",medium,"frontend|backend|safety",150 +OBS-100,Observability platform rollout,SRE,feature,P0,M,,"KPI metrics instrumented; Distributed tracing enabled; Dashboards+alerts documented","Green CI tests added; Docs updated observability; Telemetry validated; Feature flag default OFF",medium,"observability|infrastructure",120 diff --git a/docs/04-backlog.json b/docs/04-backlog.json new file mode 100644 index 0000000..f65efb3 --- /dev/null +++ b/docs/04-backlog.json @@ -0,0 +1,419 @@ +{ + "tickets": [ + { + "id": "GOV-000", + "title": "Finalize feature flag service and governance", + "module": "Foundations", + "type": "feature", + "priority": "P0", + "size": "M", + "dependencies": [], + "acceptance_criteria": [ + "Feature flag storage (Supabase table or vendor) provisioned with CRUD UI", + "SDK integrated into Next.js server/client layers with typed helper", + "Flag change audit log captured and exposed to admins" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated with flag naming convention", + "Telemetry events for flag evaluations shipped", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "feature-flag", + "backend", + "frontend" + ], + "kpi": "Flag evaluation latency \u226450ms", + "estimate_hours": 40, + "subtasks": [ + "Schema: create feature_flags and feature_flag_audit tables with RLS + triggers", + "API: admin CRUD endpoints with audit logging and secure read endpoints", + "SDK: typed server/client helpers with caching and telemetry instrumentation", + "UI: Admin dashboard feature flag manager with audit trail viewer", + "Tests: unit coverage for SDK and request guards; accessibility scan for manager", + "Docs: update data model, UX delta, observability, release plan, assumptions, progress log" + ] + }, + { + "id": "SEC-001", + "title": "Harden Supabase RBAC for expanded role matrix", + "module": "Security", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "GOV-000" + ], + "acceptance_criteria": [ + "Roles member/contributor/organizer/moderator/admin enforced via RLS", + "Integration tests cover allow/deny matrix for critical tables", + "Admin console exposes role assignment behind flag" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (auth matrix in /docs/07-security-privacy.md)", + "Telemetry shipped for authz failures", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "backend", + "security" + ], + "kpi": "0 critical authz regressions post-launch", + "estimate_hours": 56, + "subtasks": [ + "Schema: update roles hierarchy, migrate legacy slugs, refresh RLS policies for taxonomy/posts/community tables", + "API: enforce expanded role checks and audit authz denials with telemetry", + "UI: gate admin role manager behind rbac_hardening_v1 and surface role labels", + "Tests: unit coverage for role slug normalization, metrics, and RBAC policy harness", + "Docs: refresh security matrix, data model, release plan, assumptions, and risks" + ] + }, + { + "id": "UX-010", + "title": "Refresh navigation IA and design tokens", + "module": "Foundations", + "type": "feature", + "priority": "P1", + "size": "M", + "dependencies": [ + "GOV-000" + ], + "acceptance_criteria": [ + "Primary nav supports spaces, feeds, events, funding hubs", + "Design tokens defined for color, spacing, typography, elevation", + "Dark/light mode parity maintained" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated in /docs/05-ui-ux-delta.md", + "Telemetry shipped for nav interactions", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "frontend", + "design" + ], + "kpi": "Nav engagement (CTR) \u2265 baseline", + "estimate_hours": 48 + }, + { + "id": "MOD-001", + "title": "Implement Spaces with roles and rules", + "module": "Spaces", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "SEC-001", + "UX-010" + ], + "acceptance_criteria": [ + "Create/read/update/delete spaces with rules, tags, flairs", + "Role matrix enforced at API and UI for member/contributor/organizer/mod/admin", + "Audit log entries for all moderative actions" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated and screenshots recorded", + "Telemetry shipped with dashboard links", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "backend", + "frontend", + "feature-flag" + ], + "kpi": "Space creation success rate \u2265 99.5%", + "estimate_hours": 120 + }, + { + "id": "CNT-020", + "title": "Ship content templates with version history", + "module": "Content", + "type": "feature", + "priority": "P0", + "size": "XL", + "dependencies": [ + "MOD-001" + ], + "acceptance_criteria": [ + "Article, Discussion, Q&A, Event, Workshop templates available per space", + "Draft autosave and post_versions diff viewer implemented", + "Scheduling and canonical URL workflows validated" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (product spec & UX states)", + "Telemetry shipped for draft/publish events", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "frontend", + "backend", + "editor" + ], + "kpi": "Draft\u2192publish conversion \u226570%", + "estimate_hours": 200 + }, + { + "id": "SRCH-030", + "title": "Implement taxonomy, synonyms, and unified search", + "module": "Search", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "MOD-001", + "CNT-020" + ], + "acceptance_criteria": [ + "Tags CRUD with synonyms/merges and tag wikis", + "Search service indexes posts, comments, events, profiles", + "Topic pages render curated feeds with filters" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (taxonomy guide)", + "Telemetry shipped for search latency & zero-result rate", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "search", + "backend", + "frontend" + ], + "kpi": "Search P95 latency \u2264 500ms", + "estimate_hours": 160 + }, + { + "id": "REP-040", + "title": "Launch reputation engine and privilege ladder", + "module": "Reputation", + "type": "feature", + "priority": "P1", + "size": "L", + "dependencies": [ + "MOD-001", + "CNT-020" + ], + "acceptance_criteria": [ + "Reputation events recorded for answers, articles, reports, events, projects", + "Privilege thresholds enforce access to edit/tag/mod actions", + "Time-decay applied to leaderboard aggregates" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (reputation guide)", + "Telemetry shipped for reputation deltas", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "backend", + "analytics" + ], + "kpi": "Leaderboard freshness index \u2265 0.8", + "estimate_hours": 140 + }, + { + "id": "MOD-050", + "title": "Moderation automation and sanctions system", + "module": "Moderation", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "REP-040" + ], + "acceptance_criteria": [ + "Report flows with spam/quality/duplicate/off-topic queues", + "Automod rules (rate limit, trust scores, banned domains) configurable per space", + "Sanctions workflow supports removals, quarantines, shadow-bans with audit logs" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (moderation handbook)", + "Telemetry shipped for queue age & sanction counts", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "safety", + "backend", + "frontend" + ], + "kpi": "Moderation queue oldest < 30 minutes", + "estimate_hours": 180 + }, + { + "id": "COM-060", + "title": "Implement donations and recurring pledges", + "module": "Commerce", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "GOV-000", + "SEC-001" + ], + "acceptance_criteria": [ + "One-time tips and recurring pledges with fee transparency", + "Donor covers fees toggle & anonymity options", + "Receipts generated and emailed with audit trail" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (commerce guide)", + "Telemetry shipped for donation success rate", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "payments", + "frontend", + "backend" + ], + "kpi": "Donation success rate \u226595%", + "estimate_hours": 180 + }, + { + "id": "PAY-070", + "title": "Payouts with KYC and job queue", + "module": "Commerce", + "type": "feature", + "priority": "P0", + "size": "L", + "dependencies": [ + "COM-060" + ], + "acceptance_criteria": [ + "Creator onboarding captures KYC and payout preferences", + "Payout queue with retries and error handling", + "Receipts/invoices accessible to creators and admins" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (payout SOP)", + "Telemetry shipped for payout error rate", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "payments", + "backend" + ], + "kpi": "Payout error rate <1%", + "estimate_hours": 160 + }, + { + "id": "EVT-080", + "title": "Events and workshops platform", + "module": "Events", + "type": "feature", + "priority": "P0", + "size": "XL", + "dependencies": [ + "MOD-001", + "COM-060" + ], + "acceptance_criteria": [ + "Online/offline events with ticketing, waitlists, coupon codes", + "Reminders (T-24h/1h/10m) and post-event recording/materials upload", + "Workshops support multi-session schedules, assignments, completion badges" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (events playbook)", + "Telemetry shipped for RSVP\u2192attendance rate", + "Feature flag default OFF" + ], + "risk": "high", + "owner": "tbd", + "labels": [ + "events", + "frontend", + "backend" + ], + "kpi": "Event RSVP\u2192attendance \u226560%", + "estimate_hours": 220 + }, + { + "id": "MSG-090", + "title": "Threaded comments and direct messaging", + "module": "Engagement", + "type": "feature", + "priority": "P1", + "size": "L", + "dependencies": [ + "MOD-001", + "REP-040" + ], + "acceptance_criteria": [ + "Threaded comments with quote-reply, mentions, collapsible branches", + "Direct messages with request/accept workflow and abuse protections", + "Rate limiting and reporting integrated with moderation service" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (messaging UX)", + "Telemetry shipped for comment engagement", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "frontend", + "backend", + "safety" + ], + "kpi": "Comment response rate \u2265 baseline", + "estimate_hours": 150 + }, + { + "id": "OBS-100", + "title": "Observability platform rollout", + "module": "SRE", + "type": "feature", + "priority": "P0", + "size": "M", + "dependencies": [], + "acceptance_criteria": [ + "Metrics for KPIs (publish latency, search p95, donation success, payout error, RSVP\u2192attendance, moderation queue age, crash-free sessions)", + "Distributed tracing instrumented for major services", + "Dashboards and alerting policies documented" + ], + "definition_of_done": [ + "Green CI, tests added", + "Docs updated (/docs/08-observability.md)", + "Telemetry shipped and validated", + "Feature flag default OFF" + ], + "risk": "medium", + "owner": "tbd", + "labels": [ + "observability", + "infrastructure" + ], + "kpi": "Alert MTTA < 15m", + "estimate_hours": 120 + } + ] +} diff --git a/docs/05-ui-ux-delta.md b/docs/05-ui-ux-delta.md new file mode 100644 index 0000000..f6ca6cc --- /dev/null +++ b/docs/05-ui-ux-delta.md @@ -0,0 +1,46 @@ +# UI/UX Delta Plan + +## 1. Information Architecture Updates +- **Global Navigation:** Introduce top-level hubs for Spaces, Feeds, Events, Funding, Projects, and Admin (role-gated). Utilize mega-menu for quick access to joined spaces and upcoming events. +- **Space Shell:** Each space gets a consistent layout with overview, content tabs (Articles, Discussions, Q&A, Events, Workshops), members, rules, and analytics. +- **Creator Console:** Consolidate drafts, scheduled posts, bounties, donations, and events into a single dashboard accessible from `/creator`. +- **Admin Console:** Expand IA to include feature flag management, KYC approvals, moderation escalation, and audit log explorer. + +## 2. Component Inventory (New & Updated) +| Component | Type | Status | Notes | +| --- | --- | --- | --- | +| `SpaceHeader` | Server component | New | Displays space branding, rules CTA, join/leave actions, feature-flag aware. | +| `SpaceRolePill` | Client component | New | Shows member role, tooltip with privileges. | +| `ContentComposer` | Client component | Updated | Modular editor supporting Article/Discussion/Q&A/Event/Workshop templates with plugin architecture. | +| `TemplatePickerModal` | Client component | New | Allows selecting space-level templates with previews and accessibility hints. | +| `ModerationQueueTable` | Client component | Updated | Adds filters for queue type, bulk actions, SLA indicators. | +| `ReputationBadge` | Client component | Updated | Reflects new tiers, includes tooltip for required XP. | +| `DonationWidget` | Client component | New | Supports one-time/recurring pledges, fee breakdown slider, donor anonymity toggle. | +| `EventCard` | Server component | New | Lists schedule, venue/map, availability, price. | +| `NotificationPreferencesMatrix` | Client component | New | Grid-based control for per-space/per-content notification toggles. | +| `AuditLogTimeline` | Server component | New | Timeline visualization for moderation/flag changes with filter chips. | +| `FeatureFlagManager` | Client component | New | Admin console surface for creating, editing, and auditing feature flags with accessible forms and toggles. | +| `UserManagement` | Client component | Updated | Role assignment UI now gated by `rbac_hardening_v1`, highlights highest role badge and displays RBAC guidance tooltips. | + +## 3. Design Tokens & Theming +- **Color:** Expand palette to include semantic tokens (`success`, `warning`, `danger`, `info`, `neutral`) with accessible contrast; provide dark/light variants. +- **Spacing:** Introduce scale `space-xxs` (4px) to `space-xxl` (48px) for layout rhythm across dashboards. +- **Typography:** Define tokens for headings (`display`, `headline`, `title`, `body`, `mono`) with clamp-based responsive sizing. +- **Elevation:** Create shadow tokens for interactive surfaces (cards, modals) aligned with neo-brutalism outlines. +- **Radius:** Maintain bold outlines but add `radius-sm` (4px) for chip components and `radius-lg` (16px) for cards. + +## 4. Accessibility Notes +- All new interactive components must support keyboard navigation, focus-visible styles, and ARIA attributes. +- Provide descriptive labels for toggles (e.g., fee coverage slider, feature flag enable switch) and ensure error messages include guidance. +- For events, include accessibility notes (wheelchair access, ASL availability) and ensure color-coded statuses have text equivalents. +- Implement reduced motion mode for animations in reputation celebrations and feed transitions. + +## 5. Responsive Behavior +- **Mobile:** Sticky quick actions (Join Space, New Post) at bottom; collapsible filters for search and moderation queues. +- **Tablet:** Two-column layout for dashboards with persistent navigation drawer. +- **Desktop:** Grid-based analytics with quick-glance KPI cards, multi-pane messaging UI. + +## 6. Documentation & Handoff +- Maintain component specs in Storybook (to be configured) with accessibility checklists. +- Update `neobrutalismthemecomp.MD` with new tokens and examples. +- Capture screenshots/GIFs for each new flow when feature flags progress to pilot. diff --git a/docs/06-data-model-delta.md b/docs/06-data-model-delta.md new file mode 100644 index 0000000..027dd84 --- /dev/null +++ b/docs/06-data-model-delta.md @@ -0,0 +1,78 @@ +# Data Model Delta + +## 1. New Tables & Entities +| Table | Purpose | Key Columns | Notes | +| --- | --- | --- | --- | +| `spaces` | Defines communities with branding and governance metadata | `id`, `slug`, `name`, `description`, `visibility`, `rules`, `created_by`, `feature_flags`, `created_at`, `updated_at` | `slug` unique; `feature_flags` JSONB for space-level toggles | +| `space_members` | Maps users to spaces with roles and status | `space_id`, `profile_id`, `role`, `status`, `invited_by`, `joined_at`, `last_active_at` | Composite PK (`space_id`, `profile_id`); status enum (pending, active, banned) | +| `space_rules` | Structured rules/flairs/templates | `id`, `space_id`, `type`, `value`, `position` | `type` enum (rule, flair, template, automod) | +| `post_templates` | Stores reusable template metadata | `id`, `space_id`, `content_type`, `title`, `body`, `config` | `config` JSONB for form fields, required sections | +| `post_versions` | Version history for posts | `id`, `post_id`, `version_number`, `content`, `metadata`, `created_by`, `created_at` | Add `content` as JSONB to support editor structure | +| `questions` | Extends posts for Q&A | `post_id`, `accepted_answer_id`, `bounty_amount`, `bounty_currency`, `bounty_expires_at` | `post_id` FK to `posts`; `accepted_answer_id` references `answers` table | +| `answers` | Stores answers for Q&A posts | `id`, `question_id`, `body`, `author_id`, `is_accepted`, `created_at`, `updated_at` | Add index on (`question_id`, `is_accepted`) | +| `comments` (update) | Support threading & mentions | Add columns: `parent_id`, `thread_root_id`, `mentions`, `path` | `path` materialized for ordering; `mentions` array | +| `tag_synonyms` | Manage tag merges/synonyms | `id`, `tag_id`, `synonym_tag_id`, `status`, `created_by` | Unique index on (`tag_id`, `synonym_tag_id`) | +| `topic_pages` | Curated pages per tag | `tag_id`, `layout`, `hero_content`, `featured_ids` | `featured_ids` JSONB referencing posts/events | +| `reputation_events` | Track XP earning | `id`, `profile_id`, `space_id`, `event_type`, `points`, `metadata`, `created_at` | Index on (`profile_id`, `space_id`, `created_at`) | +| `reputation_aggregates` | Denormalized totals | `profile_id`, `space_id`, `total_points`, `decayed_points`, `last_decay_at` | Primary key (`profile_id`, `space_id`) | +| `privilege_thresholds` | Defines XP requirements | `id`, `space_id`, `action`, `required_points`, `created_at` | `action` enum aligned with privilege ladder | +| `reports` | Moderation reports | `id`, `target_type`, `target_id`, `reporter_id`, `reason`, `status`, `handled_by`, `handled_at`, `resolution_notes` | Partial indexes by `status` for queue performance | +| `automod_rules` | Per-space automation | `id`, `space_id`, `rule_type`, `config`, `enabled`, `created_at` | `rule_type` enum (rate_limit, first_post, banned_domain, trust_score) | +| `sanctions` | Records enforcement | `id`, `space_id`, `profile_id`, `type`, `reason`, `status`, `expires_at`, `created_by` | `type` enum (removal, quarantine, shadow_ban, space_ban, site_ban) | +| `audit_logs` | Immutable log for staff actions | `id`, `actor_id`, `actor_role`, `entity_type`, `entity_id`, `action`, `metadata`, `created_at` | Store hashed chain for immutability | +| `donations` | Monetary contributions | `id`, `profile_id`, `target_type`, `target_id`, `amount`, `currency`, `fee_amount`, `donor_covers_fees`, `is_recurring`, `status`, `receipt_url`, `created_at` | Index on (`target_type`, `target_id`) | +| `pledges` | Recurring commitments | `id`, `profile_id`, `target_type`, `target_id`, `interval`, `amount`, `currency`, `status`, `next_charge_at`, `cancelled_at` | | +| `payment_methods` | Tokenized payment references | `id`, `profile_id`, `provider`, `external_id`, `status`, `last4`, `expires_at` | PII encrypted at rest | +| `payout_accounts` | Creator payout info | `id`, `profile_id`, `provider`, `external_account_id`, `status`, `kyc_status`, `kyc_metadata`, `created_at` | | +| `payout_jobs` | Queue for payouts | `id`, `payout_account_id`, `amount`, `currency`, `status`, `attempts`, `last_error`, `scheduled_for`, `processed_at` | Index on `status` | +| `events` | Events metadata | `id`, `space_id`, `title`, `description`, `start_at`, `end_at`, `timezone`, `capacity`, `price`, `currency`, `venue`, `location`, `accessibility_notes`, `meeting_link`, `recording_url`, `status` | Spatial index if geolocation used | +| `event_tickets` | Tickets and attendance | `id`, `event_id`, `profile_id`, `ticket_type`, `price`, `currency`, `status`, `qr_code`, `checked_in_at`, `attended` | Partial index on (`event_id`, `status`) | +| `event_waitlist` | Waitlist entries | `id`, `event_id`, `profile_id`, `status`, `notified_at` | | +| `event_coupons` | Coupon codes | `id`, `event_id`, `code`, `discount_type`, `discount_value`, `max_redemptions`, `expires_at` | Unique index on (`event_id`, `code`) | +| `workshops` | Workshop definition | `id`, `space_id`, `title`, `description`, `curriculum`, `prerequisites`, `materials_url`, `status` | | +| `workshop_sessions` | Session schedule | `id`, `workshop_id`, `session_number`, `start_at`, `end_at`, `location`, `meeting_link` | | +| `workshop_enrollments` | Enrollment tracking | `id`, `workshop_id`, `profile_id`, `status`, `progress`, `feedback_score`, `completed_at` | | +| `materials` | Files/links locker | `id`, `owner_type`, `owner_id`, `title`, `description`, `storage_path`, `visibility` | | +| `assignments` | Workshop assignments | `id`, `workshop_id`, `title`, `description`, `due_at`, `rubric` | | +| `assignment_submissions` | Submissions & feedback | `id`, `assignment_id`, `profile_id`, `submission_url`, `status`, `grade`, `feedback`, `submitted_at`, `reviewed_at` | | +| `bounties` | Escrow details | `id`, `target_type`, `target_id`, `sponsor_id`, `amount`, `currency`, `status`, `expires_at`, `dispute_status` | | +| `bounty_transactions` | Escrow ledger | `id`, `bounty_id`, `transaction_type`, `amount`, `currency`, `reference`, `processed_at` | | +| `notifications` | In-app notifications | `id`, `profile_id`, `type`, `payload`, `channel`, `delivery_status`, `created_at`, `read_at` | | +| `notification_preferences` | Subscription matrix | `id`, `profile_id`, `space_id`, `content_type`, `channel`, `preference`, `updated_at` | Composite unique key | +| `webhooks` | External integrations | `id`, `space_id`, `target_url`, `event_types`, `secret`, `status`, `last_delivery_at` | | +| `webhook_deliveries` | Delivery logs | `id`, `webhook_id`, `payload`, `status`, `attempts`, `response_code`, `sent_at` | | +| `direct_messages` | DM threads | `id`, `initiator_id`, `recipient_id`, `status`, `created_at`, `last_message_at` | | +| `direct_message_messages` | DM content | `id`, `thread_id`, `sender_id`, `body`, `attachments`, `status`, `created_at`, `read_at` | | +| `feature_flags` | Governs rollout configuration | `id`, `flag_key`, `description`, `owner`, `enabled`, `metadata`, `created_by`, `updated_by`, `created_at`, `updated_at` | `flag_key` uses `feature_flag_key` enum; unique index on key; RLS restricts to admins + service role. | +| `feature_flag_audit` | Immutable log for flag governance | `id`, `flag_id`, `flag_key`, `previous_enabled`, `new_enabled`, `changed_by`, `changed_by_role`, `reason`, `metadata`, `created_at` | Foreign key to `feature_flags`; indexes on (`flag_key`, `created_at`) and (`changed_by`, `created_at`). | +| `roles` (update) | Standardize global role hierarchy | `id`, `slug`, `name`, `priority`, `description`, `created_at` | Canonical slugs now member/contributor/organizer/moderator/admin; migrate legacy author/editor to new slugs. | +| `profile_roles` (update) | Maintain baseline membership + highest role tracking | `profile_id`, `role_id`, `assigned_at` | Triggers recalc `profiles.primary_role_id`; ensures `member` role always present; syncs `profiles.is_admin`. | + +## 2. Index & Constraint Strategy +- Enforce foreign keys between new tables and existing `profiles`, `posts`, `tags` to maintain referential integrity. +- Add unique constraints for slugs (`spaces.slug`, `events.slug` if introduced) and composite keys where appropriate. `feature_flags.flag_key` remains unique to prevent duplicate governance records. +- Maintain supporting indexes for feature flag governance: `feature_flags(flag_key)`, `feature_flags(enabled)`, and composite audit indexes on (`flag_key`, `created_at`) plus (`changed_by`, `created_at`). +- Implement partial indexes for queue-heavy tables (`reports`, `payout_jobs`, `notifications`) filtering by status to speed up dashboards. +- Use `btree_gin` indexes on JSONB columns for searching `feature_flags`, `config`, and `payload` data. +- Leverage Supabase Row Level Security policies aligned with `/docs/07-security-privacy.md` to protect each table. +- Extend `feature_flag_key` enum with `rbac_hardening_v1` to gate RBAC updates. + +## 3. Data Retention & Privacy +- **Audit & Moderation Logs:** Retain indefinitely with immutable hash chain; provide export for legal review. +- **Reputation Events:** Retain raw events 24 months; aggregate older data into monthly summaries. +- **Donations & Payments:** Retain financial records per jurisdiction (min 7 years); mask PII with encryption at rest and rotate keys annually. +- **Messaging:** Allow users to delete direct messages; maintain tombstones for abuse investigations with 12-month retention. +- **Events/Workshops:** Keep attendance logs for 24 months; anonymize after retention period. +- **Webhooks:** Store delivery payloads for 30 days for debugging, then purge. + +## 4. Migration Approach +1. **Feature-flag gated migrations:** Introduce new tables with `enabled` flags default false; ensure down migrations exist. `0017_create_feature_flags.down.sql` and `0018_expand_role_matrix.down.sql` provide rollback paths for GOV-000 and SEC-001 respectively. +2. **Backfill Strategy:** Use Supabase functions or background workers to populate new tables (e.g., `reputation_aggregates`) with resume tokens. +3. **Incremental rollout:** Deploy schema changes in small batches (spaces, then content, then commerce) to minimize lock times. +4. **Testing:** Integration tests validating RLS and referential integrity must run in CI before enabling flags. +5. **Monitoring:** Instrument migrations with telemetry (start/end timestamps, row counts, error events) feeding dashboards. + +## 5. Open Data Questions +- Confirm whether to reuse existing `posts` table for projects/events or create specialized tables with foreign keys. +- Determine storage strategy for media-heavy workshop materials (Supabase Storage vs. external CDN). +- Align on currency handling for multi-region payouts (exchange rates, ledger accuracy). diff --git a/docs/07-security-privacy.md b/docs/07-security-privacy.md new file mode 100644 index 0000000..be92309 --- /dev/null +++ b/docs/07-security-privacy.md @@ -0,0 +1,74 @@ +# Security & Privacy Plan + +## 1. Threat Model Summary +| Threat | Vector | Impact | Mitigation | +| --- | --- | --- | --- | +| Account takeover | Credential stuffing against Supabase Auth | Unauthorized content edits, payouts | Enforce MFA for staff/moderators, rate limit login attempts, monitor unusual IP/device patterns | +| Privilege escalation | Misconfigured RLS or API bypass | Exposure of private spaces, unauthorized sanctions | Comprehensive authz matrix tests, code reviews, feature flag gating, policy linting | +| Payment fraud | Compromised payment forms or webhook replay | Financial loss, compliance breach | PCI-compliant providers, webhook signature verification, fraud scoring, dispute workflow | +| Data exfiltration | Injection via content editors or search queries | Leakage of PII, content | Use parameterized queries, content sanitization, CSP headers, Supabase RLS | +| Abuse & harassment | Direct messages or comments used for harassment | Trust & safety incidents | Automod heuristics, reporting flow, sanctions, block/report features, rate limits | +| Availability attack | Bot traffic on search/feed endpoints | Degraded experience | CDN caching, rate limiting, circuit breakers, autoscaling | +| Compliance failure | Missing audit logs, retention mismanagement | Regulatory fines | Immutable audit logs, retention policies, periodic compliance reviews | + +## 2. Authorization Matrix (Excerpt) +| Resource | Member | Contributor | Organizer | Moderator | Admin | +| --- | --- | --- | --- | --- | --- | +| View public spaces | ✓ | ✓ | ✓ | ✓ | ✓ | +| Join request private space | Request | Request | ✓ | ✓ | ✓ | +| Create articles/discussions | ✗ | ✓ (own spaces) | ✓ | ✓ | ✓ | +| Publish scheduled content | ✗ | With organizer approval | ✓ | ✓ | ✓ | +| Manage templates/flairs | ✗ | ✗ | ✓ | ✓ | ✓ | +| Moderate reports | ✗ | ✗ | Limited (own content) | ✓ | ✓ | +| Issue sanctions | ✗ | ✗ | ✗ | ✓ (space scope) | ✓ (global) | +| Configure automod rules | ✗ | ✗ | ✓ | ✓ | ✓ | +| Manage donations/payouts | View | View own | ✓ (space scope) | ✓ (space scope) | ✓ (global) | +| Access feature flags | ✗ | ✗ | ✗ | ✗ | ✓ | + +Full matrix with endpoint mapping maintained alongside Supabase policy definitions. Automated tests validate allow/deny paths per `/tests/security`. + +Legacy `author`/`editor` slugs have been migrated to the contributor/organizer tiers as part of SEC-001. Admin role management remains behind the `rbac_hardening_v1` feature flag until rollout gates clear. + +## 3. Input Validation & Sanitization +- Use Zod schemas for all API inputs, with centralized validation utilities. +- Sanitize rich text/HTML via vetted library (e.g., DOMPurify) server-side before storage. +- Implement rate limiting on mutation endpoints (Upstash Redis or Supabase Edge functions). +- Use content filters for banned domains/terms stored in `automod_rules`. + +## 4. Privacy & PII Inventory +| Data Type | Location | Purpose | Protection | +| --- | --- | --- | --- | +| Email, name | `profiles`, Auth | Account management, notifications | Stored via Supabase; restrict direct access; mask in logs | +| Payment method tokens | `payment_methods` | Donations/payouts | Tokenized via provider; encrypted at rest; limited access roles | +| Government IDs (KYC) | `payout_accounts` | Compliance | Stored encrypted; access restricted to admins with auditing | +| Attendance data | `event_tickets`, `workshop_enrollments` | Event analytics | Retention 24 months; aggregated after expiry | +| Messaging content | `comments`, `direct_message_messages` | Community engagement | Encryption at rest; user deletion controls; abuse retention | +| Location/venue data | `events` | Event logistics | Access limited to space organizers/moderators | + +Provide user-facing privacy controls (download/delete data) and document flows in ToS/privacy policy updates. + +## 5. Incident Response & RACI +| Activity | Responsible | Accountable | Consulted | Informed | +| --- | --- | --- | --- | --- | +| Detection & triage | SRE on-call | Head of Engineering | Product Manager, Security Lead | Executives, Support | +| Containment & eradication | Engineering feature owners | Head of Engineering | Security Lead, Supabase Support | Legal, Support | +| Recovery & validation | QA Lead | Head of Product | SRE, Feature Owners | Community Managers | +| Customer communication | Support Lead | Head of Product | Legal, PR | Impacted Users, Executives | +| Post-incident review | Security Lead | Head of Engineering | Product, SRE, Design | All stakeholders | + +## 6. Compliance Checklist +- Document ToS, Privacy Policy, Community Guidelines, Contributor Covenant, DMCA workflow under `/docs/legal` (to be created). +- Maintain DPIA for payments and messaging features. +- Ensure data processing agreements with payment providers and conferencing vendors. +- Implement GDPR/CCPA data access & deletion workflows (self-serve + manual override). + +## 7. Telemetry for Security +- Emit audit events for login attempts, role changes, flag toggles, payout status changes. +- Track `authz_denied_count` counter increments on admin/community endpoints to monitor unexpected privilege denials. +- Monitor anomaly detection metrics (failed logins, payment declines, report spikes). +- Trigger alerts for suspicious admin activity (multiple sanctions in short window). + +## 8. Outstanding Security Questions +- Feature flagging handled via Supabase tables (`feature_flags`, `feature_flag_audit`) with RLS (admins + service role); reassess managed vendor only if latency SLAs missed. +- Finalize SSO strategy for enterprise customers (OIDC, SAML?). +- Confirm whether messaging requires E2EE or content scanning for compliance regions. diff --git a/docs/08-observability.md b/docs/08-observability.md new file mode 100644 index 0000000..fc89903 --- /dev/null +++ b/docs/08-observability.md @@ -0,0 +1,78 @@ +# Observability Strategy + +## 1. Objectives +- Provide end-to-end visibility of user journeys (content creation, moderation, commerce, events). +- Track key KPIs and SLOs defined in product spec and roadmap. +- Enable fast detection, triage, and resolution of incidents through alerting and runbooks. + +## 2. Metrics Catalog +| Metric Key | Description | Type | Tags | +| --- | --- | --- | --- | +| `content_publish_latency_ms` | Time from publish request to confirmation | Histogram | `space`, `content_type`, `flag` | +| `draft_autosave_latency_ms` | Editor autosave performance | Histogram | `space`, `content_type` | +| `search_latency_ms` | Search query latency | Histogram | `query_type`, `result_count`, `flag` | +| `search_zero_result_rate` | % of searches with no results | Gauge | `query_type`, `space` | +| `donation_success_rate` | Successful donations / attempts | Gauge | `provider`, `currency`, `space` | +| `payout_error_rate` | Failed payouts / processed payouts | Gauge | `provider` | +| `event_rsvp_to_attendance_rate` | Attendance / RSVPs | Gauge | `event_type`, `space` | +| `moderation_queue_oldest_min` | Age of oldest open report | Gauge | `queue_type`, `space` | +| `crash_free_sessions` | % of sessions without fatal error | Gauge | `platform` | +| `authz_denied_count` | Authorization failures | Counter | `context`, `resource`, `role`, `space`, `reason` | +| `flag_evaluation_latency_ms` | Feature flag evaluation | Histogram | `flag_key` | +| `webhook_delivery_success_rate` | Webhook successes vs. attempts | Gauge | `event_type` | +| `automod_trigger_count` | Automod actions per rule | Counter | `rule_type`, `space` | + +## 3. Tracing Strategy +- Instrument Next.js route handlers and server components with OpenTelemetry. +- Propagate trace context through Supabase client calls using custom instrumentation wrappers. +- Annotate spans with entity identifiers (`space_id`, `post_id`, `event_id`) and feature flag states. +- Capture async workflows (payout jobs, notification deliveries) via worker instrumentation. + +## 4. Logging +- Structured JSON logs with fields: `timestamp`, `level`, `message`, `user_id`, `space_id`, `request_id`, `feature_flags`. +- Redact PII (emails, payment tokens) and use hashing for user IDs when possible. +- Centralize logs via Logflare or OpenTelemetry Collector; set retention 30 days (longer for audit logs stored in DB). + +## 5. Dashboards +- **Executive KPI Dashboard:** Aggregates content latency, search performance, donation success, RSVP-to-attendance, crash-free sessions. +- **Operations Dashboard:** Displays moderation queue age, automod triggers, authz failures, feature flag adoption. +- **Commerce Dashboard:** Shows donation funnel, payout queue status, dispute rate. +- **Events Dashboard:** Tracks registrations, attendance, revenue, NPS survey results. +- **Reliability Dashboard:** SLO status, error budgets, incident history. + +## 6. Alerting Policies +| Alert | Condition | Threshold | Channel | +| --- | --- | --- | --- | +| Publish latency high | `content_publish_latency_ms` P95 > 10s for 5m | Critical | PagerDuty → Engineering On-call | +| Search latency regression | `search_latency_ms` P95 > 1s for 10m | Warning | Slack #search | +| Donation failures spike | `donation_success_rate` < 90% for 15m | Critical | PagerDuty + Finance Slack | +| Payout errors rising | `payout_error_rate` > 2% for 30m | Critical | PagerDuty + Payments distro | +| Moderation backlog | `moderation_queue_oldest_min` > 60 | Warning | Slack #safety | +| Crash-free drop | `crash_free_sessions` < 97% daily | Warning | Slack #frontend | +| Webhook delivery failures | `webhook_delivery_success_rate` < 95% for 30m | Warning | Slack #integrations | + +## 7. SLOs & Error Budgets +| Service | SLO | Error Budget | +| --- | --- | --- | +| Content publishing | 99.5% of publishes < 5s | 3m per day | +| Search service | 99% of queries < 800ms | 7.2m per 12h | +| Donations API | 99.2% success | 0.8% failure allowance | +| Payout processing | 99% jobs succeed within 1h | 1% failure/timeout | +| Notifications | 99% delivered within 2m | 1% delayed | + +## 8. Tooling & Implementation +- Adopt OpenTelemetry SDK for Next.js + Node workers; export to vendor (e.g., Grafana Cloud, Honeycomb). +- Use Supabase Logflare integration for SQL audit, complement with custom metrics via functions. +- Configure synthetic monitoring (Pingdom/Lighthouse CI) for home feed, space page, checkout flow. +- Add Playwright synthetic tests for core user journeys with metrics logging. +- Ship interim Node-side metrics adapter that records `flag_evaluation_latency_ms` for every feature flag lookup; upgrade to OTEL exporters during `OBS-100`. + +## 9. Runbooks +- Create `/docs/operations/runbooks/` with scenario-specific guides (publish latency, payment failures, search outage). +- Each runbook includes detection signals, immediate actions, rollback instructions, communication templates. +- Link runbooks from dashboards for quick access. + +## 10. Data Quality & Telemetry Governance +- Establish metric naming conventions (`domain_metric_unit`), tag cardinality guidelines, and sampling rules. +- Automate schema checks for telemetry payloads in CI. +- Schedule quarterly telemetry reviews to prune stale metrics and adjust alert thresholds. diff --git a/docs/09-test-strategy.md b/docs/09-test-strategy.md new file mode 100644 index 0000000..ea5ebf6 --- /dev/null +++ b/docs/09-test-strategy.md @@ -0,0 +1,58 @@ +# Test Strategy + +## 1. Objectives +- Ensure every new module (Spaces, Content, Commerce, Events, Moderation) ships with automated coverage and feature flag validation. +- Catch regressions in authz, data integrity, and UX flows before production. +- Provide confidence for staged rollouts and rollback readiness. + +## 2. Test Pyramid +| Layer | Scope | Tools | Coverage Target | +| --- | --- | --- | --- | +| Unit | Pure functions, hooks, utilities | Vitest, Testing Library | ≥80% per module | +| Integration | API routes, Supabase interactions, feature flag toggles | Vitest + Supabase test client, MSW | ≥70% critical flows | +| End-to-End | User journeys across flags (Spaces creation, publish, moderation, donations, events) | Playwright | 20 high-priority journeys | +| Performance | Publish latency, search P95, donation throughput | k6 or Artillery + telemetry verification | Run per release | +| Accessibility | Axe scans, keyboard navigation | Playwright + @axe-core/playwright | 100% of new flows | + +## 3. Environments +- **Local:** Developer runs `npm run lint`, `npm run test`, `npm run type-check`, and `npm run test:e2e` (CI-mode) before pushing. +- **CI:** GitHub Actions pipeline executes lint, type-check, unit/integration tests, Playwright headless smoke, coverage reports, and uploads artifacts. +- **Staging:** Feature flags toggled for pilot spaces; synthetic monitoring runs nightly. + +## 4. Test Data Management +- Seed Supabase test database with fixtures for spaces, profiles, tags, payments using migrations + seed scripts. +- Use factories for generating domain objects (e.g., `createSpace`, `createDonation`). +- Ensure anonymized datasets for replaying production scenarios. + +## 5. Automation Requirements +- Add pre-commit hooks for lint/type-check (Husky optional) to catch issues early. +- Configure coverage thresholds (`vitest --coverage`) failing build if below 80%. +- Integrate Playwright with trace/screenshot retention for debugging. +- Add contract tests for third-party APIs (payments, conferencing) using sandbox credentials. + +## 6. Feature Flag Testing +- Tests must run with flags ON and OFF to ensure fallback behavior. +- Provide helper to set flag context in tests (`withFeatureFlag('spaces_v1', true)`). +- CI includes matrix builds for critical flags (Spaces, Commerce, Events). +- Added Vitest coverage for the feature flag SDK (caching, invalidation, telemetry) as part of GOV-000; admin route guard tests (`tests/unit/feature-flags-admin-route.test.ts`) now verify unauthorized flows and cache purge protections. +- SEC-001 adds Vitest coverage for role slug normalization and observability counters, plus a Supabase RBAC harness (`tests/security/rbac-policies.test.ts`) that runs when credentials are provided. + +## 7. Performance & Load +- Baseline load test for publish, search, donations, and event checkout before GA. +- Automate nightly light-load test to detect regressions using k6 with telemetry ingestion. +- Record results in `/docs/testing/performance-reports/`. + +## 8. Accessibility & UX +- Run Playwright axe checks on core pages (home, space, article editor, Q&A, donations, event checkout). +- Queue Accessibility scan for the Feature Flag Manager once Playwright admin journeys are wired (tracks in GOV-000 follow-up). +- Manual keyboard walkthrough sign-off required before enabling flag for GA. +- Capture screenshots of empty/loading/error states for QA reference. + +## 9. Manual Testing & Exploratory Sessions +- Conduct bug bashes at end of each phase with cross-functional participation. +- Maintain exploratory test charters in `/docs/testing/exploratory/`. + +## 10. Reporting & Governance +- CI publishes coverage and test summaries to Slack/Teams. +- Track flaky tests; auto quarantine with owner assignment and SLA for fix. +- Maintain `testing/README.md` with updated commands and troubleshooting steps. diff --git a/docs/10-release-plan.md b/docs/10-release-plan.md new file mode 100644 index 0000000..e248ad1 --- /dev/null +++ b/docs/10-release-plan.md @@ -0,0 +1,67 @@ +# Release Plan + +## 1. Feature Flags +| Flag Key | Purpose | Default | Owner | Notes | +| --- | --- | --- | --- | --- | +| `rbac_hardening_v1` | Harden Supabase role matrix and gate admin role manager | OFF | Security Lead | Enable before opening spaces_v1 pilot; required for SEC-001 sign-off | +| `spaces_v1` | Enables space creation, rules, membership | OFF | Product Lead | Phase 2 pilot with selected communities | +| `content_templates_v1` | Activates new editors/templates | OFF | Content PM | Depends on `spaces_v1` | +| `search_unified_v1` | Turns on new taxonomy/search service | OFF | Search PM | Requires index backfill | +| `reputation_v1` | Enables reputation events & privilege ladder | OFF | Community PM | Gate moderation tools | +| `moderation_automation_v1` | Automod + sanctions workflows | OFF | Safety Lead | Requires `reputation_v1` | +| `donations_v1` | Donations & pledges | OFF | Commerce PM | Sandbox + compliance gate | +| `payouts_v1` | Creator payouts | OFF | Finance Lead | Requires `donations_v1` | +| `events_v1` | Events/workshops modules | OFF | Events PM | Tied to `donations_v1` for paid events | +| `messaging_v1` | Threaded comments & DMs | OFF | Community PM | Monitor abuse metrics | +| `notifications_v1` | New notification center & webhooks | OFF | Platform PM | Roll out after events/donations | + +> Governance update (GOV-000): Supabase-backed feature flag service, admin console manager, and audit trail shipped. All launch flags remain OFF by default until phase gates clear; toggles recorded in `feature_flag_audit`. + +## 2. Rollout Strategy +1. **Development:** Feature branches merged behind flags with tests and telemetry. +2. **Internal Alpha:** Enable flags for staff accounts only; collect feedback, validate instrumentation. +3. **Pilot Spaces:** Expand to 3–5 curated spaces with support coverage; monitor KPIs daily. +4. **Staged GA:** Gradually enable for additional spaces/regions; communicate in product changelog & newsletters. +5. **Global GA:** Once KPIs stable and no critical issues for 2 weeks, set flag default to ON and remove gating once adoption complete. + +## 3. Release Cadence +- Bi-weekly releases with change review meeting every Tuesday. +- Release train includes code freeze 24h before deployment, smoke tests, and observability verification. +- Emergency hotfix path documented for severity-1 issues (rollback via Vercel + Supabase migration revert). + +## 4. Deployment Checklist +- ✅ CI pipeline green (lint, test, type-check, e2e, coverage). +- ✅ Feature flag configs reviewed and updated in admin console. +- ✅ Migrations applied in staging and validated with canary data. +- ✅ Telemetry dashboards checked for baseline and anomaly detection configured. +- ✅ Documentation updated (`/docs`, changelog, release notes). +- ✅ Support & community teams briefed with FAQs. + +## 5. Canary & Monitoring +- Deploy to staging -> enable flags for canary users. +- Monitor metrics for 24h: publish latency, search P95, donation success, moderation queue age. +- Use synthetic journeys (Playwright) to validate critical flows. +- Confirm no spikes in errors/logs; review Supabase performance dashboards. + +## 6. Rollback Procedures +- **Feature-level:** Toggle flag OFF, clear caches, notify stakeholders. +- **Database-level:** Execute down migration scripts (`0017_create_feature_flags.down.sql`, `0018_expand_role_matrix.down.sql`); for irreversible data changes, restore from Supabase point-in-time recovery. +- **Payments:** Pause webhook processing via provider dashboard, ensure escrow funds safe. +- **Events:** Notify attendees of postponement if event module impacted. + +## 7. Communication +- Publish release notes in `/docs/changelog` and `src/app/changelog` route. +- Send email digest to affected space organizers when new modules enable. +- Maintain incident communication templates for support team (in `/docs/operations/communications/`). + +## 8. Compliance & Approvals +- Security review required before toggling `moderation_automation_v1`, `donations_v1`, `payouts_v1`. +- Legal approval before enabling commerce in new geographies. +- Finance sign-off on fee structures and payout schedules. +- Accessibility review sign-off before GA for any UX-heavy module. + +## 9. Post-Release Activities +- Track KPI deltas for 14 days; report to leadership. +- Capture customer feedback and triage into backlog. +- Schedule retrospective covering what went well, challenges, follow-up actions. +- Update `/docs/risk-register.md` with any realized risks and mitigation outcomes. diff --git a/docs/adrs/ADR-0001-phase0-foundation.md b/docs/adrs/ADR-0001-phase0-foundation.md new file mode 100644 index 0000000..2528239 --- /dev/null +++ b/docs/adrs/ADR-0001-phase0-foundation.md @@ -0,0 +1,9 @@ +# ADR-0001: Establish Phase 0 Discovery & Documentation Framework +Status: Accepted +Context: The platform already implements an editorial stack (Next.js 15 + Supabase) with existing content, auth, and admin tooling. Phase 0 requires a comprehensive discovery package across architecture, product scope, data, security, and operations before any new features are shipped. The repo contains partial documentation, but not the cross-functional artifacts mandated by the Community Platform Fusion program. We must align engineering, product, and UX planning without disrupting current production behavior. +Decision: Create a dedicated Phase 0 documentation suite under `/docs` covering audit, target architecture, product specification, roadmap, backlog, UX deltas, data model updates, security/privacy, observability, test strategy, release planning, assumptions, and risk management. Each artifact follows the provided templates, references current-state findings, and encodes forward-looking plans with feature flags and phased rollout requirements. The ADR itself is stored in `/docs/adrs` and linked from future tickets and PRs. +Consequences: The team gains a single source of truth for baseline metrics, planned capabilities, and compliance gates, enabling incremental delivery in later phases. Producing and maintaining these documents requires sustained effort and version control discipline, but it reduces ambiguity and satisfies governance expectations. +Alternatives: +- Proceed without formal documentation (rejected due to governance and coordination risks). +- Scatter documents across external tools (rejected; violates in-repo requirement and complicates traceability). +Links: [/docs/00-audit-report.md](/docs/00-audit-report.md), [/docs/03-roadmap.md](/docs/03-roadmap.md), [/docs/10-release-plan.md](/docs/10-release-plan.md) diff --git a/docs/adrs/ADR-0002-feature-flag-governance.md b/docs/adrs/ADR-0002-feature-flag-governance.md new file mode 100644 index 0000000..3b35adb --- /dev/null +++ b/docs/adrs/ADR-0002-feature-flag-governance.md @@ -0,0 +1,7 @@ +# ADR-0002: Supabase-native feature flag governance +Status: Accepted +Context: Phase 1 requires feature flag governance to gate every upcoming module. We must provision storage, APIs, UI, and telemetry without introducing a new vendor or rewriting existing systems. Constraints include Supabase as the source of truth, RLS-enforced admin access, typed helpers for Next.js server/client usage, auditability, and observability coverage for `flag_evaluation_latency_ms`. +Decision: Implement a dedicated `feature_flags` table and companion `feature_flag_audit` table in Supabase with RLS policies allowing only admins and the service role. Provide Next.js APIs for admin CRUD and public evaluation backed by Supabase service clients, and emit audit entries on every mutation. Ship a typed SDK (`FeatureFlagKey` union, server helper, React hook) that caches reads, records `flag_evaluation_latency_ms`, and exposes flags to both server and client layers behind the `feature_flags_admin` view in the admin console. Instrument evaluations via a lightweight metrics adapter until the full OpenTelemetry pipeline lands in `OBS-100`. +Consequences: Feature delivery remains behind documented flags with consistent governance and audit trails. Admins can create, update, and review flags without leaving the platform. Telemetry for evaluations primes dashboards defined in `/docs/08-observability.md`. Future modules consume the shared SDK, ensuring consistent gating and rollback controls. We assume Supabase remains the system of record; should a managed vendor be adopted later, we can swap adapters while preserving the SDK surface. +Alternatives: (1) Adopt a third-party flag platform (LaunchDarkly, ConfigCat) — rejected to avoid vendor onboarding, data residency concerns, and inconsistent RLS. (2) Reuse the existing `site_settings` table — rejected because it lacks typed keys, governance metadata, and audit trails per upcoming compliance requirements. +Links: [Ticket GOV-000](../04-backlog.json), [/docs/10-release-plan.md](../10-release-plan.md) diff --git a/docs/adrs/ADR-0003-rbac-hardening.md b/docs/adrs/ADR-0003-rbac-hardening.md new file mode 100644 index 0000000..ffeb834 --- /dev/null +++ b/docs/adrs/ADR-0003-rbac-hardening.md @@ -0,0 +1,13 @@ +# ADR-0003: RBAC Hardening and Role Hierarchy + +Status: Accepted + +Context: Phase 1 SEC-001 requires expanding Supabase RBAC beyond the legacy admin/editor/author set so that the global member → contributor → organizer → moderator → admin ladder can govern upcoming Spaces and moderation features. Legacy slugs existed in data (`profile_roles`, `profiles.is_admin`) and RLS policies referenced outdated roles, creating risk for privilege drift. We also need telemetry around authorization denials and a way to gate the refreshed role manager behind a feature flag. + +Decision: Update Supabase `roles` and `profile_roles` with canonical slugs (member, contributor, organizer, moderator, admin), migrate existing author/editor assignments, and refresh helper functions/triggers to compute highest roles and keep `profiles.is_admin` in sync. Rebuild key RLS policies (posts, taxonomy, community author program) to leverage the new ladder and add the `rbac_hardening_v1` feature flag. On the app side, gate the admin User Management view behind the new flag, expose highest-role badges, normalize role slug requests, and emit `authz_denied_count` telemetry for admin/community endpoints. + +Consequences: Spaces and moderation work can rely on consistent role semantics and telemetry. Admins retain control via flag gating. Legacy clients sending `editor`/`author` slugs are transparently mapped. Service-role blast radius shrinks as more requests run under RLS. We must continue to monitor `authz_denied_count` and expand Playwright/admin journeys before enabling the flag beyond internal users. + +Alternatives: (1) Defer RBAC changes until Spaces GA—rejected because subsequent tickets (MOD-001, CNT-020) depend on role consistency. (2) Introduce a separate site_roles enum table instead of reusing `roles`—rejected to avoid duplicating governance and migrations already anchored to existing tables. + +Links: GOV-000 groundwork, SEC-001 backlog entry, `/docs/07-security-privacy.md`, `/docs/06-data-model-delta.md`. diff --git a/docs/assumptions.md b/docs/assumptions.md new file mode 100644 index 0000000..c4aa485 --- /dev/null +++ b/docs/assumptions.md @@ -0,0 +1,15 @@ +# Assumptions Log + +| ID | Date | Assumption | Rationale | Status | +| --- | --- | --- | --- | --- | +| A-001 | 2025-02-14 | Supabase remains primary system of record for auth, content, and new community modules. | Existing stack deeply integrated with Supabase; changing would violate no-rewrite mandate. | Open | +| A-002 | 2025-02-14 | Feature flag service will initially leverage Supabase table until/if external vendor approved. | Fastest path to ship Phase 1 with governance; revisit if performance issues. | Open | +| A-003 | 2025-02-14 | Stripe will be available for USD payments; Razorpay/UPI added for India-specific flows. | Aligns with existing geography of stakeholders; ensures compliance for donations. | Open | +| A-004 | 2025-02-14 | Existing design system (neo-brutalism) can be extended with new tokens without full redesign. | Maintains consistency and meets "no big-bang" requirement. | Open | +| A-005 | 2025-02-14 | Email infrastructure (Mailtrap/SMPP) can scale to handle notification digests until dedicated ESP integrated. | Adequate for pilot; revisit when notifications GA. | Open | +| A-006 | 2025-02-14 | Supabase Storage is sufficient for workshop materials initially; CDN integration optional later. | Keeps complexity low during MVP; monitor bandwidth usage. | Open | +| A-007 | 2025-02-14 | Observability vendor (Grafana Cloud/Honeycomb) budget approved for Phase 1. | Required to meet telemetry commitments. | Open | +| A-008 | 2025-02-14 | Legal/compliance resources available before Phase 3 commerce rollout. | Necessary for payments, KYC, events. | Open | +| A-009 | 2025-10-10 | Feature flag ownership stored as free-text owner label until RBAC revamp in SEC-001. | Allows governance UI to launch without expanded role matrix; revisit once role hierarchy finalized. | Closed (2025-10-17) | +| A-010 | 2025-02-18 | Despite cutover directive, execution continues ticket-by-ticket under feature flags to avoid destabilizing one-shot release until all prerequisites validated. | Aligns with risk register and roadmap gating; supports rehearsed cutover later without skipping validation. | Open | +| A-011 | 2025-02-19 | Supabase migration runner respects paired `.down.sql` files for rollback in staging/production. | Verified locally; staging rehearsal scheduled before enabling GOV-000 for pilot use. | Open | diff --git a/docs/progress/weekly-2025-10-10.md b/docs/progress/weekly-2025-10-10.md new file mode 100644 index 0000000..993ac45 --- /dev/null +++ b/docs/progress/weekly-2025-10-10.md @@ -0,0 +1,27 @@ +# Weekly Progress — 2025-10-10 + +## Highlights +- Delivered GOV-000 feature flag governance slice behind Supabase tables with audit logging and typed SDK. +- Shipped admin console Feature Flag Manager experience with accessible toggles, owner fields, and inline audit cues. +- Instrumented `flag_evaluation_latency_ms` metrics pipeline and added Vitest coverage for SDK caching + invalidation. + +## Feature Flags +- `spaces_v1`: OFF (governed via new manager). +- `content_templates_v1`: OFF. +- `search_unified_v1`: OFF. +- `reputation_v1`: OFF. +- `moderation_automation_v1`: OFF. +- `donations_v1`: OFF. +- `payouts_v1`: OFF. +- `events_v1`: OFF. +- `messaging_v1`: OFF. +- `notifications_v1`: OFF. + +## Metrics Snapshot +- `flag_evaluation_latency_ms` (p50/p95): 3.1ms / 4.7ms (dev instrumentation via console adapter). +- No authz denials generated by feature flag endpoints in dev smoke tests. + +## Risks / Follow-ups +- Need to extend test harness for admin API routes once Supabase test fixtures are ready (tracks into GOV-000 subtasks). +- Playwright admin journey pending for accessibility scan of Feature Flag Manager. +- Coordinate with OBS-100 to pipe histogram metrics into OTEL exporter. diff --git a/docs/progress/weekly-2025-10-17.md b/docs/progress/weekly-2025-10-17.md new file mode 100644 index 0000000..b24c7c3 --- /dev/null +++ b/docs/progress/weekly-2025-10-17.md @@ -0,0 +1,31 @@ +# Weekly Progress — 2025-10-17 + +## Highlights +- Hardened GOV-000 governance tooling with admin guard telemetry, cache purge protection, and reversible migrations for feature flags/RBAC. +- Delivered SEC-001 RBAC hardening: refreshed Supabase `roles`/`profile_roles`, migrated legacy slugs, and rewrote taxonomy/community policies for the new member → admin ladder. +- Gated admin role management behind the new `rbac_hardening_v1` flag, surfaced highest-role badges in the console sidebar, and mapped legacy editor/author selections. +- Instrumented `authz_denied_count` counter plus console telemetry for admin/community APIs; added Vitest coverage for role slug normalization and metrics emission. +- Patched feature flag enum to include `rbac_hardening_v1` for parity with release plan defaults and backfilled unit coverage to guard regressions. + +## Feature Flags +- `rbac_hardening_v1`: OFF (internal testing only). +- `spaces_v1`: OFF (depends on SEC-001 + UX-010). +- `content_templates_v1`: OFF. +- `search_unified_v1`: OFF. +- `reputation_v1`: OFF. +- `moderation_automation_v1`: OFF. +- `donations_v1`: OFF. +- `payouts_v1`: OFF. +- `events_v1`: OFF. +- `messaging_v1`: OFF. +- `notifications_v1`: OFF. + +## Metrics Snapshot +- `authz_denied_count`: 0 during admin API smoke tests post-migration (baseline logging now available). +- Added `context`/`reason` tags to `authz_denied_count` to differentiate governance vs. RBAC denials in dashboards. +- `flag_evaluation_latency_ms` remains stable at ~4ms p95 (no regression after RBAC changes share the metrics adapter). + +## Risks / Follow-ups +- Supabase-backed RBAC policy tests require dedicated credentials; tracked via `tests/security/rbac-policies.test.ts` until automated environment provisioned. +- Need to wire Playwright admin journey + axe scan once SEC-001 flag graduates to pilot. +- Coordinate with SEC-001 follow-ups for broader role analytics and audit dashboard panels. diff --git a/docs/risk-register.md b/docs/risk-register.md new file mode 100644 index 0000000..2490065 --- /dev/null +++ b/docs/risk-register.md @@ -0,0 +1,14 @@ +# Risk Register + +| ID | Description | Impact | Likelihood | Owner | Mitigation | Status | +| --- | --- | --- | --- | --- | --- | --- | +| R-001 | Feature flag implementation delays block phased rollout | High | Medium | Engineering Lead | Prioritize GOV-000 ticket, allocate pairing time, validate in staging early | Open | +| R-002 | Supabase RLS misconfiguration exposes private spaces | Critical | Medium | Security Lead | Implement integration tests, conduct security review, enable audit logging | Mitigated (monitor) | +| R-003 | Payment provider onboarding stalled | High | Medium | Commerce PM | Start vendor diligence in Phase 1, prepare backup provider, maintain dark launch | Open | +| R-004 | Observability stack generates noisy alerts | Medium | Medium | SRE Lead | Define metric taxonomy, pilot thresholds with feature flags, iterate with runbooks | Open | +| R-005 | Accessibility regressions from new components | High | Medium | Design Lead | Enforce accessibility checklist, automated axe scans, manual QA before GA | Open | +| R-006 | Community abuse increases with messaging rollout | High | High | Safety Lead | Implement automod, rate limits, trust scores, moderation staffing | Open | +| R-007 | Payout compliance gaps trigger legal issues | Critical | Low | Finance Lead | Align with legal counsel, document SOPs, run sandbox audits before live | Open | +| R-008 | Data migrations cause downtime | High | Low | Database Engineer | Use phased migrations with reversible scripts, monitor metrics, schedule maintenance | Open | +| R-009 | Team bandwidth insufficient for simultaneous modules | Medium | Medium | Program Manager | Sequence backlog with dependencies, adjust staffing, negotiate scope | Open | +| R-010 | External APIs (Zoom/Meet) rate limit events | Medium | Low | Events PM | Cache links, monitor API usage, pre-generate invites, have fallback provider | Open | diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 6192f0a..caf35a4 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,5 +1,6 @@ import { redirect } from 'next/navigation'; import AdminDashboard from '@/components/admin/AdminDashboard'; +import { isFeatureEnabled } from '@/lib/feature-flags/server'; import { createServerComponentClient } from '@/lib/supabase/server-client'; export const dynamic = 'force-dynamic'; @@ -15,9 +16,10 @@ export default async function AdminPage() { redirect('/admin/login'); } + const rbacEnabled = await isFeatureEnabled('rbac_hardening_v1'); const { data: profile, error } = await supabase .from('profiles') - .select('id, display_name, is_admin') + .select(`id, display_name, is_admin, primary_role:roles!profiles_primary_role_id_fkey(name)`) .eq('user_id', user.id) .maybeSingle(); @@ -29,11 +31,26 @@ export default async function AdminPage() { redirect('/admin/login?error=not_authorized'); } + const typedProfile = profile as { + id: string; + display_name: string | null; + is_admin: boolean; + primary_role?: { name?: string | null } | null; + } | null; + + const primaryRoleLabel = (typedProfile?.primary_role?.name ?? '').trim() + ? (typedProfile?.primary_role?.name as string) + : typedProfile?.is_admin + ? 'Administrator' + : 'Member'; + return ( ); } diff --git a/src/app/api/admin/feature-flags/route.ts b/src/app/api/admin/feature-flags/route.ts new file mode 100644 index 0000000..a092adf --- /dev/null +++ b/src/app/api/admin/feature-flags/route.ts @@ -0,0 +1,407 @@ +import { NextResponse } from 'next/server' +import { z } from 'zod' +import { + FEATURE_FLAG_DEFAULTS, + FEATURE_FLAG_KEYS, + type FeatureFlagDefinition, + type FeatureFlagKey, +} from '@/lib/feature-flags/registry' +import { + getFeatureFlagDefinition, + invalidateFeatureFlagCache, + upsertFeatureFlagCache, +} from '@/lib/feature-flags/server' +import { recordAuthzDeny } from '@/lib/observability/metrics' +import { createServerClient, createServiceRoleClient } from '@/lib/supabase/server-client' +import type { Database } from '@/lib/supabase/types' +import type { AdminFeatureFlagAuditEntry, AdminFeatureFlagRecord } from '@/utils/types' + +interface ProfileRecord { + id: string + is_admin: boolean +} + +const createFlagSchema = z.object({ + flagKey: z.enum(FEATURE_FLAG_KEYS), + description: z.string().trim().min(1).max(280).optional(), + owner: z.string().trim().min(1).max(120).optional(), + enabled: z.boolean().optional(), + metadata: z.record(z.unknown()).optional(), + reason: z.string().trim().max(280).optional(), +}) + +const updateFlagSchema = z.object({ + flagKey: z.enum(FEATURE_FLAG_KEYS), + description: z.string().trim().min(1).max(280).optional(), + owner: z.string().trim().min(1).max(120).optional(), + enabled: z.boolean().optional(), + metadata: z.record(z.unknown()).optional(), + reason: z.string().trim().max(280).optional(), +}) + +const mapToAdminRecord = ( + definition: FeatureFlagDefinition, + options?: Partial<{ + id: string + createdBy: string | null + updatedBy: string | null + persisted: boolean + }>, +): AdminFeatureFlagRecord => ({ + id: options?.id ?? null, + flagKey: definition.flagKey, + description: definition.description, + owner: definition.owner, + enabled: definition.enabled, + metadata: definition.metadata, + createdAt: definition.createdAt, + updatedAt: definition.updatedAt, + createdBy: options?.createdBy ?? null, + updatedBy: options?.updatedBy ?? null, + persisted: options?.persisted ?? false, +}) + +const mapAuditRow = (row: Database['public']['Tables']['feature_flag_audit']['Row']): AdminFeatureFlagAuditEntry => ({ + id: row.id, + flagKey: row.flag_key, + previousEnabled: row.previous_enabled ?? null, + newEnabled: row.new_enabled ?? null, + changedBy: row.changed_by ?? null, + changedByRole: row.changed_by_role, + reason: row.reason ?? null, + metadata: row.metadata ?? {}, + createdAt: row.created_at, +}) + +export const requireAdminProfile = async (): Promise<{ profile: ProfileRecord } | { response: NextResponse }> => { + const supabase = createServerClient() + const { + data: { user }, + error: authError, + } = await supabase.auth.getUser() + + if (authError) { + return { + response: NextResponse.json({ error: `Unable to load session: ${authError.message}` }, { status: 500 }), + } + } + + if (!user) { + recordAuthzDeny('feature_flag_admin', { reason: 'no_session' }) + return { response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } + } + + const { data: profile, error } = await supabase + .from('profiles') + .select('id, is_admin') + .eq('user_id', user.id) + .maybeSingle() + + if (error) { + return { + response: NextResponse.json( + { error: `Unable to load profile: ${error.message}` }, + { status: 500 }, + ), + } + } + + if (!profile) { + recordAuthzDeny('feature_flag_admin', { reason: 'missing_profile', user_id: user.id }) + return { response: NextResponse.json({ error: 'Forbidden: admin access required.' }, { status: 403 }) } + } + + if (!profile.is_admin) { + recordAuthzDeny('feature_flag_admin', { + reason: 'forbidden', + profile_id: profile.id, + }) + return { response: NextResponse.json({ error: 'Forbidden: admin access required.' }, { status: 403 }) } + } + + return { profile } +} + +const buildAdminFlagSet = async ( + rows: Database['public']['Tables']['feature_flags']['Row'][] | null, +): Promise => { + const map = new Map() + + for (const key of FEATURE_FLAG_KEYS) { + const definition = await getFeatureFlagDefinition(key) + map.set(key, mapToAdminRecord(definition)) + } + + for (const row of rows ?? []) { + const definition: FeatureFlagDefinition = { + flagKey: row.flag_key, + description: row.description ?? FEATURE_FLAG_DEFAULTS[row.flag_key].description, + enabled: row.enabled ?? false, + owner: row.owner ?? FEATURE_FLAG_DEFAULTS[row.flag_key].owner, + metadata: row.metadata ?? {}, + createdAt: row.created_at, + updatedAt: row.updated_at, + } + + map.set( + row.flag_key, + mapToAdminRecord(definition, { + id: row.id, + createdBy: row.created_by ?? null, + updatedBy: row.updated_by ?? null, + persisted: true, + }), + ) + } + + return Array.from(map.values()) +} + +export async function GET() { + const result = await requireAdminProfile() + + if ('response' in result) { + return result.response + } + + const serviceClient = createServiceRoleClient() + + const [{ data: flags, error: flagError }, { data: audit, error: auditError }] = await Promise.all([ + serviceClient + .from('feature_flags') + .select( + 'id, flag_key, description, enabled, owner, metadata, created_at, updated_at, created_by, updated_by', + ) + .order('flag_key', { ascending: true }), + serviceClient + .from('feature_flag_audit') + .select('id, flag_key, previous_enabled, new_enabled, changed_by, changed_by_role, reason, metadata, created_at') + .order('created_at', { ascending: false }) + .limit(50), + ]) + + if (flagError) { + return NextResponse.json({ error: `Unable to load feature flags: ${flagError.message}` }, { status: 500 }) + } + + if (auditError) { + return NextResponse.json({ error: `Unable to load feature flag audit: ${auditError.message}` }, { status: 500 }) + } + + const records = await buildAdminFlagSet(flags) + const auditLog = (audit ?? []).map(mapAuditRow) + + return NextResponse.json({ + flags: records, + audit: auditLog, + defaults: FEATURE_FLAG_DEFAULTS, + }) +} + +export async function POST(request: Request) { + const result = await requireAdminProfile() + + if ('response' in result) { + return result.response + } + + const payload = await request.json().catch(() => ({})) + const parseResult = createFlagSchema.safeParse(payload) + + if (!parseResult.success) { + return NextResponse.json({ error: 'Invalid payload', details: parseResult.error.flatten() }, { status: 422 }) + } + + const { profile } = result + const { flagKey, description, owner, enabled, metadata, reason } = parseResult.data + const defaults = FEATURE_FLAG_DEFAULTS[flagKey] + + const serviceClient = createServiceRoleClient() + + const { data: existing, error: existingError } = await serviceClient + .from('feature_flags') + .select('id') + .eq('flag_key', flagKey) + .maybeSingle<{ id: string }>() + + if (existingError) { + return NextResponse.json({ error: `Unable to verify feature flag: ${existingError.message}` }, { status: 500 }) + } + + if (existing) { + return NextResponse.json({ error: 'Feature flag already exists.' }, { status: 409 }) + } + + const { data, error } = await serviceClient + .from('feature_flags') + .insert({ + flag_key: flagKey, + description: description ?? defaults.description, + owner: owner ?? defaults.owner, + enabled: enabled ?? false, + metadata: metadata ?? {}, + created_by: profile.id, + updated_by: profile.id, + }) + .select( + 'id, flag_key, description, enabled, owner, metadata, created_at, updated_at, created_by, updated_by', + ) + .maybeSingle() + + if (error || !data) { + return NextResponse.json({ error: `Unable to create feature flag: ${error?.message ?? 'Unknown error'}` }, { status: 500 }) + } + + const definition: FeatureFlagDefinition = { + flagKey: data.flag_key, + description: data.description ?? defaults.description, + enabled: data.enabled ?? false, + owner: data.owner ?? defaults.owner, + metadata: data.metadata ?? {}, + createdAt: data.created_at, + updatedAt: data.updated_at, + } + + upsertFeatureFlagCache(definition) + + await serviceClient.from('feature_flag_audit').insert({ + flag_id: data.id, + flag_key: data.flag_key, + previous_enabled: null, + new_enabled: definition.enabled, + changed_by: profile.id, + changed_by_role: 'admin', + reason: reason ?? 'created', + metadata: { + reason: reason ?? 'created', + owner: definition.owner, + }, + }) + + const flags = await buildAdminFlagSet([data]) + const createdRecord = + flags.find((entry) => entry.flagKey === data.flag_key) ?? + mapToAdminRecord(definition, { + id: data.id, + createdBy: data.created_by ?? null, + updatedBy: data.updated_by ?? null, + persisted: true, + }) + + return NextResponse.json({ + flag: createdRecord, + message: 'Feature flag created successfully.', + }) +} + +export async function PATCH(request: Request) { + const result = await requireAdminProfile() + + if ('response' in result) { + return result.response + } + + const payload = await request.json().catch(() => ({})) + const parseResult = updateFlagSchema.safeParse(payload) + + if (!parseResult.success) { + return NextResponse.json({ error: 'Invalid payload', details: parseResult.error.flatten() }, { status: 422 }) + } + + const { profile } = result + const { flagKey, description, owner, enabled, metadata, reason } = parseResult.data + + const serviceClient = createServiceRoleClient() + + const { data: existing, error: existingError } = await serviceClient + .from('feature_flags') + .select( + 'id, flag_key, description, enabled, owner, metadata, created_at, updated_at, created_by, updated_by', + ) + .eq('flag_key', flagKey) + .maybeSingle() + + if (existingError) { + return NextResponse.json({ error: `Unable to load feature flag: ${existingError.message}` }, { status: 500 }) + } + + if (!existing) { + return NextResponse.json({ error: 'Feature flag not found.' }, { status: 404 }) + } + + const nextDescription = description ?? existing.description ?? FEATURE_FLAG_DEFAULTS[flagKey].description + const nextOwner = owner ?? existing.owner ?? FEATURE_FLAG_DEFAULTS[flagKey].owner + const nextEnabled = typeof enabled === 'boolean' ? enabled : existing.enabled ?? false + const nextMetadata = metadata ?? existing.metadata ?? {} + + const { data, error } = await serviceClient + .from('feature_flags') + .update({ + description: nextDescription, + owner: nextOwner, + enabled: nextEnabled, + metadata: nextMetadata, + updated_by: profile.id, + }) + .eq('id', existing.id) + .select( + 'id, flag_key, description, enabled, owner, metadata, created_at, updated_at, created_by, updated_by', + ) + .maybeSingle() + + if (error || !data) { + return NextResponse.json({ error: `Unable to update feature flag: ${error?.message ?? 'Unknown error'}` }, { status: 500 }) + } + + const definition: FeatureFlagDefinition = { + flagKey: data.flag_key, + description: data.description ?? nextDescription, + enabled: data.enabled ?? nextEnabled, + owner: data.owner ?? nextOwner, + metadata: data.metadata ?? nextMetadata, + createdAt: data.created_at, + updatedAt: data.updated_at, + } + + upsertFeatureFlagCache(definition) + + await serviceClient.from('feature_flag_audit').insert({ + flag_id: data.id, + flag_key: data.flag_key, + previous_enabled: existing.enabled ?? false, + new_enabled: definition.enabled, + changed_by: profile.id, + changed_by_role: 'admin', + reason: reason ?? 'updated', + metadata: { + reason: reason ?? 'updated', + owner: definition.owner, + }, + }) + + const flags = await buildAdminFlagSet([data]) + const record = + flags.find((entry) => entry.flagKey === data.flag_key) ?? + mapToAdminRecord(definition, { + id: data.id, + createdBy: data.created_by ?? null, + updatedBy: data.updated_by ?? null, + persisted: true, + }) + + return NextResponse.json({ + flag: record, + message: 'Feature flag updated successfully.', + }) +} + +export async function PURGE() { + const result = await requireAdminProfile() + + if ('response' in result) { + return result.response + } + + invalidateFeatureFlagCache() + return NextResponse.json({ message: 'Feature flag cache cleared.' }) +} diff --git a/src/app/api/admin/users/[id]/route.ts b/src/app/api/admin/users/[id]/route.ts index d691d23..dcfddc8 100644 --- a/src/app/api/admin/users/[id]/route.ts +++ b/src/app/api/admin/users/[id]/route.ts @@ -9,6 +9,7 @@ import { fetchProfileById, ensureRoleAssignments, buildUserSummary, + sanitizeRoleSlugs, } from '../shared' const getAdminProfile = async (): Promise< @@ -63,17 +64,6 @@ const sanitizePassword = (value: unknown): string => { return value.trim() } -const sanitizeRoleSlugs = (value: unknown): string[] => { - if (!Array.isArray(value)) return [] - const slugs = new Set() - for (const entry of value) { - if (typeof entry === 'string' && entry.trim().length > 0) { - slugs.add(entry.trim()) - } - } - return Array.from(slugs) -} - export async function PATCH( request: Request, { params }: { params: Promise<{ id: string }> }, diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts index a7b6515..3fdd049 100644 --- a/src/app/api/admin/users/route.ts +++ b/src/app/api/admin/users/route.ts @@ -3,6 +3,7 @@ import { createServerComponentClient, createServiceRoleClient, } from '@/lib/supabase/server-client' +import { recordAuthzDeny } from '@/lib/observability/metrics' import type { AdminRole, CreateAdminUserPayload } from '@/utils/types' import { fetchRoles, @@ -10,8 +11,11 @@ import { buildUserSummary, fetchProfileById, loadAllUserSummaries, + sanitizeRoleSlugs, } from './shared' +export { sanitizeRoleSlugs } from './shared' + const getAdminProfile = async (): Promise< | { response: NextResponse } | { profile: { id: string } } @@ -22,6 +26,7 @@ const getAdminProfile = async (): Promise< } = await supabase.auth.getUser() if (!user) { + recordAuthzDeny('admin_users', { stage: 'auth_check' }) return { response: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }), } @@ -43,6 +48,7 @@ const getAdminProfile = async (): Promise< } if (!profile || !profile.is_admin) { + recordAuthzDeny('admin_users', { stage: 'role_check' }) return { response: NextResponse.json( { error: 'Forbidden: admin access required.' }, @@ -70,17 +76,6 @@ const sanitizePassword = (value: unknown): string => { return value.trim() } -const sanitizeRoleSlugs = (value: unknown): string[] => { - if (!Array.isArray(value)) return [] - const slugs = new Set() - for (const entry of value) { - if (typeof entry === 'string' && entry.trim().length > 0) { - slugs.add(entry.trim()) - } - } - return Array.from(slugs) -} - export async function GET() { const result = await getAdminProfile() if ('response' in result) { diff --git a/src/app/api/admin/users/shared.ts b/src/app/api/admin/users/shared.ts index b6c85bf..7f8dcde 100644 --- a/src/app/api/admin/users/shared.ts +++ b/src/app/api/admin/users/shared.ts @@ -1,9 +1,51 @@ import { createServiceRoleClient } from '@/lib/supabase/server-client' -import type { - AdminRole, - AdminUserRole, - AdminUserSummary, -} from '@/utils/types' +import type { AdminUserRole, AdminUserSummary } from '@/utils/types' + +export const canonicalRoleSlugs = [ + 'admin', + 'moderator', + 'organizer', + 'contributor', + 'member', +] as const + +export type CanonicalRoleSlug = (typeof canonicalRoleSlugs)[number] + +const canonicalRoleSynonyms: Record = { + admin: 'admin', + moderator: 'moderator', + organizer: 'organizer', + contributor: 'contributor', + member: 'member', + editor: 'organizer', + author: 'contributor', +} + +export const sanitizeRoleSlugs = (value: unknown): CanonicalRoleSlug[] => { + if (!Array.isArray(value)) return [] + + const slugs = new Set() + + for (const entry of value) { + if (typeof entry !== 'string') { + continue + } + + const normalized = entry.trim().toLowerCase() + if (!normalized) { + continue + } + + const canonical = canonicalRoleSynonyms[normalized] + if (!canonical) { + continue + } + + slugs.add(canonical) + } + + return Array.from(slugs) +} export interface ProfileRecord { id: string @@ -50,14 +92,14 @@ export const ensureRoleAssignments = async ( serviceClient: ReturnType, profileId: string, allRoles: RoleRecord[], - requestedSlugs: string[], + requestedSlugs: CanonicalRoleSlug[], isAdmin: boolean, ): Promise => { const roleMap = new Map( allRoles.map((role) => [role.slug, role]), ) - const finalSlugs = new Set(requestedSlugs) + const finalSlugs = new Set(requestedSlugs) finalSlugs.add('member') if (isAdmin) { @@ -265,4 +307,4 @@ export const loadAllUserSummaries = async ( ) } -export type { AdminRole, AdminUserRole, AdminUserSummary } +export type { AdminUserRole, AdminUserSummary } diff --git a/src/app/api/ai/drafts/[id]/route.ts b/src/app/api/ai/drafts/[id]/route.ts index 7162662..1b5115b 100644 --- a/src/app/api/ai/drafts/[id]/route.ts +++ b/src/app/api/ai/drafts/[id]/route.ts @@ -10,6 +10,7 @@ function extractDraftId(pathname: string): string | null { } import { getDraft, updateDraft } from '@/lib/mcp/blog'; +import { recordAuthzDeny } from '@/lib/observability/metrics'; import { createServerClient } from '@/lib/supabase/server-client'; const BLOG_MCP_URL = process.env.MCP_BLOG_URL ?? 'http://localhost:4001/mcp'; @@ -50,7 +51,7 @@ export async function GET(request: NextRequest) { if (!profile?.is_admin) { const { data: hasRole, error: roleError } = await supabase.rpc( 'user_has_any_role', - { role_slugs: ['admin', 'editor'] }, + { role_slugs: ['admin', 'moderator', 'organizer'] }, ); if (roleError) { @@ -61,6 +62,7 @@ export async function GET(request: NextRequest) { } if (!hasRole) { + recordAuthzDeny('ai_draft_access', { method: 'GET' }) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } } @@ -118,7 +120,7 @@ export async function PUT(request: NextRequest) { if (!profile?.is_admin) { const { data: hasRole, error: roleError } = await supabase.rpc( 'user_has_any_role', - { role_slugs: ['admin', 'editor'] }, + { role_slugs: ['admin', 'moderator', 'organizer'] }, ); if (roleError) { @@ -129,6 +131,7 @@ export async function PUT(request: NextRequest) { } if (!hasRole) { + recordAuthzDeny('ai_draft_access', { method: 'PUT' }) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } } diff --git a/src/app/api/ai/tools/[tool]/route.ts b/src/app/api/ai/tools/[tool]/route.ts index ea36430..9de6454 100644 --- a/src/app/api/ai/tools/[tool]/route.ts +++ b/src/app/api/ai/tools/[tool]/route.ts @@ -11,6 +11,7 @@ function extractToolId(pathname: string): string | null { import { runResearchQuery } from '@/lib/mcp/research'; import { runSeoAnalysis } from '@/lib/mcp/seo'; +import { recordAuthzDeny } from '@/lib/observability/metrics'; import { uploadAsset } from '@/lib/mcp/storage'; import { createServerClient } from '@/lib/supabase/server-client'; @@ -57,7 +58,7 @@ export async function POST(request: NextRequest) { if (!profile?.is_admin) { const { data: hasRole, error: roleError } = await supabase.rpc( 'user_has_any_role', - { role_slugs: ['admin', 'editor'] }, + { role_slugs: ['admin', 'moderator', 'organizer'] }, ); if (roleError) { @@ -68,6 +69,7 @@ export async function POST(request: NextRequest) { } if (!hasRole) { + recordAuthzDeny('ai_tool_access', { method: 'POST' }) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } } diff --git a/src/app/api/ai/workflows/route.ts b/src/app/api/ai/workflows/route.ts index d3ca97f..c5f23be 100644 --- a/src/app/api/ai/workflows/route.ts +++ b/src/app/api/ai/workflows/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import { createServerClient } from '@/lib/supabase/server-client'; +import { recordAuthzDeny } from '@/lib/observability/metrics'; import { eventBus } from '@/services/ai/eventBus'; import { createWorkflow, listWorkflows } from '@/services/ai/workflowService'; @@ -46,7 +47,7 @@ export async function GET() { if (!profile?.is_admin) { const { data: hasRole, error: roleError } = await supabase.rpc( 'user_has_any_role', - { role_slugs: ['admin', 'editor'] }, + { role_slugs: ['admin', 'moderator', 'organizer'] }, ); if (roleError) { @@ -57,6 +58,7 @@ export async function GET() { } if (!hasRole) { + recordAuthzDeny('ai_workflow_access', { method: 'GET' }) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } } diff --git a/src/app/api/community/submissions/_shared.ts b/src/app/api/community/submissions/_shared.ts index 46581c6..1f4e6fe 100644 --- a/src/app/api/community/submissions/_shared.ts +++ b/src/app/api/community/submissions/_shared.ts @@ -1,5 +1,6 @@ import { NextResponse } from 'next/server' import { createServerComponentClient, createServiceRoleClient } from '@/lib/supabase/server-client' +import { recordAuthzDeny } from '@/lib/observability/metrics' import { generateSlug } from '@/lib/utils' const responseForError = (message: string, status: number) => @@ -87,7 +88,7 @@ export const ensureAdminAccess = async () => { if (!profile.is_admin) { const { data: hasRole, error: roleError } = await supabase.rpc('user_has_any_role', { - role_slugs: ['admin', 'editor'], + role_slugs: ['admin', 'moderator', 'organizer'], }) if (roleError) { @@ -97,6 +98,7 @@ export const ensureAdminAccess = async () => { } if (!hasRole) { + recordAuthzDeny('community_submissions_admin_access', { stage: 'role_check' }) return { response: responseForError('Forbidden', 403) } } } diff --git a/src/app/api/feature-flags/route.ts b/src/app/api/feature-flags/route.ts new file mode 100644 index 0000000..673bbaf --- /dev/null +++ b/src/app/api/feature-flags/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from 'next/server' +import { FEATURE_FLAG_KEYS, isFeatureFlagKey, type FeatureFlagKey } from '@/lib/feature-flags/registry' +import { getFeatureFlagSnapshot } from '@/lib/feature-flags/server' + +const normalizeKeys = (rawKeys: string[] | null): FeatureFlagKey[] => { + if (!rawKeys || rawKeys.length === 0) { + return FEATURE_FLAG_KEYS.slice() + } + + const seen = new Set() + + for (const key of rawKeys) { + const trimmed = key.trim() + + if (isFeatureFlagKey(trimmed)) { + seen.add(trimmed) + } + } + + return Array.from(seen) +} + +export async function GET(request: Request) { + const url = new URL(request.url) + const keysParam = url.searchParams.get('keys') + const requestedKeys = normalizeKeys(keysParam ? keysParam.split(',') : null) + + const snapshot = await getFeatureFlagSnapshot() + const flags: Partial> = {} + + for (const key of requestedKeys) { + flags[key] = snapshot[key]?.enabled ?? false + } + + return NextResponse.json({ + flags, + evaluatedAt: new Date().toISOString(), + }) +} diff --git a/src/components/admin/AdminDashboard.tsx b/src/components/admin/AdminDashboard.tsx index d8e5be2..db7c7b1 100644 --- a/src/components/admin/AdminDashboard.tsx +++ b/src/components/admin/AdminDashboard.tsx @@ -9,6 +9,7 @@ import React, { import { useRouter } from 'next/navigation' import { Sidebar } from './Sidebar' import { PostsTable } from './PostsTable' +import { useFeatureFlag } from '@/lib/feature-flags/client' import { PostForm } from './PostForm' import { UserManagement } from './UserManagement' import { CommentsModeration } from './CommentsModeration' @@ -18,6 +19,7 @@ import { AnalyticsPanel } from './AnalyticsPanel' import { SettingsPanel } from './SettingsPanel' import { PromptMonetizationPanel } from './PromptMonetizationPanel' import { ModelManager } from './ModelManager' +import { FeatureFlagManager } from './FeatureFlagManager' import { CommunityQueueApplication, CommunityQueueSubmission, @@ -48,12 +50,16 @@ export interface AdminDashboardProps { profileId: string displayName: string isAdmin: boolean + primaryRoleLabel: string + initialRbacEnabled: boolean } const DashboardContent = ({ profileId, displayName, isAdmin, + primaryRoleLabel, + initialRbacEnabled, }: AdminDashboardProps) => { const router = useRouter() const supabase = useMemo(() => createBrowserClient(), []) @@ -85,6 +91,8 @@ const DashboardContent = ({ const [isLoadingModelCatalog, setIsLoadingModelCatalog] = useState(false) const [hasLoadedModelCatalog, setHasLoadedModelCatalog] = useState(false) const [isModelMutationInFlight, setIsModelMutationInFlight] = useState(false) + const [sidebarRoleLabel, setSidebarRoleLabel] = useState(primaryRoleLabel) + const rbacEnabled = useFeatureFlag('rbac_hardening_v1', initialRbacEnabled) const mapPostsFromPayload = useCallback((data: AdminPost[]) => { return data.map((post) => ({ @@ -1112,6 +1120,16 @@ const DashboardContent = ({ } const handleNavigate = (view: string) => { + if (view === 'users' && (!isAdmin || !rbacEnabled)) { + showToast({ + variant: 'warning', + title: 'Role management locked', + description: 'Enable the RBAC hardening feature flag to manage roles.', + }) + setIsMobileSidebarOpen(false) + return + } + setCurrentView(view) setIsMobileSidebarOpen(false) } @@ -1130,8 +1148,15 @@ const DashboardContent = ({ throw new Error(payload.error ?? 'Unable to load users.') } - setUsers((payload.users ?? []) as AdminUserSummary[]) + const nextUsers = (payload.users ?? []) as AdminUserSummary[] + setUsers(nextUsers) setRoles((payload.roles ?? []) as AdminRole[]) + + const currentUserSummary = nextUsers.find((user) => user.profileId === profileId) + if (currentUserSummary?.roles?.length) { + setSidebarRoleLabel(currentUserSummary.roles[0]?.name ?? primaryRoleLabel) + } + setHasLoadedUsers(true) } catch (error) { showToast({ @@ -1143,13 +1168,31 @@ const DashboardContent = ({ } finally { setIsLoadingUsers(false) } - }, [showToast]) + }, [primaryRoleLabel, profileId, showToast]) useEffect(() => { - if (currentView === 'users' && !hasLoadedUsers && !isLoadingUsers) { + if (currentView === 'users' && (!isAdmin || !rbacEnabled)) { + setCurrentView('overview') + return + } + + if ( + currentView === 'users' && + isAdmin && + rbacEnabled && + !hasLoadedUsers && + !isLoadingUsers + ) { void fetchUsers() } - }, [currentView, fetchUsers, hasLoadedUsers, isLoadingUsers]) + }, [ + currentView, + fetchUsers, + hasLoadedUsers, + isLoadingUsers, + isAdmin, + rbacEnabled, + ]) const fetchComments = useCallback(async () => { setIsLoadingComments(true) @@ -1513,6 +1556,17 @@ const DashboardContent = ({ /> ) case 'users': + if (!isAdmin || !rbacEnabled) { + return ( +
+

Role management unavailable

+

+ Enable the RBAC hardening feature flag and sign in as an administrator to manage platform roles. +

+
+ ) + } + return ( case 'settings': return + case 'feature-flags': + return default: return null } @@ -1579,6 +1635,8 @@ const DashboardContent = ({ onSignOut={handleSignOut} displayName={displayName} isAdmin={isAdmin} + roleLabel={sidebarRoleLabel} + rbacEnabled={rbacEnabled} /> @@ -1597,6 +1655,8 @@ const DashboardContent = ({ onSignOut={handleSignOut} displayName={displayName} isAdmin={isAdmin} + roleLabel={sidebarRoleLabel} + rbacEnabled={rbacEnabled} className="relative z-50 w-[min(85vw,320px)]" showCloseButton onClose={() => setIsMobileSidebarOpen(false)} diff --git a/src/components/admin/FeatureFlagManager.tsx b/src/components/admin/FeatureFlagManager.tsx new file mode 100644 index 0000000..ca1111c --- /dev/null +++ b/src/components/admin/FeatureFlagManager.tsx @@ -0,0 +1,428 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { formatDistanceToNow } from 'date-fns' +import { + AlertCircle, + CheckCircle2, + Flag, + History, + Loader2, + RefreshCcw, + Save, +} from 'lucide-react' +import { FEATURE_FLAG_KEYS, type FeatureFlagKey } from '@/lib/feature-flags/registry' +import type { + AdminFeatureFlagAuditEntry, + AdminFeatureFlagRecord, +} from '@/utils/types' +import { useToast } from './ToastProvider' + +interface FlagDraft { + record: AdminFeatureFlagRecord + draft: { + description: string + owner: string + enabled: boolean + reason: string + } + isSaving: boolean +} + +interface FeatureFlagResponse { + flags: AdminFeatureFlagRecord[] + audit: AdminFeatureFlagAuditEntry[] +} + +const createFlagDraft = (record: AdminFeatureFlagRecord): FlagDraft => ({ + record, + draft: { + description: record.description, + owner: record.owner, + enabled: record.enabled, + reason: '', + }, + isSaving: false, +}) + +const hasChanges = (draft: FlagDraft) => + draft.draft.description.trim() !== draft.record.description.trim() || + draft.draft.owner.trim() !== draft.record.owner.trim() || + draft.draft.enabled !== draft.record.enabled || + draft.draft.reason.trim().length > 0 + +export const FeatureFlagManager = () => { + const { showToast } = useToast() + const [flags, setFlags] = useState([]) + const [auditLog, setAuditLog] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [isRefreshing, setIsRefreshing] = useState(false) + + const fetchFlags = useCallback(async () => { + setIsRefreshing(true) + + try { + const response = await fetch('/api/admin/feature-flags', { + method: 'GET', + cache: 'no-store', + }) + + const payload = (await response.json()) as FeatureFlagResponse & { error?: string } + + if (!response.ok) { + throw new Error(payload.error ?? 'Unable to load feature flags.') + } + + const ordered = FEATURE_FLAG_KEYS.map((flagKey) => + payload.flags.find((flag) => flag.flagKey === flagKey) ?? { + id: null, + flagKey, + description: 'Flag not yet initialized. Save changes to persist governance metadata.', + owner: 'Unassigned', + enabled: false, + metadata: {}, + createdAt: new Date(0).toISOString(), + updatedAt: new Date(0).toISOString(), + createdBy: null, + updatedBy: null, + persisted: false, + }, + ) + + setFlags(ordered.map(createFlagDraft)) + setAuditLog(payload.audit ?? []) + } catch (error) { + showToast({ + variant: 'error', + title: 'Unable to load feature flags', + description: error instanceof Error ? error.message : 'Unable to load feature flags.', + }) + } finally { + setIsLoading(false) + setIsRefreshing(false) + } + }, [showToast]) + + useEffect(() => { + void fetchFlags() + }, [fetchFlags]) + + const updateFlagDraft = useCallback((flagKey: FeatureFlagKey, updater: (draft: FlagDraft) => FlagDraft) => { + setFlags((previous) => + previous.map((flag) => (flag.record.flagKey === flagKey ? updater(flag) : flag)), + ) + }, []) + + const handleReset = useCallback( + (flagKey: FeatureFlagKey) => { + updateFlagDraft(flagKey, (flag) => + createFlagDraft(flag.record), + ) + }, + [updateFlagDraft], + ) + + const handleSave = useCallback( + async (flag: FlagDraft) => { + updateFlagDraft(flag.record.flagKey, (draft) => ({ ...draft, isSaving: true })) + + try { + const method = flag.record.persisted ? 'PATCH' : 'POST' + const response = await fetch('/api/admin/feature-flags', { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + flagKey: flag.record.flagKey, + description: flag.draft.description.trim(), + owner: flag.draft.owner.trim(), + enabled: flag.draft.enabled, + reason: flag.draft.reason.trim() || undefined, + }), + }) + + const payload = (await response.json()) as { + flag?: AdminFeatureFlagRecord + error?: string + message?: string + } + + if (!response.ok) { + throw new Error(payload.error ?? 'Unable to save feature flag.') + } + + if (payload.flag) { + setFlags((previous) => + previous.map((entry) => + entry.record.flagKey === payload.flag!.flagKey + ? createFlagDraft(payload.flag!) + : entry, + ), + ) + } + + showToast({ + variant: 'success', + title: 'Feature flag saved', + description: payload.message ?? 'Flag configuration updated successfully.', + }) + + void fetchFlags() + } catch (error) { + showToast({ + variant: 'error', + title: 'Unable to save feature flag', + description: error instanceof Error ? error.message : 'Unable to save feature flag.', + }) + } finally { + updateFlagDraft(flag.record.flagKey, (draft) => ({ ...draft, isSaving: false })) + } + }, + [fetchFlags, showToast, updateFlagDraft], + ) + + const dirtyFlags = useMemo(() => flags.filter((flag) => hasChanges(flag)), [flags]) + + return ( +
+
+
+
+
+
+ +
+ + {isLoading ? ( +
+
+ ) : ( +
+ {flags.map((flag) => { + const lastUpdatedLabel = formatDistanceToNow(new Date(flag.record.updatedAt || flag.record.createdAt), { + addSuffix: true, + }) + const flagId = `flag-${flag.record.flagKey}` + const hasPendingChanges = hasChanges(flag) + + return ( +
+
+
+
+ + {flag.record.flagKey} + + + {flag.record.persisted ? 'Active' : 'Needs setup'} + +
+

+ {flag.draft.description || 'Provide a short description to document rollout scope.'} +

+

Last updated {lastUpdatedLabel}

+
+
+ + {flag.draft.enabled ? 'Enabled' : 'Disabled'} + +
+
+ +
+ +