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:
@@ -0,0 +1,124 @@
|
||||
# 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 1–10)
|
||||
|
||||
| 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).
|
||||
Reference in New Issue
Block a user