#!/usr/bin/env bash # 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 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" ) WORKSPACE_SCOPED=( "desktop_account.sql" "desktop_client.sql" "desktop_task.sql" ) is_exempt() { local name="$1" for e in "${EXEMPT[@]}"; do if [[ "$name" == "$e" ]]; then return 0 fi done return 1 } is_workspace_scoped() { local name="$1" for e in "${WORKSPACE_SCOPED[@]}"; do if [[ "$name" == "$e" ]]; then return 0 fi done return 1 } for f in "$QUERY_DIR"/*.sql; do basename=$(basename "$f") if is_exempt "$basename"; then continue fi if is_workspace_scoped "$basename"; then # Desktop queries are scoped by workspace plus client/account identity. # Keep this list explicit so new query files still need tenant_id checks. if ! grep -Eq 'workspace_id[[:space:]]*=[[:space:]]*sqlc\.(n?arg)\(workspace_id\)' "$f"; then echo "ERROR: $f is workspace-scoped but missing workspace_id = sqlc.(n?arg)(workspace_id)" EXIT_CODE=1 fi else # 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 fi # 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 non-exempt query files contain tenant_id or approved workspace_id scope filters." fi exit $EXIT_CODE