Files
descrybe/docs/admin-roles-support/02-current-inventory.md
T
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

14 KiB
Raw Blame History

02 — Current inventory: admin, roles, plans (A1/legacy), support

Agent: 2/20 · Mode: read-only inventory (docs only)
Tools: codehelper project_context (short) → kickoffinvestigate/query/context; Read/Grep/Glob fallback where workspace file tools were unavailable.

Desired target (from parent brief / sibling agents 36):

Persona / package Desired access
Legacy plan (A1 + legacy-marked) Limited product nav only (Dashboard, Products, Feeds, Export Feeds, Categories, Attributes, Standard Fields, Billing, Settings). No Background Tasks / processing.monitor, stores, marketing extras
Platform admin + developer Full /admin + full ops capabilities
support_staff Support ticket queue / assign / reply only — not billing/plan mutation

1. Executive summary

Today the platform has a binary staff gate (users.is_platform_admin) and company membership roles (admin | member, plus API-key role api). There is no staff_role / support_staff / developer enum in code or schema (is_legacy, staff_role, support_staff, developerzero matches).

A1 is modeled as a client deal / custom package (non-public plan name → IsCustomPackage → default all features ON), not as a limited “legacy nav” profile. Demo tenant Local Demo Co (exA1 Slovenija) is on public Enterprise (is_custom=true), which also resolves to all-ON features.

Support desk exists (tenant + admin APIs, assignee field, agent replies) but admin support routes require full platform admin. No CSAT/rating fields found under apps/api/internal/support.


2. Role model (what exists)

2.1 Platform staff — single boolean

Symbol / field Path Notes
User.IsPlatformAdmin apps/api/internal/auth/service.go (User) JSON is_platform_admin
Service.IsPlatformAdmin apps/api/internal/auth/invites.go:209 DB: SELECT is_platform_admin FROM users WHERE id = $1 AND is_active
RequirePlatformAdmin apps/api/internal/httpapi/middleware.go:245 Session + DB check; not a client claim
checkPlatformAdmin apps/api/internal/httpapi/middleware.go:262 Test hook testPlatformAdmin or Auth.IsPlatformAdmin
requirePlatformAdmin apps/web/src/lib/admin-gate.ts:8 Client gate via GET /api/auth/me
authSession.isPlatformAdmin apps/web/src/lib/auth-session.svelte.ts UI session mirror
applyPlatformAdmins apps/api/cmd/migrator/admins.go:13 Legacy MySQL admin_usersusers.is_platform_admin
seed-demo apps/api/cmd/seed-demo/main.go Forces demo user is_platform_admin = true

Missing vs desired: admin / developer / support_staff staff roles; APIs to list/assign staff roles; middleware that allows support-only routes without full platform admin.

2.2 Company membership roles

Concept Where Values
Membership role memberships.role (auth/company handlers) admin, member (invite normalize rejects owner)
CompanyAdminAllowed middleware.go:43 admin || api
requireCompanyAdmin middleware.go:48 403 "admin required"
allowCompanyAdminOrPlatform middleware.go:58 Company admin or platform admin (cutover: all-member tenants)
Team promote/demote company_handlers.go, company_member_role_test.go Last-admin guards

Orthogonal to plan features (also noted in docs/plan-permissions/02-plans-permissions-current.md § membership).

2.3 Support message “agent” role (not staff RBAC)

Ticket messages use author_roleuser | agent | system (apps/web/src/lib/support/admin-api.ts). ReplyAsAgent (support/tickets.go:325) writes author_role='agent'. That is message authorship, not a user staff role.


3. Admin panel — web routes

All under apps/web/src/routes/admin/. Pages call requirePlatformAdmin() (except layout which only shows chrome when me.user.is_platform_admin).

Route File In AdminNav? Purpose
/admin +page.svelte Overview Analytics summary + tool cards
/admin/users users/+page.svelte Yes User list, set-password emails, dev set-password / impersonate
/admin/analytics analytics/+page.svelte Yes Tokens / credits / jobs
/admin/billing billing/+page.svelte Yes Plans, assign, credits, cycles + PlanPermissionsPanel
/admin/support support/+page.svelte Yes Ticket queue
/admin/support/[id] support/[id]/+page.svelte (via Support) Ticket detail / reply
/admin/stuck-products stuck-products/+page.svelte Yes Stuck jobs
/admin/settings settings/+page.svelte Yes Platform settings
/admin/tasks-cleanup tasks-cleanup/+page.svelte No (overview card only) Same stuck cleanup
/admin/logs logs/+page.svelte No (deep link; nav comment: no logs API) Placeholder
/admin/bootstrap bootstrap/+page.svelte No Check admin flag; cannot create admins via API
/admin/migrate-organizations migrate-organizations/+page.svelte No Migration helper UI

