Files
descrybe/docs/plan-permissions/07-custom-enable-all.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

2.8 KiB

07 — Custom packages: enable-all / disable-all / global sections (agent 7/10)

Behavior

Custom detection (IsCustomPackage)

Matches existing product semantics — not is_custom alone:

Case Result
plans.is_custom = true (includes seeded Enterprise) custom
Plan name not in public ladder (IsPublicProductPlan = false: A1, Merkur, admin deals, …) custom
Free / Starter / Growth / Business with is_custom=false standard

Public listing still uses IsPublicProductPlan / ListPublicPlans. Feature defaults use IsCustomPackage.

On custom package create (UpsertPlan, id == 0)

  1. Non-public names force is_custom = true (client-deal alignment).
  2. If the request omits features, materialize all registry keys = true (AllRegistryFeatures(true)).
  3. If the request provides features (even {}), that map is respected (replace semantics).

Updates (id > 0) do not auto-rewrite features when features is omitted.

Per-plan helpers

Service HTTP
EnableAllPlanFeatures(planID) POST /api/admin/plans/{planID}/features/enable-all
DisableAllPlanFeatures(planID) POST /api/admin/plans/{planID}/features/disable-all

Both write the full registry into plans.features (all true / all false).

Global section helpers (all plans)

Service HTTP
EnableSectionForAllPlans(section) / DisableSectionForAllPlans(section) wrappers around SetSectionGate
SetSectionGate(section, enabled) PUT /api/admin/feature-gates/sections/{section} body { "enabled": bool }

Runtime: effective = plan_allows ∩ global_section ∩ global_feature (see 03-permission-contract.md).

Resolve note

Even with empty features {}, PlanAllowsFeature returns true for every key when IsCustomPackage — create-time materialization is for explicit admin UI state / enable-all parity.

Files touched (agent 7)

  • apps/api/internal/billing/custom_package_features.goIsCustomPackage, AllRegistryFeatures, section wrappers, create prep
  • apps/api/internal/billing/custom_package_features_test.go
  • apps/api/internal/billing/plan_features.go — resolve/default use IsCustomPackage; DisableAllPlanFeatures
  • apps/api/internal/billing/service.goUpsertPlan create hook
  • apps/api/internal/billing/default_plan_features_seed.go — sparse defaults use IsCustomPackage
  • apps/api/internal/httpapi/plan_features_handlers.go — disable-all handler
  • apps/api/internal/httpapi/server.go — route mount
  • apps/api/internal/httpapi/plan_features_handlers_test.go — mount coverage

Shared with agents 4/5: EnableAllPlanFeatures, SetSectionGate, migration 026_plan_features.sql, feature catalog.