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.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+217
View File
@@ -0,0 +1,217 @@
# 06 — Default plan feature matrix (agent 6/10)
Seeds / defaults for dashboard feature permissions. Coordinates with `01-feature-keys.json`, `03-permission-contract.md`, and backend `DefaultPlanFeatures` / `EnsureDefaultFeatureSeeds`.
Machine-readable twin: [`06-defaults-matrix.json`](./06-defaults-matrix.json).
---
## PROBLEM
Built-in packages must expose the features each plan allows; custom packages enable **all** registry keys; global section switches default **ON**.
---
## ASSUMPTIONS
1. **No `features_customized` flag.** Non-empty `plans.features` JSON = admin customization — `EnsureDefaultFeatureSeeds` / `EnsureDefaultPlans` never overwrite it. Empty `'{}'` = unset → `DefaultPlanFeatures(name, is_custom)` at resolve.
2. **Enterprise** is public ladder with `is_custom=true` → all features ON (same as named client deals).
3. **EPREL** (`capability.eprel`) is ON for all plans (matches `CanUseEPREL`); `docs/free-tier.md` still says paid-only — code wins.
4. **Meters** (SKU cap, monthly credits, feed/export/storage counts) stay on `plans` columns / marketing copy; feature keys gate UI/API access only.
5. **Global gates:** missing `platform_feature_gates` row ⇒ enabled. Seeder inserts section rows as `enabled=true` with `ON CONFLICT DO NOTHING` (preserves admin OFF).
---
## Implementation map
| Piece | Location |
|-------|----------|
| Registry keys / Free+Starter deny lists | `apps/api/internal/billing/feature_catalog.go` |
| `DefaultPlanFeatures` / resolve | `apps/api/internal/billing/plan_features.go` |
| Global section seed + sparse helpers | `apps/api/internal/billing/default_plan_features_seed.go` |
| Hooked from | `EnsureDefaultPlans``EnsureDefaultFeatureSeeds` |
| Schema | `apps/api/sql/schema/026_plan_features.sql` |
---
## Plan packaging (meters)
| Plan | SKU cap | Monthly AI credits | `is_custom` |
|------|--------:|-------------------:|:-----------:|
| Free | 50 | 0 | false |
| Starter | 100 | 100 | false |
| Plus | 400 | 400 | false |
| Growth | 1,200 | 1,200 | false |
| Business | 4,000 | 4,000 | false |
| Scale | 12,000 | 12,000 | false |
| Enterprise | unlimited | 1,000,000 | **true** |
| Custom deals | deal | deal | **true** |
---
## Denied keys (sparse false)
### Free
- `capability.ai_processing`
- `capability.api_access`
- `capability.brand_ai_apply`
- `capability.byok`
- `capability.campaign_ai`
- `capability.email_live_send`
- `capability.seo_ai_rewrite`
- `catalog.products.process_ai_descriptions`
- `catalog.products.process_ai_titles`
- `integrations.ai.byok`
- `marketing.brand_ai_apply`
- `marketing.campaigns.generate_ai`
- `marketing.campaigns.send`
- `marketing.seo.ai_rewrite`
- `settings.api_keys`
### Starter
- `capability.byok`
- `integrations.ai.byok`
### Growth / Business / Enterprise / Custom
No denials — all registry keys ON (Enterprise/Custom via `is_custom`).
---
## Full matrix
Legend: **ON** = allowed by `DefaultPlanFeatures` / custom-all-on; **OFF** = denied until upgrade or admin override.
| feature_key | Free | Starter | Growth | Business | Enterprise/Custom |
|---|:---:|:---:|:---:|:---:|:---:|
| `shell.navigation` | ON | ON | ON | ON | ON |
| `shell.command_palette` | ON | ON | ON | ON | ON |
| `shell.company_switcher` | ON | ON | ON | ON | ON |
| `shell.support_notifications` | ON | ON | ON | ON | ON |
| `shell.tutorial` | ON | ON | ON | ON | ON |
| `shell.account_menu` | ON | ON | ON | ON | ON |
| `shell.billing_recovery_banner` | ON | ON | ON | ON | ON |
| `dashboard.overview` | ON | ON | ON | ON | ON |
| `dashboard.stats` | ON | ON | ON | ON | ON |
| `dashboard.quick_links` | ON | ON | ON | ON | ON |
| `dashboard.recent_jobs` | ON | ON | ON | ON | ON |
| `dashboard.news_feed` | ON | ON | ON | ON | ON |
| `dashboard.activation_checklist` | ON | ON | ON | ON | ON |
| `dashboard.migrated_checklist` | ON | ON | ON | ON | ON |
| `dashboard.etl_gaps` | ON | ON | ON | ON | ON |
| `dashboard.store_reconnect` | ON | ON | ON | ON | ON |
| `dashboard.upgrade_banners` | ON | ON | ON | ON | ON |
| `catalog.products` | ON | ON | ON | ON | ON |
| `catalog.products.tab_processed` | ON | ON | ON | ON | ON |
| `catalog.products.tab_needs_review` | ON | ON | ON | ON | ON |
| `catalog.products.tab_error` | ON | ON | ON | ON | ON |
| `catalog.products.tab_processing` | ON | ON | ON | ON | ON |
| `catalog.products.tab_unprocessed` | ON | ON | ON | ON | ON |
| `catalog.products.process_categories` | ON | ON | ON | ON | ON |
| `catalog.products.process_attributes` | ON | ON | ON | ON | ON |
| `catalog.products.process_ai_titles` | OFF | ON | ON | ON | ON |
| `catalog.products.process_ai_descriptions` | OFF | ON | ON | ON | ON |
| `catalog.products.enrichment_review` | ON | ON | ON | ON | ON |
| `catalog.products.export_selection` | ON | ON | ON | ON | ON |
| `catalog.products.upgrade_prompt` | ON | ON | ON | ON | ON |
| `catalog.categories` | ON | ON | ON | ON | ON |
| `catalog.categories.title_formula` | ON | ON | ON | ON | ON |
| `catalog.categories.description_formula` | ON | ON | ON | ON | ON |
| `catalog.attributes` | ON | ON | ON | ON | ON |
| `catalog.attributes.bulk_import` | ON | ON | ON | ON | ON |
| `catalog.standard_fields` | ON | ON | ON | ON | ON |
| `catalog.standard_fields.groups` | ON | ON | ON | ON | ON |
| `catalog.structured_descriptions` | ON | ON | ON | ON | ON |
| `catalog.vector_categories` | ON | ON | ON | ON | ON |
| `feeds.list` | ON | ON | ON | ON | ON |
| `feeds.add_url` | ON | ON | ON | ON | ON |
| `feeds.add_csv` | ON | ON | ON | ON | ON |
| `feeds.sync` | ON | ON | ON | ON | ON |
| `feeds.mapping` | ON | ON | ON | ON | ON |
| `feeds.mapping.select_item` | ON | ON | ON | ON | ON |
| `feeds.mapping.map_fields` | ON | ON | ON | ON | ON |
| `feeds.export_feeds` | ON | ON | ON | ON | ON |
| `feeds.export_feeds.create` | ON | ON | ON | ON | ON |
| `feeds.export_feeds.generate` | ON | ON | ON | ON | ON |
| `feeds.uploads` | ON | ON | ON | ON | ON |
| `stores.hub` | ON | ON | ON | ON | ON |
| `stores.woocommerce` | ON | ON | ON | ON | ON |
| `stores.woocommerce.connection` | ON | ON | ON | ON | ON |
| `stores.woocommerce.categories` | ON | ON | ON | ON | ON |
| `stores.woocommerce.attributes` | ON | ON | ON | ON | ON |
| `stores.woocommerce.orders` | ON | ON | ON | ON | ON |
| `stores.woocommerce.reviews` | ON | ON | ON | ON | ON |
| `stores.woocommerce.settings` | ON | ON | ON | ON | ON |
| `stores.shopify` | ON | ON | ON | ON | ON |
| `stores.shopify.connection` | ON | ON | ON | ON | ON |
| `stores.shopify.orders` | ON | ON | ON | ON | ON |
| `stores.shopify.settings` | ON | ON | ON | ON | ON |
| `processing.monitor` | ON | ON | ON | ON | ON |
| `marketing.campaigns` | ON | ON | ON | ON | ON |
| `marketing.campaigns.create` | ON | ON | ON | ON | ON |
| `marketing.campaigns.generate_ai` | OFF | ON | ON | ON | ON |
| `marketing.campaigns.send` | OFF | ON | ON | ON | ON |
| `marketing.content_calendar` | ON | ON | ON | ON | ON |
| `marketing.brand_kit` | ON | ON | ON | ON | ON |
| `marketing.brand_ai_apply` | OFF | ON | ON | ON | ON |
| `marketing.seo` | ON | ON | ON | ON | ON |
| `marketing.seo.template_fill` | ON | ON | ON | ON | ON |
| `marketing.seo.ai_rewrite` | OFF | ON | ON | ON | ON |
| `marketing.reviews` | ON | ON | ON | ON | ON |
| `integrations.ai` | ON | ON | ON | ON | ON |
| `integrations.ai.byok` | OFF | OFF | ON | ON | ON |
| `integrations.email` | ON | ON | ON | ON | ON |
| `integrations.email.test` | ON | ON | ON | ON | ON |
| `integrations.email.blast` | ON | ON | ON | ON | ON |
| `billing.overview` | ON | ON | ON | ON | ON |
| `billing.customer_portal` | ON | ON | ON | ON | ON |
| `billing.quick_upgrade` | ON | ON | ON | ON | ON |
| `billing.plans_compare` | ON | ON | ON | ON | ON |
| `billing.checkout` | ON | ON | ON | ON | ON |
| `settings.profile` | ON | ON | ON | ON | ON |
| `settings.company` | ON | ON | ON | ON | ON |
| `settings.alerts` | ON | ON | ON | ON | ON |
| `settings.api_keys` | OFF | ON | ON | ON | ON |
| `settings.team` | ON | ON | ON | ON | ON |
| `settings.team_invite` | ON | ON | ON | ON | ON |
| `support.center` | ON | ON | ON | ON | ON |
| `support.ticket_create` | ON | ON | ON | ON | ON |
| `support.ticket_thread` | ON | ON | ON | ON | ON |
| `capability.sku_cap` | ON | ON | ON | ON | ON |
| `capability.ai_credits` | ON | ON | ON | ON | ON |
| `capability.ai_processing` | OFF | ON | ON | ON | ON |
| `capability.eprel` | ON | ON | ON | ON | ON |
| `capability.normalize_specs_fill` | ON | ON | ON | ON | ON |
| `capability.campaign_ai` | OFF | ON | ON | ON | ON |
| `capability.email_live_send` | OFF | ON | ON | ON | ON |
| `capability.brand_ai_apply` | OFF | ON | ON | ON | ON |
| `capability.seo_ai_rewrite` | OFF | ON | ON | ON | ON |
| `capability.feed_source_limit` | ON | ON | ON | ON | ON |
| `capability.export_feed_limit` | ON | ON | ON | ON | ON |
| `capability.storage_limit` | ON | ON | ON | ON | ON |
| `capability.api_access` | OFF | ON | ON | ON | ON |
| `capability.byok` | OFF | OFF | ON | ON | ON |
---
## Global section defaults
All section masters default **ON**: `shell`, `dashboard`, `catalog`, `feeds`, `stores`, `processing`, `marketing`, `integrations`, `billing`, `settings`, `support`, `capabilities`.
---
## Seeder behavior
1. `EnsureDefaultPlans` upserts ladder meters **without** writing `plans.features`.
2. Then `EnsureDefaultFeatureSeeds` inserts missing section gates as enabled (`ON CONFLICT DO NOTHING`).
3. Empty `features` left empty — resolve expands via `DefaultPlanFeatures`.
4. Custom create/update “enable all” helpers (`EnableAllPlanFeatures`) owned with agent 7; matrix already returns all-true for `is_custom`.
---
## Verification
- Unit: `TestDefaultPlanFeaturesMatrix` (Free/Starter/Growth/Custom).
- `go test ./internal/billing/ -count=1 -run DefaultPlanFeatures`