8686from services .domain_intel_service import DomainIntelService
8787from services .edge_cache .og_writethrough import OgEdgeWritethrough
8888from services .entitlements import EntitlementService
89+ from services .entitlements .over_limit import OverLimitService
8990from services .events .sinks import (
9091 InlineDomainEventSink ,
9192 NullDomainEventSink ,
@@ -291,21 +292,34 @@ def build_entitlement_service(
291292 cache , events , subscriptions , overrides = store or build_entitlement_store (
292293 db , redis_client
293294 )
295+ domain_repo = CustomDomainRepository (db ["custom_domains" ])
296+ endpoint_repo = WebhookEndpointRepository (db ["webhook-endpoints" ])
297+ key_repo = ApiKeyRepository (db ["api-keys" ])
298+ reconciler = OverLimitService (
299+ endpoints = endpoint_repo ,
300+ domains = domain_repo ,
301+ keys = key_repo ,
302+ tenant_resolver = CachedMongoTenantResolver (
303+ repo = domain_repo ,
304+ redis_client = redis_client ,
305+ system_default_domain = settings .system_default_domain ,
306+ ),
307+ webhook_owner_cache = OwnerSubscriptionCache (
308+ redis_client , ttl_seconds = settings .webhooks .matcher_cache_ttl_seconds
309+ ),
310+ )
294311 return EntitlementService (
295312 subscriptions ,
296313 overrides ,
297314 events ,
298315 cache ,
299316 selfhost = settings .billing .selfhost ,
300317 usage = {
301- "custom_domains_max" : CustomDomainRepository (
302- db ["custom_domains" ]
303- ).count_by_owner ,
304- "webhook_endpoints_max" : WebhookEndpointRepository (
305- db ["webhook-endpoints" ]
306- ).count_by_user ,
307- "api_keys_max" : ApiKeyRepository (db ["api-keys" ]).count_by_user ,
318+ "custom_domains_max" : domain_repo .count_by_owner ,
319+ "webhook_endpoints_max" : endpoint_repo .count_by_user ,
320+ "api_keys_max" : key_repo .count_by_user ,
308321 },
322+ over_limit = reconciler ,
309323 )
310324
311325
@@ -601,7 +615,6 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
601615 webhook_executor ,
602616 webhook_owner_cache ,
603617 master_secret = settings .secret_key ,
604- max_endpoints = wh_settings .max_endpoints ,
605618 )
606619
607620 # ── Safety pipeline ──────────────────────────────────────────────
@@ -764,6 +777,7 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
764777 events = app .state .domain_event_sink ,
765778 user_repo = user_repo ,
766779 tag_service = app .state .tag_service ,
780+ entitlements = app .state .entitlement_service ,
767781 )
768782 app .state .bulk_url_service = BulkUrlService (
769783 url_repo ,
@@ -778,7 +792,6 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
778792 app .state .stats_service = StatsService (
779793 click_repo ,
780794 url_repo ,
781- max_date_range_days = settings .max_date_range_days ,
782795 tag_service = app .state .tag_service ,
783796 )
784797 # One resolver serves BOTH public read-only surfaces (preview + stats)
@@ -790,7 +803,15 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
790803 emoji_repo ,
791804 system_default_domain = settings .system_default_domain ,
792805 )
793- app .state .public_preview_service = PublicPreviewService (public_link_resolver )
806+ app .state .feature_flag_service = FeatureFlagService (
807+ feature_flag_repo , feature_flag_cache
808+ )
809+
810+ app .state .public_preview_service = PublicPreviewService (
811+ public_link_resolver ,
812+ app .state .entitlement_service ,
813+ app .state .feature_flag_service ,
814+ )
794815 app .state .url_expand_service = UrlExpandService (
795816 blocked_url_repo ,
796817 MetaFetchCache (redis_client , prefix = "url_expand" ),
@@ -808,7 +829,8 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
808829 app .state .public_stats_service = PublicStatsService (
809830 public_link_resolver ,
810831 app .state .stats_service ,
811- max_date_range_days = settings .max_date_range_days ,
832+ app .state .entitlement_service ,
833+ app .state .feature_flag_service ,
812834 )
813835 # Report intake shares the resolver (existence checks answer from the
814836 # same generation the redirect serves) and the ops notifier + captcha
@@ -832,7 +854,6 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
832854 )
833855 app .state .api_key_service = ApiKeyService (
834856 api_key_repo ,
835- max_active_keys = settings .max_active_api_keys ,
836857 )
837858 app .state .page_layout_service = PageLayoutService (page_layout_repo )
838859 token_factory = TokenFactory (
@@ -940,10 +961,6 @@ def wire_services(app: FastAPI, settings: AppSettings, redis_client) -> None:
940961
941962 app .state .app_grant_repo = app_grant_repo
942963
943- app .state .feature_flag_service = FeatureFlagService (
944- feature_flag_repo , feature_flag_cache
945- )
946-
947964 # Whenever clicks are tracked inline, link.clicked webhooks must fan
948965 # out at emit time — the worker's stream group only sees clicks that
949966 # ride the stream. Keying on the CLICK sink (not the domain sink)
0 commit comments