# 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`