Skip to main content

max / makenotwork

19.3 KB · 436 lines History Blame Raw
1 //! Centralized application constants
2 //!
3 //! Magic numbers that were scattered across modules. Storage-specific limits
4 //! (file sizes, presign expiry, allowed types) remain in storage.rs since
5 //! they're only used there.
6
7 // -- Database --
8 pub const DB_POOL_MAX_CONNECTIONS: u32 = 25;
9 pub const DB_POOL_MIN_CONNECTIONS: u32 = 2;
10 pub const DB_ACQUIRE_TIMEOUT_SECS: u64 = 3;
11 /// Rotate connections after 30 minutes to prevent stale sessions.
12 pub const DB_MAX_LIFETIME_SECS: u64 = 1800;
13 /// Prune idle connections after 10 minutes.
14 pub const DB_IDLE_TIMEOUT_SECS: u64 = 600;
15
16 // -- Sessions --
17 pub const SESSION_EXPIRY_DAYS: i64 = 7;
18 /// Skip DB touch if validated within this window. Doubles as the upper bound on
19 /// session-revocation lag (admin suspend, logout-everywhere, password change) —
20 /// shorter = tighter revocation, slightly more DB load on the auth hot path.
21 pub const SESSION_TOUCH_CACHE_SECS: u64 = 5;
22
23 // -- Login security --
24 pub const MAX_LOGIN_ATTEMPTS: i32 = 5;
25 pub const LOCKOUT_MINUTES: i64 = 15;
26 /// How long a half-completed login (password verified, awaiting 2FA) stays
27 /// valid before the user must re-enter their password. Defends against the
28 /// "unattended browser one TOTP from logged in" failure mode.
29 pub const PENDING_2FA_TTL_SECS: i64 = 600;
30
31 // -- Email link expiry (seconds) --
32 pub const PASSWORD_RESET_EXPIRY_SECS: i64 = 900; // 15 minutes
33 pub const EMAIL_VERIFICATION_EXPIRY_SECS: i64 = 86400; // 24 hours
34 pub const ACCOUNT_DELETION_EXPIRY_SECS: i64 = 3600; // 1 hour
35
36 // -- Stripe fees (for display only — actual fees set by Stripe) --
37 pub const STRIPE_FEE_PERCENTAGE: f64 = 0.029; // 2.9%
38 pub const STRIPE_FEE_FIXED_CENTS: f64 = 30.0; // $0.30
39 /// Stripe minimum charge amount in cents (USD). Charges below this are rejected.
40 pub const STRIPE_MINIMUM_CHARGE_CENTS: i64 = 50; // $0.50
41
42 // -- Page / query limits --
43 pub const DASHBOARD_TRANSACTION_LIMIT: i64 = 100;
44
45 // -- SyncKit --
46 pub const SYNCKIT_JWT_EXPIRY_SECS: i64 = 7 * 24 * 3600; // 7 days
47 pub const SYNCKIT_PUSH_MAX_CHANGES: usize = 500;
48 pub const SYNCKIT_PULL_PAGE_SIZE: i64 = 500;
49 pub const SYNCKIT_API_KEY_LENGTH: usize = 32; // 32 bytes = 64 hex chars
50 pub const SYNC_LOG_RETAIN_DAYS: i64 = 90;
51 pub const SYNC_LOG_COMPACT_MIN_AGE_DAYS: i64 = 7; // Safety margin for cursor-based compaction
52 pub const SYNCKIT_MAX_BLOB_SIZE_BYTES: i64 = 500 * 1024 * 1024; // 500 MB
53 pub const SYNCKIT_MAX_BLOB_STORAGE_BYTES: i64 = 10 * 1024 * 1024 * 1024; // 10 GB per user per app
54 pub const SYNCKIT_MAX_DEVICES_PER_APP: i64 = 50; // Max devices per user per app
55 pub const SYNCKIT_BLOB_PRESIGN_EXPIRY_SECS: u64 = 3600; // 1 hour
56 pub const SYNCKIT_MAX_SSE_CONNECTIONS_PER_USER: usize = 10;
57 pub const SYNCKIT_ROTATION_STALE_HOURS: i64 = 24;
58 pub const SYNCKIT_ROTATION_BATCH_MAX: usize = 500;
59
60 // -- Subscriptions --
61 pub const MIN_SUBSCRIPTION_PRICE_CENTS: i32 = 100; // $1.00 minimum
62
63 // -- OAuth --
64 pub const OAUTH_CODE_EXPIRY_SECS: i64 = 600; // 10 minutes
65 pub const OAUTH_CODE_LENGTH: usize = 32; // 32 bytes = 64 hex chars
66
67 // -- Health monitoring --
68 pub const HEALTH_CHECK_INTERVAL_SECS: u64 = 60;
69 pub const ALERT_COOLDOWN_SECS: u64 = 300; // 5 minutes
70 pub const HEALTH_HISTORY_RETAIN_DAYS: i64 = 90;
71
72 // -- Scheduled publish --
73 pub const SCHEDULER_INTERVAL_SECS: u64 = 60;
74
75 // How often the rate-limiter bucket-map sweeper reclaims stale GCRA entries.
76 // Bounds limiter map size by active (not cumulative-unique) client keys.
77 pub const GOVERNOR_SWEEP_INTERVAL_SECS: u64 = 60;
78
79 // -- TOTP / 2FA --
80 pub const TOTP_SKEW: u8 = 1; // Allow +/-1 time step (+/-30s)
81 pub const TOTP_STEP: u64 = 30; // 30-second windows
82 pub const TOTP_DIGITS: usize = 6; // 6-digit codes
83 pub const BACKUP_CODE_COUNT: usize = 10; // Generate 10 codes
84 pub const BACKUP_CODE_LENGTH: usize = 8; // 8 alphanumeric chars
85
86 // -- Anti-enumeration --
87 pub const USERNAME_CHECK_DELAY_MS: u64 = 400;
88
89 // -- Rate limiting --
90 // Auth endpoints (login, join): burst 5, then 2/sec.
91 // fast-tests: relaxed to burst 20 so lockout tests can fire 5+ attempts without hitting rate limiter.
92 #[cfg(not(feature = "fast-tests"))]
93 pub const AUTH_RATE_LIMIT_MS: u64 = 500;
94 #[cfg(not(feature = "fast-tests"))]
95 pub const AUTH_RATE_LIMIT_BURST: u32 = 5;
96 #[cfg(feature = "fast-tests")]
97 pub const AUTH_RATE_LIMIT_MS: u64 = 10;
98 #[cfg(feature = "fast-tests")]
99 pub const AUTH_RATE_LIMIT_BURST: u32 = 20;
100 // Username validation: burst 10, then 1/sec
101 pub const VALIDATE_RATE_LIMIT_PER_SEC: u64 = 1;
102 pub const VALIDATE_RATE_LIMIT_BURST: u32 = 10;
103 // API write endpoints (CRUD): burst 30, then 2/sec
104 pub const API_WRITE_RATE_LIMIT_MS: u64 = 500;
105 pub const API_WRITE_RATE_LIMIT_BURST: u32 = 30;
106 // API read endpoints (GET): burst 60, then 10/sec (prevents enumeration)
107 pub const API_READ_RATE_LIMIT_MS: u64 = 100;
108 pub const API_READ_RATE_LIMIT_BURST: u32 = 60;
109 // API export endpoints: burst 3, then 1/sec
110 pub const API_EXPORT_RATE_LIMIT_PER_SEC: u64 = 1;
111 pub const API_EXPORT_RATE_LIMIT_BURST: u32 = 3;
112 // Guest checkout (public, no auth): burst 10, then 1/sec
113 pub const GUEST_CHECKOUT_RATE_LIMIT_PER_SEC: u64 = 1;
114 pub const GUEST_CHECKOUT_RATE_LIMIT_BURST: u32 = 10;
115 // License key validation (public): burst 20, then 5/sec
116 pub const LICENSE_KEY_RATE_LIMIT_MS: u64 = 200;
117 pub const LICENSE_KEY_RATE_LIMIT_BURST: u32 = 20;
118 // File upload: burst 10, then 2/sec
119 pub const UPLOAD_RATE_LIMIT_MS: u64 = 500;
120 pub const UPLOAD_RATE_LIMIT_BURST: u32 = 10;
121 // OAuth authorize/token: burst 5/10, then 2/sec
122 pub const OAUTH_RATE_LIMIT_MS: u64 = 500;
123 pub const OAUTH_RATE_LIMIT_BURST: u32 = 5;
124 pub const OAUTH_TOKEN_RATE_LIMIT_MS: u64 = 500;
125 pub const OAUTH_TOKEN_RATE_LIMIT_BURST: u32 = 10;
126 // SyncKit auth: burst 5, then 1/sec
127 pub const SYNCKIT_AUTH_RATE_LIMIT_PER_SEC: u64 = 1;
128 pub const SYNCKIT_AUTH_RATE_LIMIT_BURST: u32 = 5;
129 // SyncKit sync (push/pull) — per-IP: burst 30, then 10/sec
130 pub const SYNCKIT_SYNC_RATE_LIMIT_MS: u64 = 100;
131 pub const SYNCKIT_SYNC_RATE_LIMIT_BURST: u32 = 30;
132 // SyncKit sync — per-app: burst 60, then 20/sec (higher than per-IP because
133 // a single app may have many users behind different IPs)
134 pub const SYNCKIT_APP_RATE_LIMIT_MS: u64 = 50;
135 pub const SYNCKIT_APP_RATE_LIMIT_BURST: u32 = 60;
136 // 2FA verification: burst 5, then 2/sec (same as auth)
137 pub const TWO_FACTOR_RATE_LIMIT_MS: u64 = 500;
138 pub const TWO_FACTOR_RATE_LIMIT_BURST: u32 = 5;
139
140 // Dashboard tab reads: generous but bounded (5/sec, burst 20)
141 pub const DASHBOARD_READ_RATE_LIMIT_MS: u64 = 200;
142 pub const DASHBOARD_READ_RATE_LIMIT_BURST: u32 = 20;
143
144 // -- Pagination --
145 pub const DISCOVER_PAGE_SIZE: u32 = 25;
146 pub const FEED_PAGE_SIZE: u32 = 25;
147 pub const PAGINATION_WINDOW_SIZE: u32 = 5;
148
149 // -- Creator broadcast fan-out --
150 /// Max concurrent in-flight email sends per broadcast. The outer worker
151 /// task spawns up to this many child tasks, then waits on one to drain
152 /// before spawning the next.
153 pub const BROADCAST_PARALLELISM: usize = 16;
154 /// Delay between successive broadcast send-task spawns. Spreads Postmark
155 /// API load when a creator with thousands of followers fires a broadcast
156 /// — at parallelism 16 + 100 ms cadence, steady-state is ~10 sends/sec.
157 pub const BROADCAST_CHUNK_DELAY_MS: u64 = 100;
158 /// Recipient cap per broadcast send. Above this, the request is refused
159 /// with an instruction to contact support. Bounds Postmark spend exposure
160 /// from any single approved creator. Founder-window cohort is well under
161 /// this; the cap is the floor we'd lift on request, not the ceiling.
162 pub const BROADCAST_MAX_RECIPIENTS: usize = 10_000;
163
164 /// Cap on buyer-departure notification fan-out per creator-deletion event.
165 /// Account deletion notifies historical buyers about content removal. A
166 /// creator with millions of completed sales should not turn one deletion
167 /// into a Postmark bomb. The cap bounds both the in-memory buyer list and
168 /// total outbound email volume; if hit, we log a warning and notify the
169 /// oldest-buyers slice the SQL chose.
170 pub const BUYER_DEPARTURE_MAX_NOTIFICATIONS: i64 = 50_000;
171
172 // -- File scanning --
173 pub const SCAN_MAX_MEMORY_BYTES: usize = 100 * 1024 * 1024; // 100 MB in-memory threshold
174 pub const SCAN_MAX_CONCURRENT: usize = 4; // Max concurrent file scans (each can use up to 100 MB RAM)
175 pub const SCAN_WORKER_COUNT: usize = 2; // Background worker tasks draining scan_jobs queue
176 /// Retention window for terminal-state (`done`, `failed`) `scan_jobs` rows.
177 /// Queued/running rows are operational queue state and not affected.
178 pub const SCAN_JOB_RETENTION_DAYS: u32 = 30;
179 /// Directory under which the scanner spools large objects to tempfiles
180 /// before invoking path/stream-based layer entries. On production, systemd
181 /// provisions this via `StateDirectory=mnw/scan-spool` so the path resolves
182 /// to `/var/lib/mnw/scan-spool`. Override with `MNW_SCAN_SPOOL_DIR` for dev.
183 pub const SCAN_SPOOL_DIR: &str = "/var/lib/makenotwork/scan-spool";
184 /// Files in `SCAN_SPOOL_DIR` older than this are considered orphaned
185 /// (a panic, OOM, or hard kill left them behind) and reaped on the
186 /// next sweep. RAII drop in `SpoolHandle` covers the live path; this
187 /// covers process-death.
188 pub const SCAN_SPOOL_ORPHAN_AGE_SECS: u64 = 3600;
189 /// Hard cap on a single spooled object. Above this, the scanner refuses
190 /// the job rather than risk filling the volume. 8 GiB matches the largest
191 /// payload the upload tier currently allows.
192 pub const SCAN_SPOOL_MAX_BYTES: u64 = 8 * 1024 * 1024 * 1024;
193 /// Minimum free space the spool volume must retain after writing the
194 /// pending object. The scanner refuses if `statvfs(free) - expected_size`
195 /// would drop below this threshold.
196 pub const SCAN_SPOOL_FREE_RESERVE_BYTES: u64 = 2 * 1024 * 1024 * 1024;
197
198 // -- Caddy on-demand TLS --
199 // Caps concurrent cache-miss DB lookups in `/api/domains/caddy-ask`. Cache hits
200 // are unbounded (DashMap). At capacity, the handler returns 503 so Caddy retries
201 // later instead of stampeding the DB pool or driving ACME issuance for garbage
202 // domains. Sized small because the slow path is one indexed lookup.
203 pub const CADDY_ASK_MAX_CONCURRENT: usize = 8;
204 pub const SCAN_ZIP_MAX_RATIO: f64 = 100.0; // Max compression ratio before ZIP bomb
205 pub const SCAN_ZIP_MAX_DEPTH: u32 = 2; // Max nested archives (detection is 1 level deep; decompressed size limit is the primary defense)
206 pub const SCAN_ZIP_MAX_UNCOMPRESSED: u64 = 2 * 1024 * 1024 * 1024; // 2 GB uncompressed limit
207 pub const SCAN_MALWAREBAZAAR_TIMEOUT_SECS: u64 = 5;
208 pub const SCAN_CLAMAV_TIMEOUT_SECS: u64 = 30;
209
210 // -- Invite system --
211 pub const INVITES_ENABLED: bool = true;
212 pub const INVITE_LIMIT_PER_CREATOR: i64 = 5; // max unredeemed codes per creator
213
214 // -- Git source browser --
215 pub const GIT_MAX_FILE_SIZE_BYTES: usize = 1_024_000; // 1MB display limit
216 pub const GIT_COMMITS_PER_PAGE: usize = 30;
217 pub const GIT_DIFF_MAX_FILES: usize = 20; // Inline diff hunks for first N files
218 pub const GIT_DIFF_MAX_LINES: usize = 500; // Per-file line cap for diff display
219 pub const GIT_REPOS_PER_PAGE: usize = 30;
220 pub const GIT_FILE_LOG_MAX_WALK: usize = 1000; // Max commits to walk for per-file history
221 pub const GIT_RAW_MAX_BYTES: usize = 100 * 1024 * 1024; // 100 MB raw download limit
222 pub const GIT_UPLOAD_PACK_MAX_BYTES: usize = 10 * 1024 * 1024; // 10 MB upload-pack body limit
223
224 // -- Webhook security --
225 pub const WEBHOOK_TIMESTAMP_TOLERANCE_SECS: u64 = 300; // 5 minutes
226
227 // -- Collections --
228 pub const MAX_COLLECTIONS_PER_USER: i64 = 50;
229 pub const MAX_ITEMS_PER_COLLECTION: i64 = 200;
230
231 // -- OTA updates --
232 pub const OTA_PRESIGN_EXPIRY_SECS: u64 = 3600; // 1 hour
233 // OTA management: burst 10, then 2/sec (same as API write)
234 pub const OTA_WRITE_RATE_LIMIT_MS: u64 = 500;
235 pub const OTA_WRITE_RATE_LIMIT_BURST: u32 = 10;
236 // OTA public (updater check, download): burst 30, then 10/sec
237 pub const OTA_READ_RATE_LIMIT_MS: u64 = 100;
238 pub const OTA_READ_RATE_LIMIT_BURST: u32 = 30;
239
240 // -- Build pipeline --
241 pub const BUILD_TIMEOUT_SECS: u64 = 1800; // 30 min
242 pub const BUILD_MAX_LOG_BYTES: usize = 5_242_880; // 5 MB
243 pub const BUILD_HISTORY_LIMIT: i64 = 50;
244 pub const BUILD_TRIGGER_RATE_LIMIT_PER_SEC: u64 = 1;
245 pub const BUILD_TRIGGER_RATE_LIMIT_BURST: u32 = 3;
246 pub const BUILD_WRITE_RATE_LIMIT_MS: u64 = 500;
247 pub const BUILD_WRITE_RATE_LIMIT_BURST: u32 = 10;
248 // Git browsing: burst 30, then 5/sec (blame/log can be expensive)
249 pub const GIT_BROWSE_RATE_LIMIT_MS: u64 = 200;
250 pub const GIT_BROWSE_RATE_LIMIT_BURST: u32 = 30;
251 pub const BUILD_ALLOWED_TARGETS: &[&str] = &[
252 "linux/x86_64",
253 "linux/aarch64",
254 "darwin/x86_64",
255 "darwin/aarch64",
256 ];
257
258 // -- Streaming --
259 pub const STREAMING_CACHE_MAX_SECS: u64 = 86400; // 24 hours max presigned URL lifetime
260 /// Rate limit for stream/download URL requests: 1 per 3 seconds, burst of 10.
261 pub const STREAM_RATE_LIMIT_MS: u64 = 3000;
262 pub const STREAM_RATE_LIMIT_BURST: u32 = 10;
263
264 // -- Date display formats --
265 pub const DATE_FMT_SHORT: &str = "%b %d"; // "Mar 25"
266 pub const DATE_FMT_FULL: &str = "%b %d, %Y"; // "Mar 25, 2026"
267 pub const DATE_FMT_ISO: &str = "%Y-%m-%d"; // "2026-03-25"
268 pub const DATE_FMT_DATETIME: &str = "%b %d, %Y %H:%M"; // "Mar 25, 2026 14:30"
269 pub const DATE_FMT_DATETIME_UTC: &str = "%b %d, %Y %H:%M UTC"; // "Mar 25, 2026 14:30 UTC"
270
271 // -- Platform content --
272 pub const CHANGELOG_PROJECT_SLUG: &str = "changelog";
273
274 // -- String / buffer limits --
275 pub const USER_AGENT_MAX_LENGTH: usize = 512;
276 pub const SYNCKIT_MAX_KEY_ENVELOPE_BYTES: usize = 4096;
277 pub const MAX_PRICE_CENTS: i32 = 1_000_000; // $10,000
278
279 // -- Sandbox accounts --
280 /// How long a sandbox session lasts before auto-cleanup.
281 pub const SANDBOX_EXPIRY_SECS: i64 = 3600; // 1 hour
282 /// How often the cleanup job runs.
283 pub const SANDBOX_CLEANUP_INTERVAL_SECS: u64 = 300; // 5 minutes
284 /// Rate limit: sandbox creation.
285 /// Production: 1 per 30 seconds, burst 2. fast-tests: 1 per 10ms, burst 10.
286 /// Run integration tests with `cargo test --features fast-tests` to avoid rate-limit failures.
287 #[cfg(not(feature = "fast-tests"))]
288 pub const SANDBOX_RATE_LIMIT_MS: u64 = 30_000;
289 #[cfg(not(feature = "fast-tests"))]
290 pub const SANDBOX_RATE_LIMIT_BURST: u32 = 2;
291 #[cfg(feature = "fast-tests")]
292 pub const SANDBOX_RATE_LIMIT_MS: u64 = 10;
293 #[cfg(feature = "fast-tests")]
294 pub const SANDBOX_RATE_LIMIT_BURST: u32 = 10;
295 /// Max concurrent active sandboxes per IP.
296 pub const SANDBOX_MAX_PER_IP: i64 = 3;
297
298 // ── Compile-time invariants on the constants above ───────────────────────────
299 //
300 // Encoded as `const _: () = assert!(...)` rather than `#[test]` functions: these
301 // are checked when the crate is COMPILED, so a bad constant fails the build
302 // (not just a test run), and the whole invariant set sits next to the values.
303
304 // Price constants
305 const _: () = assert!(MAX_PRICE_CENTS > 0);
306 const _: () = assert!(MAX_PRICE_CENTS <= 10_000_000); // <= $100,000
307 const _: () = assert!(MIN_SUBSCRIPTION_PRICE_CENTS > 0);
308 const _: () = assert!(MIN_SUBSCRIPTION_PRICE_CENTS < MAX_PRICE_CENTS);
309
310 // Stripe fee constants
311 const _: () = assert!(STRIPE_FEE_PERCENTAGE > 0.0 && STRIPE_FEE_PERCENTAGE < 0.5);
312 const _: () = assert!(STRIPE_FEE_FIXED_CENTS > 0.0);
313
314 // Database pool
315 const _: () = assert!(DB_POOL_MAX_CONNECTIONS > DB_POOL_MIN_CONNECTIONS);
316 const _: () = assert!(DB_POOL_MIN_CONNECTIONS > 0);
317 const _: () = assert!(DB_ACQUIRE_TIMEOUT_SECS > 0);
318 const _: () = assert!(DB_MAX_LIFETIME_SECS > DB_IDLE_TIMEOUT_SECS);
319
320 // Session constants
321 const _: () = assert!(SESSION_EXPIRY_DAYS > 0 && SESSION_EXPIRY_DAYS <= 365);
322 const _: () = assert!(SESSION_TOUCH_CACHE_SECS > 0 && SESSION_TOUCH_CACHE_SECS < 86400);
323
324 // Login security
325 const _: () = assert!(MAX_LOGIN_ATTEMPTS > 0);
326 const _: () = assert!(LOCKOUT_MINUTES > 0);
327
328 // Email link expiry ordering
329 const _: () = assert!(PASSWORD_RESET_EXPIRY_SECS > 0);
330 const _: () = assert!(EMAIL_VERIFICATION_EXPIRY_SECS > PASSWORD_RESET_EXPIRY_SECS);
331 const _: () = assert!(ACCOUNT_DELETION_EXPIRY_SECS > 0);
332
333 // Scheduler
334 const _: () = assert!(SCHEDULER_INTERVAL_SECS > 0);
335
336 // Rate-limit bursts all positive
337 const _: () = assert!(AUTH_RATE_LIMIT_BURST > 0);
338 const _: () = assert!(VALIDATE_RATE_LIMIT_BURST > 0);
339 const _: () = assert!(API_WRITE_RATE_LIMIT_BURST > 0);
340 const _: () = assert!(API_READ_RATE_LIMIT_BURST > 0);
341 const _: () = assert!(API_EXPORT_RATE_LIMIT_BURST > 0);
342 const _: () = assert!(LICENSE_KEY_RATE_LIMIT_BURST > 0);
343 const _: () = assert!(UPLOAD_RATE_LIMIT_BURST > 0);
344 const _: () = assert!(OAUTH_RATE_LIMIT_BURST > 0);
345 const _: () = assert!(OAUTH_TOKEN_RATE_LIMIT_BURST > 0);
346
347 // Rate-limit burst ordering: read > write > auth
348 const _: () = assert!(API_READ_RATE_LIMIT_BURST > API_WRITE_RATE_LIMIT_BURST);
349 const _: () = assert!(API_WRITE_RATE_LIMIT_BURST > AUTH_RATE_LIMIT_BURST);
350
351 // Rate-limit intervals positive
352 const _: () = assert!(AUTH_RATE_LIMIT_MS > 0);
353 const _: () = assert!(API_WRITE_RATE_LIMIT_MS > 0);
354 const _: () = assert!(API_READ_RATE_LIMIT_MS > 0);
355
356 // File size limits
357 const _: () = assert!(SCAN_MAX_MEMORY_BYTES > 0);
358 const _: () = assert!(SCAN_SPOOL_FREE_RESERVE_BYTES < SCAN_SPOOL_MAX_BYTES);
359 const _: () = assert!(SCAN_SPOOL_MAX_BYTES > SCAN_MAX_MEMORY_BYTES as u64);
360 const _: () = assert!(SCAN_JOB_RETENTION_DAYS >= 7); // no same-day purge race
361 const _: () = assert!(BROADCAST_PARALLELISM > 0 && BROADCAST_PARALLELISM <= 64);
362 const _: () = assert!(SCAN_ZIP_MAX_UNCOMPRESSED > SCAN_MAX_MEMORY_BYTES as u64);
363 const _: () = assert!(GIT_RAW_MAX_BYTES > GIT_MAX_FILE_SIZE_BYTES);
364 const _: () = assert!(SCAN_ZIP_MAX_RATIO > 0.0);
365 const _: () = assert!(SCAN_ZIP_MAX_DEPTH > 0);
366
367 // SyncKit
368 const _: () = assert!(SYNCKIT_PUSH_MAX_CHANGES > 0);
369 const _: () = assert!(SYNCKIT_PULL_PAGE_SIZE > 0);
370 const _: () = assert!(SYNCKIT_MAX_BLOB_SIZE_BYTES > 0);
371 const _: () = assert!(SYNCKIT_JWT_EXPIRY_SECS > 0);
372
373 // TOTP
374 const _: () = assert!(TOTP_DIGITS == 6);
375 const _: () = assert!(TOTP_STEP == 30);
376 const _: () = assert!(BACKUP_CODE_COUNT > 0);
377 const _: () = assert!(BACKUP_CODE_LENGTH > 0);
378
379 // Pagination
380 const _: () = assert!(DISCOVER_PAGE_SIZE > 0);
381 const _: () = assert!(FEED_PAGE_SIZE > 0);
382 const _: () = assert!(PAGINATION_WINDOW_SIZE > 0);
383
384 // String constants non-empty
385 const _: () = assert!(!DATE_FMT_SHORT.is_empty());
386 const _: () = assert!(!DATE_FMT_FULL.is_empty());
387 const _: () = assert!(!DATE_FMT_ISO.is_empty());
388 const _: () = assert!(!DATE_FMT_DATETIME.is_empty());
389 const _: () = assert!(!DATE_FMT_DATETIME_UTC.is_empty());
390 const _: () = assert!(!CHANGELOG_PROJECT_SLUG.is_empty());
391 const _: () = assert!(!BUILD_ALLOWED_TARGETS.is_empty());
392
393 // Collections
394 const _: () = assert!(MAX_COLLECTIONS_PER_USER > 0);
395 const _: () = assert!(MAX_ITEMS_PER_COLLECTION > 0);
396
397 // Build pipeline
398 const _: () = assert!(BUILD_TIMEOUT_SECS > 0);
399 const _: () = assert!(BUILD_MAX_LOG_BYTES > 0);
400
401 // Health monitoring
402 const _: () = assert!(HEALTH_CHECK_INTERVAL_SECS > 0);
403 const _: () = assert!(ALERT_COOLDOWN_SECS > HEALTH_CHECK_INTERVAL_SECS);
404
405 // Sandbox
406 const _: () = assert!(SANDBOX_EXPIRY_SECS > 0);
407 const _: () = assert!(SANDBOX_CLEANUP_INTERVAL_SECS > 0);
408 const _: () = assert!(SANDBOX_CLEANUP_INTERVAL_SECS < SANDBOX_EXPIRY_SECS as u64);
409 const _: () = assert!(SANDBOX_MAX_PER_IP > 0);
410
411 // Webhook
412 const _: () = assert!(WEBHOOK_TIMESTAMP_TOLERANCE_SECS > 0);
413
414 // OAuth
415 const _: () = assert!(OAUTH_CODE_EXPIRY_SECS > 0);
416 const _: () = assert!(OAUTH_CODE_LENGTH > 0);
417
418 // Buffer limits
419 const _: () = assert!(USER_AGENT_MAX_LENGTH > 0);
420 const _: () = assert!(SYNCKIT_MAX_KEY_ENVELOPE_BYTES > 0);
421
422 #[cfg(test)]
423 mod tests {
424 use super::*;
425
426 /// The build-target FORMAT check uses `str::contains`, which isn't const —
427 /// so this invariant stays a runtime test (the rest are compile-time above).
428 #[test]
429 fn build_allowed_targets_are_os_slash_arch() {
430 for target in BUILD_ALLOWED_TARGETS {
431 assert!(!target.is_empty());
432 assert!(target.contains('/'), "target should be os/arch format: {target}");
433 }
434 }
435 }
436