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
@@ -44,6 +44,7 @@ WHERE generation_task_id = sqlc.arg(generation_task_id)
AND tenant_id = sqlc.arg(tenant_id)::bigint;
-- name: KolDashboardOverview :one
-- Cross-tenant aggregation: KOL owns packages across subscriber tenants.
SELECT
(SELECT COUNT(DISTINCT s.tenant_id) FROM kol_subscriptions s
WHERE s.package_id = ANY(sqlc.arg(package_ids)::bigint[]) AND s.status='active'
@@ -53,32 +54,53 @@ SELECT
AND s.status='active'
AND s.created_at >= date_trunc('month', NOW())) AS month_subs,
(SELECT COUNT(*) FROM kol_usage_logs u
WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND u.tenant_id IS NOT NULL) AS total_usage,
WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[])) AS total_usage,
(SELECT COUNT(*) FROM kol_usage_logs u
WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND u.tenant_id IS NOT NULL
AND u.created_at >= date_trunc('month', NOW())) AS month_usage,
(SELECT COUNT(*) FROM kol_usage_logs u
WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND u.tenant_id IS NOT NULL
AND u.status='failed') AS failure_count;
-- name: KolDashboardTrend :many
SELECT date_trunc('day', u.created_at)::date AS d,
COUNT(*) FILTER (WHERE u.status='completed') AS usages
FROM kol_usage_logs u
WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND u.tenant_id IS NOT NULL
AND u.created_at >= NOW() - sqlc.arg(period_days)::int * INTERVAL '1 day'
GROUP BY d
ORDER BY d ASC;
-- Returns a per-day series combining usage count and new subscription count for KOL dashboard.
WITH days AS (
SELECT generate_series(
date_trunc('day', NOW()) - (sqlc.arg(period_days)::int - 1) * INTERVAL '1 day',
date_trunc('day', NOW()),
INTERVAL '1 day'
)::date AS d
),
u AS (
SELECT date_trunc('day', created_at)::date AS d,
COUNT(*) FILTER (WHERE status = 'completed') AS usages
FROM kol_usage_logs
WHERE package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND created_at >= NOW() - sqlc.arg(period_days)::int * INTERVAL '1 day'
GROUP BY 1
),
s AS (
SELECT date_trunc('day', created_at)::date AS d,
COUNT(*) AS subscriptions
FROM kol_subscriptions
WHERE package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND status IN ('active', 'expired', 'revoked')
AND created_at >= NOW() - sqlc.arg(period_days)::int * INTERVAL '1 day'
GROUP BY 1
)
SELECT days.d,
COALESCE(u.usages, 0)::bigint AS usages,
COALESCE(s.subscriptions, 0)::bigint AS subscriptions
FROM days
LEFT JOIN u ON u.d = days.d
LEFT JOIN s ON s.d = days.d
ORDER BY days.d ASC;
-- name: KolUsageCountByPackage :many
-- Cross-tenant aggregation for KOL dashboard.
SELECT package_id, COUNT(*) AS total,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed
FROM kol_usage_logs
WHERE package_id = ANY(sqlc.arg(package_ids)::bigint[])
AND tenant_id IS NOT NULL
GROUP BY package_id;