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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user