Files
descrybe/docs/plan-permissions/README.md
T
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

6.0 KiB
Raw Blame History

Plan permissions — system index

Per-plan dashboard feature toggles, global section master switches, defaults for the public ladder, custom enable-all, admin UI, dashboard gating, and API enforcement.

Runtime rule:

effective(feature) = plan_allows(feature)
  AND global_section_enabled(section(feature))
  AND global_feature_enabled(feature)

Meters (max_products, credit wallet, can_use_ai) stay as today and compose with feature keys.


Doc map (agents 110)

Doc Owner What it covers
01-dashboard-feature-catalog.md 1 Every tab/section + suggested defaults
01-feature-keys.json 1 Machine-readable registry
02-plans-permissions-current.md 2 How plans/entitlements worked before
02-extension-points.json 2 Files/symbols to extend
03-permission-contract.md / .json 3 Normative contract
04-backend-model.md 4 Schema + billing service
05-api.md / 05-openapi-fragment.yaml 5 Admin + tenant HTTP surface
06-defaults-matrix.md / .json 6 Default matrix per plan
07-custom-enable-all.md 7 Custom create + enable/disable-all
08-admin-ui.md 8 /admin/billing → Permissions tab
09-dashboard-gating.md 9 User nav/route/action gating
10-enforcement.md 10 API AssertFeature wiring + tests

How an admin configures it

  1. Sign in as platform adminPlatform billing (/admin/billing).
  2. Open the Permissions tab (PlanPermissionsPanel).
  3. Per plan: select a package → toggle feature keys → Enable all / Disable all.
  4. Global: flip a section master (e.g. disable marketing for all plans) via section controls / PUT /api/admin/feature-gates/sections/{section}.
  5. Tenant UI reads features from /api/auth/me (credits) and /api/billing/capabilities.
  6. Sensitive API mutations call billing.AssertFeature / AssertProcessingFeatures and return 402 { "error":"feature_disabled", "code":"plan_gate", "feature":"…", "upgrade_url":"/pricing" }.

API cheat sheet

Action Endpoint
Tenant capabilities GET /api/billing/capabilities
Credits + features GET /api/billing/credits / GET /api/auth/me
Plan feature editor GET|PUT /api/admin/plans/{id}/features
Enable / disable all on a plan POST …/features/enable-all | disable-all
Global gates GET|PUT /api/admin/feature-gates
Section for all plans PUT /api/admin/feature-gates/sections/{section}

Auth: session + company for tenant; session + platform admin for admin mutations. CSRF on dashboard POSTs.


Code map

Layer Path
Migration apps/api/sql/schema/026_plan_features.sql
Catalog apps/api/internal/billing/feature_catalog.go
Resolve + CRUD apps/api/internal/billing/plan_features.go, features_api.go
Custom enable-all apps/api/internal/billing/custom_package_features.go
Seed EnsureDefaultFeatureSeeds (from EnsureDefaultPlans)
Enforcement apps/api/internal/billing/feature_enforcement.go + call sites
HTTP apps/api/internal/httpapi/plan_features_handlers.go, plan_gate.go
Admin UI PlanPermissionsPanel.svelte, admin-plan-permissions.ts
Dashboard gate plan-capabilities.ts, PlanRouteGuard.svelte, Nav.svelte

Defaults (summary)

Package Behavior
Free Core catalog/feeds/stores ON; AI / API keys / live email / campaign AI / SEO AI / BYOK OFF
Starter AI + API keys ON; BYOK OFF
Growth / Business All registry keys ON
Custom (is_custom or non-public name, incl. Enterprise) All ON (create materializes full true map)
Global gates Missing row ⇒ enabled

Verify locally

.\scripts\migrate.ps1   # applies 026_plan_features.sql
cd apps\api
go test ./internal/billing/ -count=1 -run "DefaultPlanFeatures|ResolveEffective|FeatureKey|Custom|Assert"
go test ./internal/httpapi/ -count=1 -run "PlanFeatures|PlanGate"

Manual:

  1. Platform admin → Permissions → disable marketing globally → tenant GET /api/billing/capabilities lists marketing keys in disabled_features.
  2. Free company → POST /api/processing/jobs with processing_type=title → 402 feature_disabled.
  3. Create custom plan (omit features) → all resolved_features true; enable-all / disable-all round-trip.

Post-rollout verify (2026-08-05)

Check Result
Migrate path (Windows/Laragon) .\scripts\migrate.ps1 (Makefile migrate needs bash)
026_plan_features.sql Applied — goose version 26 (BOM stripped so goose could parse the file)
go test ./internal/billing/ -count=1 PASS (ok ~0.1s)
Key files present Admin PlanPermissionsPanel.svelte + admin-plan-permissions.ts; dashboard plan-capabilities.ts, PlanRouteGuard.svelte, Nav.svelte; API plan_features_handlers.go, plan_gate.go, features_api.go

Remaining blockers (from agent 10)

  1. Not every route is AssertFeature'd — list/read and many store/feed mutations are still UI-gated only.
  2. OpenAPI — session fragment in 05-openapi-fragment.yaml; public /api/v1/openapi.yaml unchanged (optional merge).
  3. Numeric marketing limits remain documentation-only (no feature-key counters).
  4. Agent 9 UI — confirm live that nav hides after capabilities load; clear admin stub mode once APIs return 200.
  5. EPREL — capability.eprel ON for Free in matrix vs historical product copy; align separately.

Remaining blockers

See 10-enforcement.md.