tenseleyflow/shithub / 5b1a1f3

Browse files

Guard org plan entitlement boundary

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5b1a1f3abc70470be3c383148d9770525cc88a06
Parents
bb04500
Tree
93227cb

4 changed files

StatusFile+-
M Makefile 5 2
M docs/internal/billing.md 4 0
M docs/internal/security-checklist.md 1 0
A scripts/lint-org-plan-boundary.sh 41 0
Makefilemodified
@@ -2,7 +2,7 @@
22
 # Targets mirror what CI runs. The Makefile is the source of truth.
33
 
44
 .DEFAULT_GOAL := help
5
-.PHONY: help dev build test test-race lint lint-policy lint-markdown lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs fmt tidy clean ci assets install-tools version deploy deploy-check restore-drill bench-staging docs docs-serve docs-verify gen-third-party-notices audit-a11y audit-a11y-pa11y audit-a11y-axe load-test
5
+.PHONY: help dev build test test-race lint lint-policy lint-markdown lint-org-plan lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs fmt tidy clean ci assets install-tools version deploy deploy-check restore-drill bench-staging docs docs-serve docs-verify gen-third-party-notices audit-a11y audit-a11y-pa11y audit-a11y-axe load-test
66
 
77
 # Build metadata embedded into the binary via -ldflags.
88
 VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
@@ -72,7 +72,7 @@ assets: ## Copy Primer CSS into internal/web/static/ for embedding.
7272
 		echo "warn: .refs/primer-css/dist not found; run 'git clone https://github.com/primer/css .refs/primer-css' first"; \
7373
 	fi
7474
 
75
-ci: lint lint-policy lint-markdown lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs test build ## Full CI pipeline (matches .github/workflows/ci.yml).
75
+ci: lint lint-policy lint-markdown lint-org-plan lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs test build ## Full CI pipeline (matches .github/workflows/ci.yml).
7676
 	@echo "ci: ok"
7777
 
7878
 lint-policy: ## Enforce policy-package boundary (no inline auth checks in handlers/git/cmd).
@@ -81,6 +81,9 @@ lint-policy: ## Enforce policy-package boundary (no inline auth checks in handle
8181
 lint-markdown: ## Enforce markdown-package boundary (no goldmark/bluemonday outside internal/markdown).
8282
 	@scripts/lint-markdown-boundary.sh
8383
 
84
+lint-org-plan: ## Enforce paid org entitlement boundary (no direct orgs.plan feature gates).
85
+	@scripts/lint-org-plan-boundary.sh
86
+
8487
 lint-secret-logs: ## Fail when source emits log lines containing token-prefix patterns.
8588
 	@scripts/lint-secret-logs.sh
8689
 
docs/internal/billing.mdmodified
@@ -111,6 +111,10 @@ Required local concepts:
111111
 Paid feature checks must live behind a central entitlement package, not
112112
 as scattered `orgs.plan` checks in handlers.
113113
 
114
+`make lint-org-plan` enforces this boundary. Schema/sqlc plumbing may
115
+store and scan the plan value, but product behavior should ask the
116
+entitlement package whether a feature key is available.
117
+
114118
 Expected feature keys:
115119
 
116120
 - `org.secret_teams`
docs/internal/security-checklist.mdmodified
@@ -35,6 +35,7 @@ document.
3535
 | Org suspension blocks writes | `policy.Can` + `DenyOrgSuspended` | S30 |
3636
 | Repo soft-delete blocks all actions | `policy.Can` + `DenyRepoDeleted` | S15 |
3737
 | Author-self-close on issues/PRs | `policy.Can` author branch | S21/S22 |
38
+| Paid org feature gates stay behind entitlements | `scripts/lint-org-plan-boundary.sh` in `make ci` | PAYMENTS SP01 |
3839
 
3940
 ## Input handling
4041
 
scripts/lint-org-plan-boundary.shadded
@@ -0,0 +1,41 @@
1
+#!/usr/bin/env bash
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+#
4
+# Fail when paid-organization feature decisions branch directly on
5
+# orgs.plan outside the billing/entitlement boundary. Plan storage is
6
+# allowed in schema/sqlc plumbing, but handlers and domain packages
7
+# should ask the entitlement layer whether a feature is available.
8
+#
9
+# Allowed locations:
10
+#   internal/billing/...          — owns subscription state
11
+#   internal/entitlements/...     — owns feature availability
12
+#   internal/*/sqlc/...           — generated data models
13
+#   internal/migrationsfs/...     — schema definitions
14
+#   *_test.go everywhere          — tests may seed or assert plan values
15
+#
16
+# Run from `make ci`.
17
+
18
+set -euo pipefail
19
+
20
+cd "$(git rev-parse --show-toplevel)"
21
+
22
+PATTERN='OrgPlan(Free|Team|Enterprise)|\.Plan[[:space:]]*(==|!=)|orgs\.plan|plan[[:space:]]+org_plan'
23
+
24
+matches=$(git grep -nE "$PATTERN" -- \
25
+  'cmd' 'internal' \
26
+  ':!internal/billing/*' \
27
+  ':!internal/entitlements/*' \
28
+  ':!internal/*/sqlc/*' \
29
+  ':!internal/migrationsfs/*' \
30
+  ':!**/*_test.go' \
31
+  2>/dev/null || true)
32
+
33
+if [[ -n "$matches" ]]; then
34
+  echo "lint-org-plan-boundary: direct org plan feature gate outside billing/entitlements:" >&2
35
+  echo "$matches" | sed 's/^/  /' >&2
36
+  echo "" >&2
37
+  echo "Fix: add or use an entitlement feature check instead of branching on orgs.plan directly." >&2
38
+  exit 1
39
+fi
40
+
41
+echo "lint-org-plan-boundary: ok"