diff --git a/internal/app/email/handler.go b/internal/app/email/handler.go index adc0fd70..87cfb1df 100644 --- a/internal/app/email/handler.go +++ b/internal/app/email/handler.go @@ -360,7 +360,7 @@ func (s *emailService) canUseWarmupPool(ctx context.Context, account *models.Ema } // orgSuspendedOrRestricted reports whether the workspace's posture bars the -// paid warmup pool. Fails open, like every other risk read. +// paid warmup pool. Fails open. func (s *emailService) orgSuspendedOrRestricted(ctx context.Context, orgID uuid.UUID) bool { if s.orgRiskRepo == nil { return false @@ -381,8 +381,8 @@ func (s *emailService) resolveWarmupPoolType(ctx context.Context, account *model if account.OrganizationID == nil { return "free" } - // A restricted organization warms in the free pool whatever it pays; the - // stored tier is checked after, since it is always set (issue #242). + // A restricted organization leaves the paid pool whatever it pays. Checked + // before the stored tier, which is never empty and would short-circuit it. if s.orgSuspendedOrRestricted(ctx, *account.OrganizationID) { return "free" } diff --git a/internal/app/email/service.go b/internal/app/email/service.go index 1b41df63..8f28c752 100644 --- a/internal/app/email/service.go +++ b/internal/app/email/service.go @@ -123,8 +123,7 @@ type OrgRiskAware interface { WireOrgRisk(r repository.OrgRiskRepository) } -// The backend attaches the posture through a type assertion, so a silently -// unsatisfied interface would leave restricted workspaces in the paid pool. +// main.go attaches the posture by type assertion, which fails silently. var _ OrgRiskAware = (*emailService)(nil) // SyncBudgetSource is the operator-editable sync fair-use section, satisfied diff --git a/internal/repository/pg_worker.go b/internal/repository/pg_worker.go index 535cdfbc..4cadd298 100644 --- a/internal/repository/pg_worker.go +++ b/internal/repository/pg_worker.go @@ -496,9 +496,8 @@ func (r *workerRepository) ClearEmailAccountWorker(ctx context.Context, emailAcc // UpdateEmailAccountWarmupPoolType writes the tier and moves the mailbox's pool membership to // match in one transaction: they record the same fact, and updating only the column left // downgraded mailboxes in the premium pool (issue #211). A mailbox in no pool stays in none. -// The membership move into premium is refused while the owning organization is restricted or -// suspended, so a worker rebalance cannot readmit a risky tenant to the paid pool (issue #242). -// The tier column is still written: it records what the workspace pays for, not where it warms. +// The move into premium is refused while the organization is restricted (issue #242); the tier +// column is still written, since it records what the workspace pays for, not where it warms. func (r *workerRepository) UpdateEmailAccountWarmupPoolType(ctx context.Context, emailAccountID uuid.UUID, poolType string) error { tx, err := r.db.Begin(ctx) if err != nil { diff --git a/internal/scheduler/warmup_scheduler.go b/internal/scheduler/warmup_scheduler.go index 24c815db..d8ce675d 100644 --- a/internal/scheduler/warmup_scheduler.go +++ b/internal/scheduler/warmup_scheduler.go @@ -47,9 +47,8 @@ func adjustmentFor(state models.WarmupHealthState) healthAdjustment { } } -// warmupPoolTypeForAccount is the pool the mailbox actually warms in. A -// restricted organization is held in the free pool whatever tier its mailbox -// carries, matching the task service's resolution (issue #242). +// warmupPoolTypeForAccount is the pool the mailbox actually warms in: a restricted +// organization is held in free whatever tier it carries. func (s *schedulerService) warmupPoolTypeForAccount(ctx context.Context, account *models.Email) string { if account == nil { return "premium" diff --git a/internal/tasks/email_task.go b/internal/tasks/email_task.go index aa699873..725b49a6 100644 --- a/internal/tasks/email_task.go +++ b/internal/tasks/email_task.go @@ -744,11 +744,8 @@ func (s *tasksService) resolveWarmupPoolType(ctx context.Context, account *Email if account.OrganizationID == nil { return "free" } - // A restricted organization leaves the paid pool whatever it pays: the - // shared reputation paying customers depend on is not for spending on a - // risky tenant. Checked before the stored tier, which is always set - // (schema default 'free', worker assignment writes it) and so would - // otherwise short-circuit this gate (issue #242). + // A restricted organization leaves the paid pool whatever it pays. Checked + // before the stored tier, which is never empty and would short-circuit it. if s.orgSuspendedOrRestricted(ctx, *account.OrganizationID) { return "free" }