Files
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

295 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 A1s 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