Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
295 lines
11 KiB
Markdown
295 lines
11 KiB
Markdown
# 03 — Roles & legacy plan matrix (agent 3/20)
|
||
|
||
**Status:** Design contract for admin roles + legacy cohort packaging.
|
||
**Machine-readable twin:** [`03-roles-matrix.json`](./03-roles-matrix.json).
|
||
**Feature keys:** align with [`docs/plan-permissions/01-feature-keys.json`](../plan-permissions/01-feature-keys.json).
|
||
**Plan defaults (public ladder):** [`docs/plan-permissions/06-defaults-matrix.json`](../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 dashboard `feature_key`s — gated by `users.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.navigation`
|
||
- `shell.command_palette`
|
||
- `shell.company_switcher`
|
||
- `shell.tutorial`
|
||
- `shell.account_menu`
|
||
- `shell.billing_recovery_banner`
|
||
|
||
OFF: `shell.support_notifications`
|
||
|
||
### Dashboard
|
||
|
||
- `dashboard.overview`
|
||
- `dashboard.stats`
|
||
- `dashboard.quick_links`
|
||
- `dashboard.recent_jobs` *(informational only; no Processing nav)*
|
||
- `dashboard.news_feed`
|
||
- `dashboard.activation_checklist`
|
||
- `dashboard.migrated_checklist`
|
||
- `dashboard.etl_gaps`
|
||
- `dashboard.upgrade_banners`
|
||
|
||
OFF: `dashboard.store_reconnect`
|
||
|
||
### Catalog
|
||
|
||
- `catalog.products` (+ all product tabs)
|
||
- `catalog.products.tab_processed`
|
||
- `catalog.products.tab_needs_review`
|
||
- `catalog.products.tab_error`
|
||
- `catalog.products.tab_processing`
|
||
- `catalog.products.tab_unprocessed`
|
||
- `catalog.products.process_categories`
|
||
- `catalog.products.process_attributes`
|
||
- `catalog.products.process_ai_titles`
|
||
- `catalog.products.process_ai_descriptions`
|
||
- `catalog.products.enrichment_review`
|
||
- `catalog.products.export_selection`
|
||
- `catalog.products.upgrade_prompt`
|
||
- `catalog.categories`
|
||
- `catalog.categories.title_formula`
|
||
- `catalog.categories.description_formula`
|
||
- `catalog.attributes`
|
||
- `catalog.attributes.bulk_import`
|
||
- `catalog.standard_fields`
|
||
- `catalog.standard_fields.groups`
|
||
|
||
OFF: `catalog.structured_descriptions`, `catalog.vector_categories`
|
||
|
||
### Feeds (import + export + CSV uploads needed for feeds)
|
||
|
||
- `feeds.list`
|
||
- `feeds.add_url`
|
||
- `feeds.add_csv`
|
||
- `feeds.sync`
|
||
- `feeds.mapping`
|
||
- `feeds.mapping.select_item`
|
||
- `feeds.mapping.map_fields`
|
||
- `feeds.export_feeds`
|
||
- `feeds.export_feeds.create`
|
||
- `feeds.export_feeds.generate`
|
||
- `feeds.uploads`
|
||
|
||
### Billing (Usage & Billing)
|
||
|
||
- `billing.overview`
|
||
- `billing.customer_portal`
|
||
- `billing.quick_upgrade`
|
||
- `billing.plans_compare`
|
||
- `billing.checkout`
|
||
|
||
### Settings
|
||
|
||
- `settings.profile`
|
||
- `settings.company`
|
||
- `settings.alerts`
|
||
- `settings.api_keys`
|
||
- `settings.team`
|
||
- `settings.team_invite`
|
||
|
||
### Capabilities (core catalog/feeds only)
|
||
|
||
- `capability.sku_cap`
|
||
- `capability.ai_credits`
|
||
- `capability.ai_processing`
|
||
- `capability.eprel`
|
||
- `capability.normalize_specs_fill`
|
||
- `capability.feed_source_limit`
|
||
- `capability.export_feed_limit`
|
||
- `capability.storage_limit`
|
||
- `capability.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_staff` column 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 / `legacy` name patterns resolve to legacy profile
|
||
- [ ] `standard_user` ceiling does not bypass Free/Starter denials in `06-defaults-matrix`
|
||
- [ ] Platform `admin` retains `/admin/*` independent of plan JSON
|