Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
11 KiB
03 — Roles & legacy plan matrix (agent 3/20)
Status: Design contract for admin roles + legacy cohort packaging.
Machine-readable twin: 03-roles-matrix.json.
Feature keys: align with docs/plan-permissions/01-feature-keys.json.
Plan defaults (public ladder): docs/plan-permissions/06-defaults-matrix.json.
PROBLEM
Migrated / legacy tenants (example: A1 → Local Demo Co catalog) must see a narrow product surface matching the legacy nav only — not the full v2 Operate/Setup marketing suite. Separately, platform roles need a clear permission ceiling that composes with plan feature keys.
Effective resolution
effective(feature) =
role_allows(role, feature)
AND plan_allows(company.plan, feature)
AND global_section_enabled(section(feature))
AND global_feature_enabled(feature)
- Company end-users (
legacy_user,standard_user): plan matrix is the primary gate; role is the ceiling. - Staff roles (
admin,developer,support_staff): role ceiling is wide; company plan still applies when acting as a tenant unless impersonation / platform override is explicit. - Platform admin routes (
/admin/*) are orthogonal to dashboardfeature_keys — gated byusers.is_platform_admin(and future staff flags), not by plan JSON.
ASSUMPTION: legacy_user / standard_user are product-cohort roles derived from plan name patterns (and/or an explicit plans.cohort = legacy|standard when implemented), not company membership admin|member.
ASSUMPTION: A1 company / plan example maps to the legacy plan profile (see plan name patterns below).
Legacy nav allow-list (ONLY)
These are the only user-facing nav destinations for the legacy cohort. Everything else is OFF.
| Nav label | Route | Parent feature key(s) |
|---|---|---|
| Dashboard | /dashboard |
dashboard.overview |
| Products | /products |
catalog.products |
| Feeds | /feeds |
feeds.list (+ mapping) |
| Export Feeds | /export-feeds |
feeds.export_feeds |
| Categories | /categories |
catalog.categories |
| Attributes | /attributes |
catalog.attributes |
| Standard Fields | /standard-fields |
catalog.standard_fields |
| Usage & Billing | /billing |
billing.overview |
| Settings | /settings |
settings.profile (+ settings tabs) |
Explicitly excluded from legacy
| Surface | Reason |
|---|---|
Background Tasks / Processing (processing.monitor) |
Out of legacy nav |
| Stores / Woo / Shopify | Out of scope |
| Marketing campaigns, content calendar | Out of scope |
| Brand kit / brand AI | Out of scope |
| SEO | Out of scope |
| Reviews | Out of scope |
| AI integrations / Email sending | Integration extras beyond feeds |
| Support center (tickets) | Support-heavy; not essential for legacy nav |
| Structured descriptions / vector categories | Extra catalog tools, no legacy nav |
| Platform admin chrome | Staff-only (admin / developer) |
Legacy feature allow-list (ON keys)
Canonical ON set for plan profile legacy (and role legacy_user). All other keys in 01-feature-keys.json are OFF.
Shell
shell.navigationshell.command_paletteshell.company_switchershell.tutorialshell.account_menushell.billing_recovery_banner
OFF: shell.support_notifications
Dashboard
dashboard.overviewdashboard.statsdashboard.quick_linksdashboard.recent_jobs(informational only; no Processing nav)dashboard.news_feeddashboard.activation_checklistdashboard.migrated_checklistdashboard.etl_gapsdashboard.upgrade_banners
OFF: dashboard.store_reconnect
Catalog
catalog.products(+ all product tabs)catalog.products.tab_processedcatalog.products.tab_needs_reviewcatalog.products.tab_errorcatalog.products.tab_processingcatalog.products.tab_unprocessedcatalog.products.process_categoriescatalog.products.process_attributescatalog.products.process_ai_titlescatalog.products.process_ai_descriptionscatalog.products.enrichment_reviewcatalog.products.export_selectioncatalog.products.upgrade_promptcatalog.categoriescatalog.categories.title_formulacatalog.categories.description_formulacatalog.attributescatalog.attributes.bulk_importcatalog.standard_fieldscatalog.standard_fields.groups
OFF: catalog.structured_descriptions, catalog.vector_categories
Feeds (import + export + CSV uploads needed for feeds)
feeds.listfeeds.add_urlfeeds.add_csvfeeds.syncfeeds.mappingfeeds.mapping.select_itemfeeds.mapping.map_fieldsfeeds.export_feedsfeeds.export_feeds.createfeeds.export_feeds.generatefeeds.uploads
Billing (Usage & Billing)
billing.overviewbilling.customer_portalbilling.quick_upgradebilling.plans_comparebilling.checkout
Settings
settings.profilesettings.companysettings.alertssettings.api_keyssettings.teamsettings.team_invite
Capabilities (core catalog/feeds only)
capability.sku_capcapability.ai_creditscapability.ai_processingcapability.eprelcapability.normalize_specs_fillcapability.feed_source_limitcapability.export_feed_limitcapability.storage_limitcapability.api_access
OFF: capability.campaign_ai, capability.email_live_send, capability.brand_ai_apply, capability.seo_ai_rewrite, capability.byok
Entire sections OFF for legacy
- stores.* — all keys
- processing.* —
processing.monitor - marketing.* — all keys
- integrations.* — all keys
- support.* — all keys
Plan name patterns → profile
| Pattern (case-insensitive) | Profile | Notes |
|---|---|---|
exact legacy |
legacy |
Explicit legacy package |
^a1(\b|[\s_-]) / contains a1 slovenija |
legacy |
A1 company / plan example |
company display historically A1 Slovenija (MySQL 97e1a309-…) |
legacy |
Even if renamed to Local Demo Co for demo |
exact free | starter | growth | business | enterprise |
standard (+ ladder tier) |
Public ladder |
is_custom=true and name does not match legacy patterns |
custom |
All-on defaults per plan-permissions contract |
| unknown / empty | standard / Free |
Fail closed to Free matrix |
A1 example = legacy. Treat A1’s assigned plan (or a dedicated Legacy plan row seeded for that tenant) as the legacy profile.
Roles
| Role key | Who | Plan / feature source | Ceiling |
|---|---|---|---|
legacy_user |
End-user on a legacy-pattern company/plan | legacy matrix | Legacy allow-list only |
standard_user |
End-user on public ladder | Plan matrix from 06-defaults-matrix (Free→Enterprise) |
Full catalog; plan denies Free/Starter AI/BYOK/etc. |
admin |
Platform admin (users.is_platform_admin) |
N/A for /admin/*; full dashboard keys ON |
All feature keys + AdminNav |
developer |
Engineering / ops with platform access | Same as admin + debug catalog extras always ON | All keys; intended for non-prod diagnostics |
support_staff |
Support agents | Support + read-heavy tenant assist | Support keys ON; marketing/integrations mostly OFF; no destructive platform settings |
Role notes
legacy_user
- Sees only the legacy nav allow-list.
- AI titles/descriptions + credits remain ON (legacy customers processed catalog data).
- No stores, processing monitor, marketing, brand, SEO, integrations extras, or support ticket UI.
standard_user
- Full v2 dashboard surface as allowed by their public plan (see
06-defaults-matrix.json). - Role matrix in JSON is the ceiling (all registry keys
true); Free/Starter denials still apply via plan.
admin (platform)
is_platform_admin = true.- All dashboard feature keys ON.
- Platform surfaces: Overview, Users, Analytics, Platform billing, Support, Stuck Products, Platform settings (
AdminNav.svelte). - May impersonate users (non-prod / gated admin APIs).
developer
- Same feature ceiling as
admin. - Explicitly keeps debug/extra catalog keys ON (
catalog.vector_categories,catalog.structured_descriptions). - Expected to use platform admin + API tooling; not a customer-facing role.
support_staff
- Dashboard: shell + dashboard overview/stats + support.* ON.
- Catalog/feeds/billing: read-assist — parent keys ON so staff can open customer context when impersonating; write-heavy marketing/integrations OFF.
- Platform: Support queue (
/admin/support), Users (limited), Stuck Products; not Platform settings or Platform billing write. - [UNCERTAIN] No dedicated
is_support_staffcolumn today — design assumes a future staff flag or group; until then map to a subset of platform-admin users.
Role × section summary
| Section | legacy_user | standard_user | admin | developer | support_staff |
|---|---|---|---|---|---|
| shell (core) | ON | ON | ON | ON | ON |
| shell.support_notifications | OFF | ON | ON | ON | ON |
| dashboard (core) | ON | ON | ON | ON | ON |
| dashboard.store_reconnect | OFF | ON | ON | ON | OFF |
| catalog (products/categories/attributes/standard fields) | ON | ON | ON | ON | ON† |
| catalog extras (structured/vector) | OFF | ON | ON | ON | OFF |
| feeds (+ export + uploads) | ON | ON | ON | ON | ON† |
| stores | OFF | ON | ON | ON | OFF |
| processing.monitor | OFF | ON | ON | ON | ON† |
| marketing | OFF | ON | ON | ON | OFF |
| integrations | OFF | ON | ON | ON | OFF |
| billing | ON | ON | ON | ON | ON† |
| settings | ON | ON | ON | ON | ON† |
| support | OFF | ON | ON | ON | ON |
| capabilities (core AI/SKU/feeds) | ON | plan | ON | ON | ON† |
| capabilities (campaign/email/brand/seo/byok) | OFF | plan | ON | ON | OFF |
/admin/* |
OFF | OFF | ON | ON | partial |
† support_staff: intended for assisted sessions / impersonation; not for self-serve marketing ops.
COMPOSITION with company membership
Existing company membership roles (admin | member) stay orthogonal:
| Concern | Gate |
|---|---|
| Invite / team / API keys / Stripe portal | company admin (or platform admin) |
| Which nav/features appear | plan profile + product role (legacy_user / standard_user) |
| Platform console | is_platform_admin / staff role |
Do not overload company membership admin with platform admin.
IMPLEMENTATION HOOKS (non-binding)
| Need | Likely home |
|---|---|
| Resolve legacy profile from plan name | DefaultPlanFeatures / plan name normalizer beside public ladder |
| Seed Legacy plan row | EnsureDefaultPlans + assign to A1/demo migrated tenant |
| Staff role flag | users column or staff group — [UNCERTAIN] until agent/schema decides |
| Nav filter | existing feature on Nav.svelte items + ResolveFeatures |
VERIFICATION checklist
- Legacy matrix ON keys ⊆
01-feature-keys.json - No
processing.monitor,stores.*,marketing.*,integrations.*,support.*ON for legacy - A1 /
legacyname patterns resolve to legacy profile standard_userceiling does not bypass Free/Starter denials in06-defaults-matrix- Platform
adminretains/admin/*independent of plan JSON