fix(kol): Phase 1 review — ACL lineage, expiry filters, tighter tenant scope

Addresses findings from consolidated Phase 1 code review:

- Add composite unique indexes + composite FKs so subscription_prompts and
  packages cannot reference mismatched (tenant_id, package_id, prompt_id)
  tuples, enforcing ACL lineage at the schema level.
- Add status CHECK constraints on all KOL tables as defense-in-depth.
- ListActiveSubscribersForPackage: drop meaningless tenant_id IS NOT NULL,
  add end_at expiry filter so fan-out skips expired subscribers.
- ApproveKolSubscription: add status='pending' guard to prevent double-approval.
- ListSubscriptionPromptsByTenant: join subscription/prompt/package/profile
  lifecycle so hidden/archived content no longer leaks; surface
  kol_display_name for UI.
- ListPublishedKolPackages: exclude expired subscribers from marketplace count.
- KolDashboardTrend: rewrite as CTE with day-series, returning both daily
  usage and new subscription counts.
- Remove cosmetic tenant_id IS NOT NULL from dashboard overview queries
  (the column is NOT NULL by schema).
- check_tenant_scope.sh: require tenant_id = sqlc.(n?)arg filter, reject
  tenant_id IS NOT NULL as a fake scope, maintain explicit exemption list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 08:34:12 +08:00
parent 3d4842e983
commit 70725193eb
12 changed files with 234 additions and 61 deletions
+29 -14
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# CI guard: reject tenant-scoped SQL query files missing tenant_id filters.
# CI guard: reject tenant-scoped SQL query files missing tenant_id = sqlc.(n?)arg filters.
# Files that are legitimately cross-tenant must be explicitly exempted.
# Usage: scripts/check_tenant_scope.sh
set -euo pipefail
@@ -7,30 +8,44 @@ set -euo pipefail
QUERY_DIR="internal/tenant/repository/queries"
EXIT_CODE=0
# Files that are cross-tenant by design. Each must justify itself in a header comment.
EXEMPT=(
"audit.sql"
"kol_marketplace.sql"
"kol_usage.sql"
)
is_exempt() {
local name="$1"
for e in "${EXEMPT[@]}"; do
if [[ "$name" == "$e" ]]; then
return 0
fi
done
return 1
}
for f in "$QUERY_DIR"/*.sql; do
# Skip files that don't need tenant_id (audit.sql has operator_id + tenant_id)
basename=$(basename "$f")
if [[ "$basename" == "audit.sql" ]] || [[ "$basename" == "kol_marketplace.sql" ]]; then
if is_exempt "$basename"; then
continue
fi
# Check each named query block for tenant_id
while IFS= read -r line; do
if [[ "$line" =~ ^--\ name: ]]; then
query_name="${line#-- name: }"
query_name="${query_name%% *}"
fi
done < "$f"
# Must contain at least one tenant_id = sqlc.arg(...) or sqlc.narg(...) filter.
if ! grep -Eq 'tenant_id[[:space:]]*=[[:space:]]*sqlc\.(n?arg)\(' "$f"; then
echo "ERROR: $f must filter on tenant_id = sqlc.(n?arg)(...)"
EXIT_CODE=1
fi
# Simple check: file should contain tenant_id somewhere
if ! grep -q 'tenant_id' "$f"; then
echo "ERROR: $f is missing tenant_id filter"
# Reject cosmetic "tenant_id IS NOT NULL" that bypasses actor scoping.
if grep -Eq 'tenant_id[[:space:]]+IS[[:space:]]+NOT[[:space:]]+NULL' "$f"; then
echo "ERROR: $f uses 'tenant_id IS NOT NULL' — this does not scope the actor"
EXIT_CODE=1
fi
done
if [ "$EXIT_CODE" -eq 0 ]; then
echo "All query files contain tenant_id filters."
echo "All non-exempt query files contain tenant_id = sqlc.arg filters."
fi
exit $EXIT_CODE