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:
@@ -20,6 +20,7 @@ SET status = 'active',
|
||||
updated_at = NOW()
|
||||
WHERE id = $3
|
||||
AND tenant_id = $4::bigint
|
||||
AND status = 'pending'
|
||||
`
|
||||
|
||||
type ApproveKolSubscriptionParams struct {
|
||||
@@ -217,10 +218,12 @@ const listActiveSubscribersForPackage = `-- name: ListActiveSubscribersForPackag
|
||||
SELECT id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
||||
FROM kol_subscriptions
|
||||
WHERE package_id = $1
|
||||
AND tenant_id IS NOT NULL
|
||||
AND status = 'active'
|
||||
AND (end_at IS NULL OR end_at > NOW())
|
||||
`
|
||||
|
||||
// Cross-tenant read: used by platform admin for subscription fan-out.
|
||||
// Excludes expired subscriptions so new prompts are not granted to them.
|
||||
func (q *Queries) ListActiveSubscribersForPackage(ctx context.Context, packageID int64) ([]KolSubscription, error) {
|
||||
rows, err := q.db.Query(ctx, listActiveSubscribersForPackage, packageID)
|
||||
if err != nil {
|
||||
@@ -292,10 +295,25 @@ const listSubscriptionPromptsByTenant = `-- name: ListSubscriptionPromptsByTenan
|
||||
SELECT sp.id, sp.tenant_id, sp.subscription_id, sp.package_id, sp.prompt_id,
|
||||
sp.status, sp.granted_at, sp.revoked_at,
|
||||
p.name AS prompt_name, p.platform_hint, p.published_revision_no,
|
||||
k.name AS package_name
|
||||
k.name AS package_name,
|
||||
pf.display_name AS kol_display_name
|
||||
FROM kol_subscription_prompts sp
|
||||
JOIN kol_prompts p ON p.id = sp.prompt_id AND p.deleted_at IS NULL
|
||||
JOIN kol_packages k ON k.id = sp.package_id AND k.deleted_at IS NULL
|
||||
JOIN kol_subscriptions s ON s.id = sp.subscription_id
|
||||
AND s.tenant_id = sp.tenant_id
|
||||
AND s.package_id = sp.package_id
|
||||
AND s.status = 'active'
|
||||
AND (s.end_at IS NULL OR s.end_at > NOW())
|
||||
JOIN kol_prompts p ON p.id = sp.prompt_id
|
||||
AND p.package_id = sp.package_id
|
||||
AND p.status = 'active'
|
||||
AND p.published_revision_no IS NOT NULL
|
||||
AND p.deleted_at IS NULL
|
||||
JOIN kol_packages k ON k.id = sp.package_id
|
||||
AND k.status = 'published'
|
||||
AND k.deleted_at IS NULL
|
||||
JOIN kol_profiles pf ON pf.id = k.kol_profile_id
|
||||
AND pf.status = 'active'
|
||||
AND pf.deleted_at IS NULL
|
||||
WHERE sp.tenant_id = $1::bigint
|
||||
AND sp.status = 'active'
|
||||
ORDER BY sp.granted_at DESC
|
||||
@@ -314,6 +332,7 @@ type ListSubscriptionPromptsByTenantRow struct {
|
||||
PlatformHint pgtype.Text `json:"platform_hint"`
|
||||
PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"`
|
||||
PackageName string `json:"package_name"`
|
||||
KolDisplayName string `json:"kol_display_name"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]ListSubscriptionPromptsByTenantRow, error) {
|
||||
@@ -338,6 +357,7 @@ func (q *Queries) ListSubscriptionPromptsByTenant(ctx context.Context, tenantID
|
||||
&i.PlatformHint,
|
||||
&i.PublishedRevisionNo,
|
||||
&i.PackageName,
|
||||
&i.KolDisplayName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user