# Admin roles, legacy plans & support desk End-to-end work from the **20-agent** `admin-roles-support` swarm: legacy (A1) limited nav, platform staff roles, admin panel polish, support assign/claim + CSAT ratings. **Runtime rule (tenant features):** ``` effective(feature) = plan_allows(feature) AND global_section_enabled(section(feature)) AND global_feature_enabled(feature) ``` Staff access is **orthogonal** to plan features: `users.staff_role` + `is_platform_admin` gate `/admin` and the support desk. --- ## Doc map (agents 1–19) | Doc | Owner | What it covers | |-----|-------|----------------| | [01-ux-research.md](./01-ux-research.md) | 1 | Admin / permissions / support UX patterns | | [02-current-inventory.md](./02-current-inventory.md) / [02-extension-points.json](./02-extension-points.json) | 2 | Pre-change inventory | | [03-roles-matrix.md](./03-roles-matrix.md) / [`.json`](./03-roles-matrix.json) | 3 | Legacy allow-list + role→feature matrix | | [04-contract.md](./04-contract.md) / [`.json`](./04-contract.json) | 4 | Unified plan + staff contract | | [05-legacy-seed.md](./05-legacy-seed.md) | 5 | Legacy profile seed / A1 detection | | [06-staff-roles.md](./06-staff-roles.md) | 6 | `admin` / `developer` / `support_staff` backend | | [07-admin-shell.md](./07-admin-shell.md) | 7 | Admin layout / nav chrome | | [08-permissions-ui.md](./08-permissions-ui.md) | 8 | Billing → Permissions panel | | [09-admin-billing-ui.md](./09-admin-billing-ui.md) | 9 | Plans list / assign polish | | [10-admin-orgs-ui.md](./10-admin-orgs-ui.md) | 10 | Users/companies + staff role grant | | [11-support-design.md](./11-support-design.md) / [11-support-api-contract.json](./11-support-api-contract.json) | 11 | Support desk design | | [12-support-backend.md](./12-support-backend.md) | 12 | Tickets / claim / assign API | | [13-support-ratings.md](./13-support-ratings.md) | 13 | CSAT 1–5 ratings | | [14-support-staff-ui.md](./14-support-staff-ui.md) | 14 | Staff inbox + assign UI | | [15-user-support-ui.md](./15-user-support-ui.md) | 15 | Customer support + rate UI | | [16-legacy-nav.md](./16-legacy-nav.md) | 16 | A1 image-nav gating | | [17-security.md](./17-security.md) | 17 | AuthZ / IDOR / CSRF pass | | [18-performance.md](./18-performance.md) | 18 | Indexes / list bounds | | [19-defaults-alignment.md](./19-defaults-alignment.md) | 19 | Ladder + Legacy + staff defaults | Related: [`../plan-permissions/README.md`](../plan-permissions/README.md) (feature keys + admin Permissions API). --- ## Schema (goose) | Version | File | Purpose | |---------|------|---------| | 026 | `apps/api/sql/schema/026_plan_features.sql` | `plans.features` + global gates | | 027 | `027_capabilities_support_perf.sql` | Capabilities + support queue indexes | | 028 | `028_plan_is_legacy.sql` | `plans.is_legacy` + name backfill | | 029 | `029_staff_roles.sql` | `users.staff_role` | | 030 | `030_support_desk.sql` | CSAT table, claim queue cols, notification kinds | Apply (Windows): ```powershell .\scripts\migrate.ps1 # or from apps/api with DATABASE_URL set: go run github.com/pressly/goose/v3/cmd/goose@v3.24.3 -dir sql/schema postgres $env:DATABASE_URL up ``` Strip UTF-8 BOM from new SQL if goose errors on `\ufeff-- +goose Up`. --- ## Code map | Concern | Paths | |---------|--------| | Legacy detect/seed | `apps/api/internal/billing/legacy_plan*.go` | | Staff roles | `apps/api/internal/auth/staff*.go`, `httpapi/middleware.go`, `admin_staff_handlers.go` | | Support desk | `apps/api/internal/support/{desk,ratings,agents,tickets}.go`, `httpapi/support_*.go` | | Admin shell | `AdminNav.svelte`, `admin-nav-ui.svelte.ts`, root `+layout.svelte` | | Permissions UI | `PlanPermissionsPanel.svelte`, `admin-plan-permissions.ts` | | Orgs / staff grant | `routes/admin/users/+page.svelte`, `admin-orgs.ts` | | Legacy nav | `Nav.svelte`, `plan-capabilities.ts`, `PlanRouteGuard.svelte` | | Staff inbox | `routes/admin/support/**` | | User rate | `SupportTicketRating.svelte`, `routes/support/**` | --- ## Manual verify checklist ### A1 / Legacy nav - [ ] `goose` ≥ **30**; `seed-demo` (or `EnsureDefaultPlans`) so **Local Demo Co** is on plan **Legacy** (`is_legacy=true`) - [ ] Restart API after migrate/seed - [ ] Login as demo → Local Demo Co → sidebar shows only: Dashboard, Products, Feeds, Export Feeds, Categories, Attributes, Standard Fields, Usage & Billing, Settings - [ ] No Background Tasks / Processing, Stores, Campaigns, Brand, SEO (no empty **More** unless Platform admin) - [ ] `GET /api/billing/capabilities`: `processing.monitor` / `stores.hub` / `marketing.*` → false; nav parents → true - [ ] Deep link `/processing` → upgrade panel ### Admin panel polish - [ ] `/admin` shell: sidebar groups Monitor / Manage, dark content, mobile drawer - [ ] `/admin/billing` → Permissions: profiles (incl. Legacy one-click), section toggles - [ ] `/admin/users`: companies + assign plan + staff role dialog ### Support assign + rate - [ ] Customer: create ticket → staff reply → resolve → rate 1–5 (`SupportTicketRating`) - [ ] Staff: `/admin/support?scope=unassigned` → Claim → Mine; Unassign returns to queue - [ ] Full admin: ticket detail → Assign to staff → Apply - [ ] API: support routes under `RequireSupportDesk`; billing mutations still `RequirePlatformAdmin` ### Staff role access - [ ] Grant `support_staff` from Users → that user sees Support (+ Overview redirects to Support), not Billing/Settings - [ ] `GET /api/admin/billing` (or plans) → **403** for support_staff - [ ] `admin` / `developer` / legacy `is_platform_admin` + NULL role → full console - [ ] Cannot PATCH own `staff_role` --- ## Verify commands ```powershell .\scripts\migrate.ps1 cd apps\api go test ./internal/billing/ ./internal/auth/ ./internal/support/ -count=1 go test ./internal/httpapi/ -count=1 -run "Staff|Support|PlanFeature|PlanGate|AdminAuth|RequirePlatform|CSAT|Csat" cd ..\web npm run check ``` Optional demo alignment: ```powershell cd apps\api go run ./cmd/seed-demo -postgres $env:DATABASE_URL ``` --- ## Integration notes (agent 20) - Merged duplicate `028_*` legacy SQL into `028_plan_is_legacy.sql`; renumbered staff → **029**, support desk → **030**. - `/admin` Overview: `support_staff` redirected to `/admin/support` (nav still shows Overview). - `seed-demo` / `EnsureLegacyDefaults` assigns **Legacy** to Local Demo Co / A1 cohort (not Enterprise all-ON). ### Remaining blockers / non-goals 1. Not every tenant mutation is `AssertFeature`'d (same as plan-permissions agent 10) — UI + critical writes first. 2. Live browser QA of admin polish + CSAT requires a running `npm run dev` session and staff test user. 3. OpenAPI public fragment may still omit some support/staff paths (session docs in plan-permissions / support contract JSON).