Nav chrome: AdminNavapps/web/src/lib/components/AdminNav.svelte (menuItems).
Layout wiring: apps/web/src/routes/+layout.svelteshowAdminNav = Boolean(me?.user?.is_platform_admin); main app Nav gets showAdmin for “Platform admin” link (adminOnly: true).


4. Admin panel — API routes

Mounted in Server.Routerapps/api/internal/httpapi/server.go:294325:

/api/admin/*  → RequireSession + RequirePlatformAdmin
Method Path Handler (approx)
GET /users handleAdminListUsers
POST /users/{id}/dev-password handleAdminDevSetPassword (non-prod)
POST /users/{id}/impersonate handleAdminDevImpersonate (non-prod)
GET /companies handleAdminListCompanies
GET /readiness handleAdminReadiness
GET /analytics handleAdminAnalytics
GET /jobs handleAdminListJobs
POST /jobs/stuck-cleanup handleAdminStuckCleanup
GET/PUT /settings platform settings
POST /settings/mail/test mail probe
GET/POST /plans list / upsert
GET/PUT /plans/{planID}/features plan feature overrides
POST /plans/{planID}/features/enable-all convenience
POST /plans/{planID}/features/disable-all convenience
GET/PUT /feature-gates global gates
PUT /feature-gates/sections/{section} section toggle
POST /plans/assign assign plan to company
POST /credits add credits
POST /billing/run-cycles renewals
POST /emails/set-password cutover emails
GET /support/tickets admin queue
GET /support/tickets/{id} admin get
POST /support/tickets/{id}/messages ReplyAsAgent
PATCH /support/tickets/{id} UpdateAdmin (status/priority/assignee)

Dev-only staff tooling (not a “developer” role): admin_dev_handlers.go — blocked when Config.IsProduction().


5. PlanPermissionsPanel & plan features

Piece Path / symbol
UI panel apps/web/src/lib/components/admin/PlanPermissionsPanel.svelte
Host page /admin/billing Permissions tab — billing/+page.svelte
Client API apps/web/src/lib/admin-plan-permissions.ts (listAdminPlansWithFeatures, loadFeatureGates, enable/disable-all, …)
Catalog apps/web/src/lib/plan-feature-catalog.ts (isDefaultPublicPlanName, sections)
Prior design doc docs/plan-permissions/08-admin-ui.md

Package kind badges (panel $derived packageKind):

  • default — public ladder name + !is_custom
  • ladder_custom — public ladder + is_custom (Enterprise)
  • customis_custom
  • deal — non-ladder name without treating as custom badge path

Stub mode when feature APIs return 404/501 (isPlanPermissionsApiUnavailable).

Backend resolve path: DefaultPlanFeaturesPlanAllowsFeatureResolveEffectiveFeatures (apps/api/internal/billing/plan_features.go). Custom packages default all catalog keys ON.


6. Plans: is_custom, public ladder, A1 / “legacy”

6.1 Public vs client deals

Symbol Path Behavior
IsPublicProductPlan billing/service.go:485 free|starter|growth|business|enterprise only
ListPublicPlans billing/service.go:600 Marketing / self-serve; excludes client deals
Admin ListPlans via /api/admin/plans All plan rows (incl. A1, Merkur, …)
EnsureDefaultPlans service.go:496 Syncs public ladder only; named deals untouched

Comments explicitly name A1, Merkur trial, legacy Basic/Professional as non-public client deals.

6.2 IsCustomPackage / is_custom

Symbol Path Rule
IsCustomPackage(name, isCustom) billing/custom_package_features.go:19 isCustom || !IsPublicProductPlan(name)
prepareCustomPackageCreateFeatures same file :49 Non-public names force IsCustom=true; empty features → all-ON map
Enterprise seed defaultPublicPlans() Public name with IsCustom: true

Tests: custom_package_features_test.go"A1", false, true (name alone ⇒ custom treatment).

6.3 Company A1 / Local Demo Co

Fact Source
Legacy MySQL company A1 Slovenija / 97e1a309-3d23-4aa2-b518-8e8d7afdfec7
Postgres id ee246275-dec0-4446-9e83-58d0c16c258a
Display name after seed Local Demo Co (docs/demo-user.md, docs/migrate-from-descrybe-new.md)
Demo plan Enterprise (is_custom=true, 1M credits) — not a plan named A1
Migrated teammate emails …@legacy.local (dev password / impersonate)

Implication: Even if a DB row named A1 exists as a client plan, feature defaults are enable-all, opposite of desired legacy limited nav. There is no is_legacy column or legacy feature profile in billing today.

6.4 Product nav gating (tenant UI)

Nav.svelte filters items via planCapabilities.can(featureKey).
processing.monitor (Background Tasks / Processing) is a normal feature key — currently ON for custom/Enterprise. Desired legacy matrix would turn it OFF (and hide stores/marketing extras).


7. Support — tenant + admin

7.1 Tenant (company-scoped)

Surface Path
UI /support, /support/new, /support/[ticketId] under apps/web/src/routes/support/
API server.go:472+GET/POST /api/support/tickets, messages, notifications
Handlers handleListSupportTickets, Create, Get, Reply — session + RequireCompany
Service ListForUser / Create / GetForUser / ReplyAsUser

Nav item: /support gated by feature support.center.

7.2 Admin / staff queue

Surface Path
UI /admin/support, /admin/support/[id]
Client apps/web/src/lib/support/admin-api.ts
API /api/admin/support/tickets* (platform admin only)
Service ListAdmin, GetAdmin, ReplyAsAgent, UpdateAdmin
Schema apps/api/sql/schema/025_support_center.sql (indexed; includes assignee_admin_user_id)

Assignee: UpdateAdmin / PATCH body assignee_admin_user_id / clear_assignee. No separate “support staff inbox” or claim-only policy — any platform admin sees the full queue.

Ratings / CSAT: not present in support package types/handlers (gap for agent 11+).


8. Gaps vs desired state

Desired Current Gap
Legacy limited nav (A1 / legacy plans) Custom/non-public → all features ON; demo A1 tenant on Enterprise all-ON Need legacy profile / is_legacy / seed matrix; map A1 (+ similar) to sparse features; turn off processing.monitor, stores, marketing extras
Admin + developer full admin Only is_platform_admin boolean Need staff role enum; map admin+developer → full /api/admin; optionally keep prod-safe “dev tools” separate from role
support_staff ticket access only Support admin APIs behind same RequirePlatformAdmin as billing Need least-privilege middleware + AdminNav subset (Support only); block plans/settings/credits
Role chips / assign staff from orgs UI Users list shows is_platform_admin badge only No assign APIs; bootstrap page cannot create admins
Support assign + staff inbox + CSAT Assignee field exists; no staff role, no CSAT Extend UpdateAdmin + UI; add rating schema
Differentiate legacy vs is_custom is_custom / deal name ⇒ enable-all Contract must separate custom deal packaging from legacy feature profile

9. Extension points (summary)

Prefer extending existing gates rather than parallel systems:

  1. Staff RBAC — extend checkPlatformAdmin / RequirePlatformAdmin (or sibling RequireStaffRole) reading a new users.staff_role (or replace boolean carefully).
  2. Legacy features — extend DefaultPlanFeatures / IsCustomPackage / seed (default_plan_features_seed.go) with a legacy profile; detection: plan name patterns (A1, …) and/or is_legacy flag.
  3. Admin navAdminNav.menuItems filtered by staff capability; support_staff → Support (+ maybe Overview read-only if needed).
  4. Support — keep /api/admin/support/* but authorize admin|developer|support_staff; optionally filter ListAdmin by assignee for least privilege.
  5. PlanPermissionsPanel — already the UI to inspect/set matrices; add Legacy badge + apply-legacy-profile action once backend exists.

Machine-readable companion: 02-extension-points.json.


  • docs/plan-permissions/02-plans-permissions-current.md — plans / features / roles orthogonality
  • docs/plan-permissions/08-admin-ui.md — PlanPermissionsPanel contract
  • docs/demo-user.md — Local Demo Co / A1 migration
  • docs/migrate-from-descrybe-new.md — A1-only migrator scope
  • Sibling agents will add 03-roles-matrix, 04-contract, 05-legacy-seed, 06-staff-roles, …