Skip to main content

velesdb_memory/
limits.rs

1//! Resource caps shared by every adapter (the MCP server and the language
2//! bindings).
3//!
4//! These are security-relevant DoS limits. They live here — not inside any one
5//! adapter — so every transport enforces the *same* numbers without a manual
6//! "keep in sync" comment, and so a build without the `mcp` feature still sees
7//! them. Each adapter formats its own transport-native error; only the values
8//! and the clamping policy are shared.
9
10/// Default hop budget for `why` traversal when the caller supplies none.
11pub const DEFAULT_WHY_HOPS: usize = 2;
12
13/// Maximum accepted fact size (1 MiB) — prevents allocating huge embeddings.
14pub const MAX_FACT_BYTES: usize = 1_048_576;
15
16/// Cap on a `recall` limit — prevents unbounded vector scans (core does not
17/// cap `k`, so the adapters do).
18pub const MAX_RECALL_LIMIT: usize = 1_000;
19
20/// Cap on `why` hop depth — prevents exponential graph fan-out.
21pub const MAX_WHY_HOPS: usize = 10;
22
23/// Maximum accepted size of a single context-compiler fragment (1 MiB, the
24/// same ceiling as [`MAX_FACT_BYTES`]) — prevents a single fragment from
25/// forcing huge allocations in the compile pipeline.
26pub const MAX_FRAGMENT_BYTES: usize = 1_048_576;
27
28/// Cap on the number of fragments in one compile request — bounds the work a
29/// single call can demand across every adapter.
30pub const MAX_FRAGMENTS: usize = 1_024;
31
32/// Maximum accepted size of a fragment's base64-encoded media payload
33/// (US-009, PR1: inline images) — 4 MiB of base64 text, roughly 3 MiB of raw
34/// bytes once decoded. Deliberately separate from [`MAX_FRAGMENT_BYTES`],
35/// which only ever measures [`crate::context::model::ContextFragment::content`]
36/// (the caption): a screenshot is not text, and capping it at the 1 MiB text
37/// ceiling would reject ordinary screenshots outright. Measured against
38/// `bytes_b64.len()` (the encoded string), so the cap can reject an
39/// oversized payload before any base64 decoding is attempted.
40pub const MAX_MEDIA_BYTES: usize = 4 * 1024 * 1024;
41
42/// Aggregate cap on ALL media payloads of one request (base64 length,
43/// summed). Without it, `MAX_FRAGMENTS` fragments each at [`MAX_MEDIA_BYTES`]
44/// would let a single request carry 4 GiB of media — far past the ~1 GiB
45/// worst case the text caps allow. 64 MiB comfortably fits a real
46/// screenshot-heavy session while bounding decode work.
47pub const MAX_TOTAL_MEDIA_BYTES: usize = 64 * 1024 * 1024;
48
49/// Cap on a caller-supplied token budget. A budget cannot force allocations
50/// by itself, but an absurd value would make the savings arithmetic
51/// meaningless, so adapters clamp to this ceiling instead of erroring.
52pub const MAX_TOKEN_BUDGET: u64 = 10_000_000;
53
54/// Clamp a caller-supplied token budget to [`MAX_TOKEN_BUDGET`].
55#[must_use]
56pub fn clamp_token_budget(budget: u64) -> u64 {
57    budget.min(MAX_TOKEN_BUDGET)
58}
59
60/// Clamp a caller-supplied recall limit to [`MAX_RECALL_LIMIT`].
61#[must_use]
62pub fn clamp_recall_limit(k: usize) -> usize {
63    k.min(MAX_RECALL_LIMIT)
64}
65
66/// Clamp a caller-supplied `why` hop budget to [`MAX_WHY_HOPS`].
67#[must_use]
68pub fn clamp_hops(hops: usize) -> usize {
69    hops.min(MAX_WHY_HOPS)
70}