dwara_core/config/limits.rs
1//! Schema-validation limits: numeric bounds the config compiler enforces
2//! and runtime code must respect.
3//!
4//! These constants are part of the CONFIG CONTRACT (a value validation
5//! rejects can never reach the runtime), so they live beside the schema
6//! rather than in the modules that also use them at runtime: validation
7//! (`snapshot::validate`) and the runtime consumers (the balancer's ring
8//! construction, the slow-start clamp, the retry parameter resolution)
9//! all read the same numbers from here.
10
11/// Ketama vnode count per unit of endpoint weight (a weight-1 endpoint
12/// gets 160 vnodes; a weight-w endpoint gets `160 * w` — footprint is
13/// additive, so adding an endpoint never resizes existing segments).
14pub const KETAMA_VNODES: u64 = 160;
15
16/// Validation bound on total ip_hash ring size (`sum(weight) * 160`).
17/// Keeps ring construction cheap and the BTreeMap small even for skewed
18/// weight sets.
19pub const MAX_RING_VNODES: u64 = 65_536;
20
21/// Maglev lookup table size (DP-07, #236). Must be a prime number for
22/// the Maglev permutation algorithm to work correctly. 65537 is the
23/// default used by Google's Maglev paper and Envoy.
24pub const MAGLEV_TABLE_SIZE: usize = 65_537;
25
26/// Validation bound for `upstreams[].slow_start_ms` (10 minutes): the ramp
27/// window is per-endpoint-entry, and unbounded windows would keep recycled
28/// addresses permanently underweighted.
29pub const MAX_SLOW_START_MS: u64 = 600_000;
30
31/// Validation bound for `upstreams[].timeouts.happy_eyeballs_ms`
32/// (DW-030, 10 minutes — the slow-start bound's twin rationale: a racing
33/// delay past this no longer races anything, it serializes dials behind a
34/// sleeper). `0` is legal and disables racing instead.
35pub const MAX_HAPPY_EYEBALLS_MS: u64 = 600_000;
36
37/// Validation bound on `retries.attempts` (mirrored in `snapshot::validate`).
38pub const MAX_RETRY_ATTEMPTS: u32 = 10;
39
40/// Validation ceiling on `retries.total_deadline_ms` (REL-01): the
41/// cross-attempt wall-clock cap including backoff delays. Ten minutes is
42/// the longest a single request's retry storm should extend tail latency
43/// (beyond that the per-upstream circuit breaker and timeouts are the
44/// right tool, not a wider retry deadline). Absent means unbounded (the
45/// v1 default, kept for backwards compatibility); a present `0` is
46/// rejected at validation (omit the field for unbounded).
47pub const MAX_RETRY_TOTAL_DEADLINE_MS: u64 = 600_000;
48
49/// Validation bounds on `retries.hedge.hedge_after_ms` (DW-063): the
50/// tail-latency threshold before a speculative copy is sent. Below 1 ms
51/// is pointless (the copy would race immediately); above 5 minutes the
52/// upstream is almost certainly dead and a retry or breaker is the
53/// right tool, not a hedge.
54pub const MIN_HEDGE_AFTER_MS: u64 = 1;
55pub const MAX_HEDGE_AFTER_MS: u64 = 300_000;
56
57/// Validation bound on `retries.hedge.hedge_max` (DW-063): at most this
58/// many speculative copies per request. Each copy consumes an
59/// endpoint slot and a retry-budget charge, so the cap bounds the
60/// amplification factor.
61pub const MAX_HEDGE_COPIES: u32 = 4;
62
63/// Validation bound on `routes[].fault_injection.delay.fixed_ms`
64/// (DW-062): the maximum injectable delay. Above 5 minutes the upstream
65/// is almost certainly dead and a timeout or breaker is the right tool,
66/// not a fault-injection delay.
67pub const MAX_FAULT_DELAY_MS: u64 = 300_000;
68
69/// Validation bound on `routes[].fault_injection.abort.status`
70/// (DW-062): the HTTP status range for abort injection.
71pub const MIN_FAULT_ABORT_STATUS: u16 = 100;
72pub const MAX_FAULT_ABORT_STATUS: u16 = 599;
73
74/// Runtime bound on the per-key GCRA state a rate-limiter window holds
75/// PER SHARD of its keyed store (see `extensions::rate_limiter`). Not a
76/// schema bound — no config value is checked against it — but a
77/// process-lifetime memory guarantee: an `[ip]`-selector limiter under
78/// key spray can hold at most `shards * this` keys per window per rule,
79/// after which idlest-first eviction takes over (a deliberately fixed
80/// constant rather than an ops knob; revisit if operators need to tune).
81pub const MAX_RATE_LIMITER_KEYS_PER_SHARD: usize = 4_096;
82
83/// Runtime bound on the replay-nonce entries the HMAC request-signing
84/// nonce cache (DW-036, `security::authn`) holds PER SHARD. Not a
85/// schema bound — a process-lifetime memory guarantee under nonce
86/// flood, exactly the GCRA cap's rationale: entries expire after twice
87/// the clock-skew window anyway, and the cap only binds when an
88/// attacker floods nonces faster than expiry retires them (a fixed
89/// constant rather than an ops knob).
90pub const MAX_NONCE_CACHE_ENTRIES_PER_SHARD: usize = 4_096;
91
92/// Validation bound on `gateway.webhooks[].max_attempts` (DW-044):
93/// deliveries retry at most this many times in total, so a dead target
94/// cannot occupy its delivery task (or the retry budget) indefinitely.
95pub const MAX_WEBHOOK_ATTEMPTS: u32 = 10;
96
97/// Validation bound on `gateway.webhooks[].timeout_ms` (DW-044): one
98/// minute is the largest per-delivery budget worth waiting on an ALERT
99/// (the gateway is not a durable queue; anything slower should be an
100/// event sink, not a webhook).
101pub const MAX_WEBHOOK_TIMEOUT_MS: u64 = 60_000;
102
103/// Validation bound on `gateway.analytics_stream.buffer` (DW-121): the
104/// firehose channel's capacity floor. Below this a single flush tick of
105/// bursty traffic would spill into emit-time drops that no buffer
106/// tuning could have absorbed.
107pub const MIN_STREAM_BUFFER: u64 = 64;
108
109/// Validation bound on `gateway.analytics_stream.buffer` (DW-121): the
110/// ceiling on records queued between the dataplane and the flusher.
111/// Every queued record is an owned copy of the access record, so the
112/// buffer is the firehose's whole memory story — this bound keeps a
113/// mistyped config from allocating an unbounded "bounded" queue.
114pub const MAX_STREAM_BUFFER: u64 = 65_536;
115
116/// Validation bound on `gateway.analytics_stream.flush_ms` (DW-121):
117/// the batch-latency ceiling (one minute — the same shape as the
118/// webhook timeout ceiling: a batch older than this is a backlogged
119/// pipeline, not a tuning knob).
120pub const MAX_STREAM_FLUSH_MS: u64 = 60_000;
121
122/// Validation floor on `gateway.analytics_stream.flush_ms` (DW-121):
123/// below 100 ms the flusher degenerates into per-record delivery, which
124/// the batch knob exists to prevent.
125pub const MIN_STREAM_FLUSH_MS: u64 = 100;
126
127/// Validation bound on `gateway.analytics_stream.batch_max` (DW-121):
128/// records per flushed batch ceiling (with the byte cap, whichever
129/// comes first — the batch is the delivery unit, so this is also the
130/// per-delivery record-count bound).
131pub const MAX_STREAM_BATCH_RECORDS: u64 = 4_096;
132
133/// Validation bound on `routes[].websocket.max_frames_per_sec`
134/// (DW-039): one hundred thousand frames per second sustained is the
135/// most an operator should need to allow (a chatty app ticks far
136/// below it; above it the policing itself becomes the load).
137pub const MAX_WEBSOCKET_FRAMES_PER_SEC: u64 = 100_000;
138
139/// Default grace period (days) after a license expires before the gate
140/// degrades to OSS (DW-032). Configurable via
141/// `gateway.license.grace_period_days`; 0 means no grace (immediate
142/// degradation on expiry). Lives in config (the lowest domain) so both
143/// extensions::licensing (the gate) and snapshot::validate (the bounds
144/// check) can read it without an upward import.
145pub const DEFAULT_LICENSE_GRACE_PERIOD_DAYS: u32 = 7;
146
147/// Maximum configurable grace period (days) after license expiry
148/// (DW-032). `gateway.license.grace_period_days` is validated to
149/// 0..=this.
150pub const MAX_LICENSE_GRACE_PERIOD_DAYS: u32 = 30;
151
152/// Validation floor on `gateway.redis_rate_limiter.connection_timeout_ms`
153/// (DW-031): below 100 ms a connection attempt is too aggressive for a
154/// network round-trip (the timeout would fire before DNS resolution
155/// completes on many setups).
156pub const MIN_REDIS_CONNECTION_TIMEOUT_MS: u64 = 100;
157
158/// Validation ceiling on `gateway.redis_rate_limiter.connection_timeout_ms`
159/// (DW-031): 30 seconds is the longest a startup connection should
160/// stall the gateway (beyond that the operator should fix their Redis,
161/// not widen the timeout).
162pub const MAX_REDIS_CONNECTION_TIMEOUT_MS: u64 = 30_000;
163
164/// Validation floor on `gateway.redis_rate_limiter.key_ttl_s` (DW-031):
165/// below 1 second keys would expire before the GCRA bucket can refill
166/// for any non-trivial rate, making the limiter ineffective.
167pub const MIN_REDIS_KEY_TTL_S: u64 = 1;
168
169/// Validation ceiling on `gateway.redis_rate_limiter.key_ttl_s`
170/// (DW-031): 7 days is the longest a stale rate-limit key should
171/// linger in Redis (beyond that the operator should lower it to keep
172/// Redis memory bounded under key spray).
173pub const MAX_REDIS_KEY_TTL_S: u64 = 604_800;
174
175/// Validation floor on `gateway.oidc_providers[].introspection_cache_ttl_s`
176/// (DW-034): below 1 second the introspection cache would expire before
177/// the next request can benefit, degenerating into a per-request IdP
178/// call (the cache exists precisely to avoid that).
179pub const MIN_OIDC_INTROSPECTION_CACHE_TTL_S: u64 = 1;
180
181/// Validation ceiling on
182/// `gateway.oidc_providers[].introspection_cache_ttl_s` (DW-034): one
183/// hour is the longest a cached `active: true` introspection should be
184/// trusted without re-checking (a revoked token would otherwise keep
185/// authenticating for an hour after revocation).
186pub const MAX_OIDC_INTROSPECTION_CACHE_TTL_S: u64 = 3600;
187
188/// Validation floor on `upstreams[].dns_discovery.refresh_interval_s`
189/// (DW-042): below 1 second the discovery task would re-resolve faster
190/// than DNS can meaningfully change, hammering the name server.
191pub const MIN_DNS_DISCOVERY_REFRESH_S: u64 = 1;
192
193/// Validation ceiling on `upstreams[].dns_discovery.refresh_interval_s`
194/// (DW-042): one hour is the longest an operator should go without
195/// noticing a stale endpoint set (beyond that the TTL-driven refresh
196/// is the better mechanism).
197pub const MAX_DNS_DISCOVERY_REFRESH_S: u64 = 3_600;
198
199/// Validation floor on `gateway.config_convergence.poll_interval_ms`
200/// (DW-054): below 100 ms the convergence poll would hammer the backend
201/// faster than another instance can publish a generation, and the
202/// round-trip itself dominates the interval.
203pub const MIN_CONFIG_CONVERGENCE_POLL_MS: u64 = 100;
204
205/// Validation ceiling on `gateway.config_convergence.poll_interval_ms`
206/// (DW-054): one minute is the longest convergence lag an operator
207/// should tolerate (the done-when targets sub-second convergence; a
208/// poll this slow is a misconfiguration).
209pub const MAX_CONFIG_CONVERGENCE_POLL_MS: u64 = 60_000;
210
211/// Validation floor on
212/// `gateway.config_convergence.drift_check_interval_ms` (DW-054): below
213/// one second the drift check would race the poll and report transient
214/// mid-convergence divergence as drift (noise).
215pub const MIN_CONFIG_CONVERGENCE_DRIFT_CHECK_MS: u64 = 1_000;
216
217/// Validation ceiling on
218/// `gateway.config_convergence.drift_check_interval_ms` (DW-054): five
219/// minutes is the longest an operator should go without a drift report
220/// (beyond that a divergent instance serves stale config for too long
221/// before anyone notices).
222pub const MAX_CONFIG_CONVERGENCE_DRIFT_CHECK_MS: u64 = 300_000;
223
224/// Validation ceiling on `upstreams[].pool.pool_idle_timeout_ms`
225/// (DP-03): ten minutes is the longest an idle connection should
226/// linger before the pool reaps it (beyond that the upstream or an
227/// intervening proxy has almost certainly reaped it already, and a
228/// stale pooled connection is a p99 regression source).
229pub const MAX_POOL_IDLE_TIMEOUT_MS: u64 = 600_000;
230
231/// Validation ceiling on `upstreams[].pool.pool_max_idle_per_host`
232/// (DP-03): the per-host idle fraction of the per-upstream
233/// `connection_cap`. 1024 is well above any reasonable cap, so this
234/// only catches a typo (a mistyped config that would allocate an
235/// unbounded "bounded" idle set).
236pub const MAX_POOL_MAX_IDLE_PER_HOST: u32 = 1024;
237
238/// Validation ceiling on
239/// `upstreams[].pool.http2_keep_alive_interval_ms` (DP-03): ten
240/// minutes is the longest PING interval worth configuring (beyond
241/// that an intervening proxy's idle timeout fires first, defeating
242/// the keep-alive).
243pub const MAX_HTTP2_KEEP_ALIVE_INTERVAL_MS: u64 = 600_000;
244
245/// Validation ceiling on
246/// `upstreams[].pool.http2_keep_alive_timeout_ms` (DP-03): ten
247/// minutes is the longest a client should wait for a PING ACK before
248/// closing (beyond that the connection is effectively dead and a
249/// reconnect is the right tool).
250pub const MAX_HTTP2_KEEP_ALIVE_TIMEOUT_MS: u64 = 600_000;
251
252/// Validation ceiling on `upstreams[].pool.max_concurrent_streams`
253/// (DP-03): one million concurrent streams per connection is well
254/// above any real h2 peer's advertised limit, so this only catches a
255/// typo. The floor is 1 (a zero-concurrency client can never send).
256pub const MAX_POOL_MAX_CONCURRENT_STREAMS: u32 = 1_000_000;