Skip to main content

laser_wire/
limits.rs

1// Wire caps, enforced client-side so an oversized op fails fast instead of
2// round-tripping, and server-side so a hostile client cannot inflate state.
3
4/// Hard ceiling on rows in a single query reply. A `limit` above it is
5/// rejected with `QueryError::TooLarge`, and a `0` limit defaults to a full
6/// page. Callers page through larger result sets with `offset`.
7pub const MAX_PAGE_SIZE: usize = 1000;
8/// Page size a streaming reader pulls when the caller has not set an explicit
9/// limit. Large enough to amortize round-trips, small enough that an
10/// unbounded scan does not spike memory.
11pub const DEFAULT_STREAM_PAGE_SIZE: usize = 100;
12/// Hard cap on the number of `agdx.idx.*` headers a single record may carry.
13/// Total header byte size is already capped, but a buggy producer could stamp
14/// dozens of tiny indexed scalars under the byte budget and slow the
15/// projector. 32 covers every legitimate analytics row with head-room.
16pub const MAX_INDEX_ENTRIES_PER_RECORD: usize = 32;
17/// Cap on the payload bytes the projector **inlines into a materialized row**
18/// (when `inline_payload` is set). This bounds only the copy kept alongside the
19/// indexed row in the read-model backend, never the original message: the Iggy
20/// log always retains the full bytes and a fetch can replay them. Held at
21/// [`MAX_VALUE_BYTES`] (8 MiB) so a single inlined body and a single KV value
22/// share one "max opaque value" ceiling. A body above it still indexes and
23/// still lives in the log. It is just not duplicated into the row, so a typed
24/// fetch decodes from the log (or a claim-check [`ContentType::Ref`] body). The
25/// cap exists because multi-MB BLOBs per row bloat the embedded query DB and
26/// slow scans.
27///
28/// [`ContentType::Ref`]: crate::content::ContentType::Ref
29pub const MAX_PROJECTOR_PAYLOAD_BYTES: usize = MAX_VALUE_BYTES;
30
31// KV caps. Keys are arbitrary bytes (text, binary, anything), at
32// most 512 bytes. Values are arbitrary opaque bytes, capped at 8 MiB:
33// generous for session, flag, counter, cached-JSON, and chunked working state,
34// yet well under the frame ceiling so a set request and a get reply each ride
35// one socket frame with envelope head-room.
36/// Maximum KV key length, in bytes.
37pub const MAX_KEY_BYTES: usize = 512;
38/// Maximum KV value size, in bytes.
39pub const MAX_VALUE_BYTES: usize = 8 * 1024 * 1024;
40/// Hard ceiling on a KV scan page (LaserData Cloud clamps to it).
41pub const MAX_SCAN_LIMIT: usize = 1000;
42/// KV scan page size when the caller sets none.
43pub const DEFAULT_SCAN_LIMIT: usize = 100;
44/// The namespace a KV call without an explicit one binds to. A namespace is a
45/// logical bucket: keys are unique within it, scans are scoped to it, and one
46/// user's namespaces stay isolated from another's.
47pub const DEFAULT_NAMESPACE: &str = "default";
48/// Maximum KV (and memory) namespace length, in bytes. A namespace is a
49/// caller-chosen bucket name that flows into grant matching and scan scoping,
50/// so it is bounded and control-character-free, while its charset stays open
51/// (namespaces legitimately carry `/`-style hierarchy).
52pub const MAX_NAMESPACE_BYTES: usize = 128;
53/// Maximum lease holder identity length, in bytes. A holder id is a stable
54/// node or worker identifier that flows into lease matching and audit events,
55/// so it is required to be non-empty and length-bounded. Its charset is open.
56pub const MAX_HOLDER_ID_BYTES: usize = 128;
57/// Shortest lease (and renewal) lifetime a request may ask for, in
58/// microseconds. Unlike a scan limit, a lease TTL is not clamped up by the
59/// store: a grant is only ever shorter than the request, so a floor is the only
60/// place the contract can keep a lease long enough to survive one round trip
61/// plus the holder's own renewal cadence. Below it, a holder would trip its
62/// deadline before it could renew, which is a caller bug worth rejecting
63/// locally rather than a lease worth granting.
64pub const MIN_LEASE_TTL_MICROS: u64 = 1_000_000;
65/// Longest lease (and renewal) lifetime a request may ask for, in
66/// microseconds. A lease is a bounded-TTL lock whose whole purpose is that a
67/// dead holder's ownership expires; an unbounded request would let one crashed
68/// holder park a key for hours. The ceiling matches
69/// [`crate::authz::MAX_SUPERVISOR_ASSERTION_TTL_MICROS`]: the same "how long
70/// may one unattended grant outlive the process that asked for it" question.
71/// The store may still grant less (`granted_ttl_micros`), so this bounds the
72/// *request*, and a holder needing longer renews.
73pub const MAX_LEASE_TTL_MICROS: u64 = 5 * 60 * 1_000_000;
74
75// Fork caps. A fork id is a caller-chosen name (e.g. `"experiment-2026-q2"`),
76// so its length is a validatable input cap like a KV key: the client rejects an
77// over-long id before the round-trip. Per-deployment resource ceilings (how
78// many forks may exist) are NOT here: those are a managed-side policy a client
79// cannot validate against, surfaced only as a `ForkError`.
80/// Maximum fork id length, in bytes.
81pub const MAX_FORK_ID_BYTES: usize = 128;
82
83// Authorization caps. A role name is an admin-chosen identifier that flows
84// into grant matching, audit events, and console rendering, so it shares the
85// fork-id shape: a strict safelist plus a byte cap every tier validates the
86// same way. 64 matches the common RBAC-identifier norm.
87/// Maximum role name length, in bytes.
88pub const MAX_ROLE_NAME_BYTES: usize = 64;
89
90/// Ceiling on one `[len: u32 LE][bytes]` frame on the managed-command sockets,
91/// enforced by both the server and LaserData Cloud. A reply above it is replaced by
92/// a structured too-large error rather than truncated. The `u32` length prefix
93/// addresses far more (4 GiB), so this is a deliberate per-frame memory bound on
94/// the whole-frame buffer, not a transport limit. Every consumer of the managed
95/// sockets (the server-side dispatch, the streaming server sidecar, the reply-byte budget)
96/// MUST source this one constant rather than redefining its own frame cap, so
97/// the layers cannot disagree and a reply admitted by one is not rejected by the
98/// next. Changing it moves all of them in lockstep.
99pub const MAX_FRAME_BYTES: usize = 64 * 1024 * 1024;
100/// Hard ceiling on a single query reply's serialized bytes. A reply rides the
101/// managed-command socket as one `[len: u32 LE][bytes]` frame, buffered whole,
102/// so it is bounded by [`MAX_FRAME_BYTES`] by construction (the two are held
103/// equal on purpose so anything a backend admits to a reply, the socket can
104/// frame). Larger result sets are not returned as one oversized reply: they
105/// page via [`MAX_PAGE_SIZE`] rows plus `offset`. Raising this means raising
106/// `MAX_FRAME_BYTES` in lockstep across the server, LaserData Cloud, and the
107/// socket buffer, since it is the same frame.
108pub const MAX_QUERY_REPLY_BYTES: usize = MAX_FRAME_BYTES;
109/// Maximum UTF-8 bytes in an operational index name or query engine label.
110pub const MAX_QUERY_NAME_BYTES: usize = 255;
111/// Maximum UTF-8 bytes in one read-only raw SQL statement.
112pub const MAX_RAW_SQL_BYTES: usize = 64 * 1024;
113/// Maximum number of positional raw SQL parameters.
114pub const MAX_QUERY_PARAMETERS: usize = 1024;
115/// Maximum UTF-8 bytes in an opaque query page cursor.
116pub const MAX_QUERY_CURSOR_BYTES: usize = 512;
117/// Maximum predicate nodes in one structured filter tree.
118pub const MAX_QUERY_PREDICATES: usize = 4096;
119/// Maximum selected, grouped, sorted, or exact-match fields in one query.
120pub const MAX_QUERY_FIELDS: usize = 4096;
121/// Maximum vector dimensions accepted by the generic query contract.
122pub const MAX_VECTOR_DIMENSIONS: usize = 65_536;
123
124/// The most managed requests one mixed-operation batch
125/// ([`AGDX_BATCH_CODE`](crate::codes::AGDX_BATCH_CODE)) may carry. Bounds the
126/// server work one frame can demand. The assembled reply is additionally
127/// bounded by [`MAX_FRAME_BYTES`] like any other.
128pub const MAX_BATCH_OPS: usize = 64;
129
130// Agent Data Exchange Protocol (AGDX) envelope caps, sized to sit inside the existing cap
131// family. The metadata caps are the load-bearing ones: that field is
132// bridge-injected, so a hostile or buggy edge gets a publish-time rejection
133// instead of inflating every record on a topic.
134/// Cap on the envelope's vocabulary strings (`operation`, `tool`,
135/// `finish_reason`), each.
136pub const MAX_AGENT_STRING_BYTES: usize = 256;
137/// Cap on a producer-supplied idempotency key.
138pub const MAX_IDEMPOTENCY_KEY_BYTES: usize = 64;
139/// Max entries in an envelope's `metadata` map.
140pub const MAX_METADATA_ENTRIES: usize = 32;
141/// Max bytes in one `metadata` key.
142pub const MAX_METADATA_KEY_BYTES: usize = 256;
143/// Max bytes in one `metadata` value (scalar/text size).
144pub const MAX_METADATA_VALUE_BYTES: usize = 1024;
145/// Max total bytes across the whole `metadata` map.
146pub const MAX_METADATA_TOTAL_BYTES: usize = 8192;
147/// Cap on a [`BodyRef`](crate::agent::BodyRef) `reference` string (a URI,
148/// object key, or KV key naming where the externalized body lives).
149pub const MAX_BODY_REFERENCE_BYTES: usize = 1024;
150/// Max capability entries on an [`AgentCard`](crate::agent::AgentCard).
151pub const MAX_CARD_CAPABILITIES: usize = 64;
152/// Ceiling on a connection's advertised metadata (`AGDX_SET_CLIENT_METADATA`).
153/// The metadata is an opaque byte payload any client may set, not agent-only: an
154/// agent advertises its card under the AGDX schema, a regular app sets whatever
155/// blob its own tooling and the console interpret. The ceiling bounds the
156/// per-connection state the streaming server holds and, because the discovery
157/// read (`AGDX_GET_CLIENTS_METADATA`) returns a page of N connections at once, it
158/// also bounds a page at `N * this`. 64 KiB is generous for any card or app blob
159/// while keeping a page well under the frame cap even at the max page size.
160pub const MAX_CLIENT_METADATA: usize = 64 * 1024;
161
162// Memory and graph caps, sized inside the existing cap family. A memory body
163// shares the opaque-value ceiling. A recall page shares the query page cap.
164/// Max bytes in one memory item's body (shares the opaque-value ceiling).
165pub const MAX_MEMORY_BODY_BYTES: usize = MAX_VALUE_BYTES;
166/// Max items a single recall returns (shares the query page cap).
167pub const MAX_RECALL_LIMIT: usize = MAX_PAGE_SIZE;
168
169/// Byte cap on a lexical search's query text (`TextQuery.query`).
170pub const MAX_TEXT_QUERY_BYTES: usize = 1024;
171/// Recall page size when the caller sets none.
172pub const DEFAULT_RECALL_LIMIT: usize = DEFAULT_STREAM_PAGE_SIZE;
173/// Max tags on one memory item.
174pub const MAX_MEMORY_TAGS: usize = 16;
175/// Max bytes in one memory tag.
176pub const MAX_MEMORY_TAG_BYTES: usize = 64;
177/// Maximum graph name length, in bytes. Bounded and control-character-free
178/// like a namespace: the name keys the projection registry and every
179/// traversal request.
180pub const MAX_GRAPH_NAME_BYTES: usize = 128;
181/// Hard ceiling on the hop depth a single graph traversal may request.
182pub const MAX_GRAPH_TRAVERSE_DEPTH: u32 = 8;
183/// Hard ceiling on nodes plus edges in one graph reply.
184pub const MAX_GRAPH_RESULT_ELEMENTS: usize = 10_000;
185/// Max labels on one graph node.
186pub const MAX_GRAPH_NODE_LABELS: usize = 16;
187/// Max encoded bytes of a node or edge `source` provenance reference. A source
188/// names a stream, topic, key, or id, all of which are bounded inputs already,
189/// so this is a defensive ceiling against a hostile or buggy upsert inflating
190/// per-element state. Sized at two key-lengths, since the largest variant (a
191/// key-value source) names both a namespace and a key, far under the opaque
192/// value ceiling, as a source ref is a short pointer, not a payload.
193pub const MAX_SOURCE_REF_BYTES: usize = 2 * MAX_KEY_BYTES;
194
195// Consumer-filter caps. A filter is evaluated per record on a streaming server
196// shard, so its size and shape are bounded before it compiles.
197/// Maximum encoded writer schema used by a consumer-filter decoder.
198pub const MAX_FILTER_SCHEMA_BYTES: usize = 1024 * 1024;
199/// Maximum nesting in an Avro writer-schema document.
200pub const MAX_FILTER_SCHEMA_DEPTH: usize = 64;
201/// Max encoded bytes of one consumer filter.
202pub const MAX_FILTER_BYTES: usize = 8192;
203/// Max expression nodes in one filter, literals inside lists included.
204pub const MAX_FILTER_NODES: usize = 128;
205/// Max nesting depth of a filter expression.
206pub const MAX_FILTER_DEPTH: usize = 8;
207/// Max JSON nesting depth a payload decoder accepts. Every server bound and
208/// SDK guard uses this one value. It stays below the `serde_json` recursion
209/// limit, so an over-deep payload is reported as `too_deep`, not `malformed`.
210pub const MAX_FILTER_PARSE_DEPTH: usize = 127;
211/// Max segments in one field path.
212pub const MAX_FILTER_PATH_SEGMENTS: usize = 16;
213/// Max bytes in one field path or header key.
214pub const MAX_FILTER_PATH_BYTES: usize = 256;
215/// Max items in one `in` list.
216pub const MAX_FILTER_LIST_ITEMS: usize = 64;
217/// Max bytes in one string literal.
218pub const MAX_FILTER_STRING_BYTES: usize = 1024;
219/// Max bytes in a saved filter's name.
220pub const MAX_FILTER_NAME_BYTES: usize = 128;
221/// Max bytes in a saved filter's description.
222pub const MAX_FILTER_DESCRIPTION_BYTES: usize = 1024;
223/// Max bytes in a stream, topic, consumer, or group name a filtered read names.
224pub const MAX_FILTER_SOURCE_NAME_BYTES: usize = 255;
225/// Max records one filtered page returns.
226pub const MAX_FILTERED_PAGE_RECORDS: u32 = 1000;
227/// Max reply bytes one filtered page may carry.
228pub const MAX_FILTERED_PAGE_BYTES: u32 = 8 * 1024 * 1024;
229/// Max source records one filtered page may ask to examine. The server
230/// lowers it to its own budget.
231pub const MAX_FILTERED_PAGE_EXAMINED: u32 = 100_000;
232/// Max records one preview may examine.
233pub const MAX_FILTER_PREVIEW_EXAMINED: u32 = 10_000;
234/// Max records one preview returns.
235pub const MAX_FILTER_PREVIEW_RECORDS: u32 = 100;
236/// Max payload bytes a preview record carries as text.
237pub const MAX_FILTER_PREVIEW_PAYLOAD_BYTES: usize = 4096;
238/// Max payload bytes a supplied test sample may carry.
239pub const MAX_FILTER_SAMPLE_BYTES: usize = 1024 * 1024;
240/// Max headers a supplied test sample may carry.
241pub const MAX_FILTER_SAMPLE_HEADERS: usize = 64;
242/// Max page size of a catalog list.
243pub const MAX_FILTER_CATALOG_PAGE: u32 = 200;