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

125 lines
6.0 KiB
Markdown
Raw 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.
# 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](./01-dashboard-feature-catalog.md) | 1 | Every tab/section + suggested defaults |
| [01-feature-keys.json](./01-feature-keys.json) | 1 | Machine-readable registry |
| [02-plans-permissions-current.md](./02-plans-permissions-current.md) | 2 | How plans/entitlements worked before |
| [02-extension-points.json](./02-extension-points.json) | 2 | Files/symbols to extend |
| [03-permission-contract.md](./03-permission-contract.md) / [`.json`](./03-permission-contract.json) | 3 | Normative contract |
| [04-backend-model.md](./04-backend-model.md) | 4 | Schema + `billing` service |
| [05-api.md](./05-api.md) / [05-openapi-fragment.yaml](./05-openapi-fragment.yaml) | 5 | Admin + tenant HTTP surface |
| [06-defaults-matrix.md](./06-defaults-matrix.md) / [`.json`](./06-defaults-matrix.json) | 6 | Default matrix per plan |
| [07-custom-enable-all.md](./07-custom-enable-all.md) | 7 | Custom create + enable/disable-all |
| [08-admin-ui.md](./08-admin-ui.md) | 8 | `/admin/billing` → Permissions tab |
| [09-dashboard-gating.md](./09-dashboard-gating.md) | 9 | User nav/route/action gating |
| [10-enforcement.md](./10-enforcement.md) | 10 | API AssertFeature wiring + tests |
---
## How an admin configures it
1. Sign in as **platform admin****Platform 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
```powershell
.\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](./10-enforcement.md#remaining-blockers).