// Package quota implementiert ARC-09: konfigurierbares Speicherlimit // je einzelnem Postfach — eine ANDERE Ebene als die tenant-weite Quota // aus Core LIC-05 (bekannter Fehler zu vermeiden: beide Ebenen müssen // unabhängig durchgesetzt werden, ein Tenant kann insgesamt genug // Kontingent haben, während ein einzelnes Postfach überläuft). package quota import "context" // LimitProvider liefert das konfigurierte Speicherlimit eines // Postfachs (Akzeptanzkriterium 1). configured=false bedeutet: kein // Limit gesetzt, also unbegrenzt — erfüllt von // mail/internal/mailboxconfig.Store.LimitBytes. type LimitProvider interface { LimitBytes(ctx context.Context, tenantSlug, mailbox string) (limitBytes int64, configured bool, err error) } // UsageProvider liefert den TATSÄCHLICHEN, real gemessenen // Speicherverbrauch eines Postfachs — erfüllt von // mail/internal/storage.UsageCounter. type UsageProvider interface { UsageBytes(ctx context.Context, tenantSlug, mailbox string) (usedBytes int64, err error) } // Result ist das Ergebnis einer Quota-Prüfung. type Result struct { Allowed bool UsedBytes int64 LimitBytes int64 } // Checker verknüpft konfiguriertes Limit und realen Verbrauch. type Checker struct { limits LimitProvider usage UsageProvider } func NewChecker(limits LimitProvider, usage UsageProvider) *Checker { return &Checker{limits: limits, usage: usage} } // Check prüft, ob mailbox innerhalb seines konfigurierten Limits ist. // Kein konfiguriertes Limit bedeutet immer "erlaubt" — die // Tenant-weite Quota (Core LIC-05) läuft unabhängig davon weiter. func (c *Checker) Check(ctx context.Context, tenantSlug, mailbox string) (Result, error) { limit, configured, err := c.limits.LimitBytes(ctx, tenantSlug, mailbox) if err != nil { return Result{}, err } if !configured { return Result{Allowed: true}, nil } used, err := c.usage.UsageBytes(ctx, tenantSlug, mailbox) if err != nil { return Result{}, err } return Result{Allowed: used < limit, UsedBytes: used, LimitBytes: limit}, nil } // Allowed ist die schmale Form von Check für Aufrufer, die nur die // Ja/Nein-Entscheidung brauchen (z. B. mail/internal/smtp.QuotaChecker, // Akzeptanzkriterium 2). func (c *Checker) Allowed(ctx context.Context, tenantSlug, mailbox string) (bool, error) { result, err := c.Check(ctx, tenantSlug, mailbox) if err != nil { return false, err } return result.Allowed, nil }