tenseleyflow/shithub / d92b17d

Browse files

Document entitlement authorization split

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
d92b17d421284c766f62a018dd8f862c13f97198
Parents
5b1a1f3
Tree
7d37234

1 changed file

StatusFile+-
M docs/internal/permissions.md 30 0
docs/internal/permissions.mdmodified
@@ -107,6 +107,30 @@ the verdict. Ordered from most-decisive to least:
107107
 10. **Login-required actions** (star/fork) on anonymous → deny
108108
    (`DenyAnonymous`).
109109
 
110
+## Authorization versus entitlements
111
+
112
+`policy.Can` answers only one question: is this actor allowed to
113
+perform this action on this resource under shithub's permission model?
114
+It must not decide whether an organization has paid for a feature.
115
+
116
+Paid organization checks are a second gate after authorization. The
117
+expected flow for gated writes is:
118
+
119
+1. Load the resource and run the normal policy check.
120
+2. Preserve `policy.Maybe404` behavior for private-resource denials.
121
+3. Ask the entitlement layer whether the organization has the specific
122
+   feature key.
123
+4. If the feature is unavailable, return a billing/upgrade response
124
+   without re-deriving ownership, visibility, role, or plan state in
125
+   the handler.
126
+
127
+The entitlement layer may inspect billing state and plan-derived
128
+features. Policy code, handlers, git transports, and domain packages
129
+must not branch directly on `orgs.plan` or sqlc `OrgPlan*` constants.
130
+That keeps security authorization independent from commercial product
131
+packaging, and makes downgrades/grace periods possible without
132
+rewriting role checks.
133
+
110134
 ## Existence-leak guard
111135
 
112136
 `policy.Maybe404(decision, repo, actor)` maps a denial to a status
@@ -212,3 +236,9 @@ the policy actor at the lookup wrapper):
212236
 Test files everywhere are exempt — they legitimately seed state. If a
213237
 new pattern surfaces (e.g. an issue handler reads `issue.author_id`),
214238
 extend the script accordingly.
239
+
240
+`scripts/lint-org-plan-boundary.sh` also runs in `make ci`. It fails on
241
+direct plan feature gates outside `internal/billing/`,
242
+`internal/entitlements/`, generated sqlc models, migrations, and tests.
243
+When adding a paid feature, add or use an entitlement feature key rather
244
+than comparing `OrgPlanTeam` or `OrgPlanEnterprise` at the call site.