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 UTF-8 byte length of a session label.
54pub const MAX_SESSION_LABEL_BYTES: usize = 256;
55/// Most session ids one heartbeat record lists. A process with more leases in
56/// one stream splits them over several records.
57pub const MAX_HEARTBEAT_SESSIONS: usize = 2048;
58pub const MAX_STATE_PATCH_OPS: usize = 256;
59pub const MAX_STATE_DOCUMENT_BYTES: usize = 8 * 1024 * 1024;
60pub const MAX_MANIFEST_FRAGMENTS: usize = 1024;
61/// Maximum lease holder identity length, in bytes. A holder id is a stable
62/// node or worker identifier that flows into lease matching and audit events,
63/// so it is required to be non-empty and length-bounded. Its charset is open.
64pub const MAX_HOLDER_ID_BYTES: usize = 128;
65/// Shortest lease (and renewal) lifetime a request may ask for, in
66/// microseconds. Unlike a scan limit, a lease TTL is not clamped up by the
67/// store: a grant is only ever shorter than the request, so a floor is the only
68/// place the contract can keep a lease long enough to survive one round trip
69/// plus the holder's own renewal cadence. Below it, a holder would trip its
70/// deadline before it could renew, which is a caller bug worth rejecting
71/// locally rather than a lease worth granting.
72pub const MIN_LEASE_TTL_MICROS: u64 = 1_000_000;
73/// Longest lease (and renewal) lifetime a request may ask for, in
74/// microseconds. A lease is a bounded-TTL lock whose whole purpose is that a
75/// dead holder's ownership expires; an unbounded request would let one crashed
76/// holder park a key for hours. The ceiling matches
77/// [`crate::authz::MAX_OPERATOR_ASSERTION_TTL_MICROS`]: the same "how long
78/// may one unattended grant outlive the process that asked for it" question.
79/// The store may still grant less (`granted_ttl_micros`), so this bounds the
80/// *request*, and a holder needing longer renews.
81pub const MAX_LEASE_TTL_MICROS: u64 = 5 * 60 * 1_000_000;
82
83// Fork caps. A fork id is a caller-chosen name (e.g. `"experiment-2026-q2"`),
84// so its length is a validatable input cap like a KV key: the client rejects an
85// over-long id before the round-trip. Per-deployment resource ceilings (how
86// many forks may exist) are NOT here: those are a managed-side policy a client
87// cannot validate against, surfaced only as a `ForkError`.
88/// Maximum fork id length, in bytes.
89pub const MAX_FORK_ID_BYTES: usize = 128;
90
91// Authorization caps. A role name is an admin-chosen identifier that flows
92// into grant matching, audit events, and console rendering, so it shares the
93// fork-id shape: a strict safelist plus a byte cap every tier validates the
94// same way. 64 matches the common RBAC-identifier norm.
95/// Maximum role name length, in bytes.
96pub const MAX_ROLE_NAME_BYTES: usize = 64;
97
98/// Ceiling on one `[len: u32 LE][bytes]` frame on the managed-command sockets,
99/// enforced by both the server and LaserData Cloud. A reply above it is replaced by
100/// a structured too-large error rather than truncated. The `u32` length prefix
101/// addresses far more (4 GiB), so this is a deliberate per-frame memory bound on
102/// the whole-frame buffer, not a transport limit. Every consumer of the managed
103/// sockets (the server-side dispatch, the streaming server sidecar, the reply-byte budget)
104/// MUST source this one constant rather than redefining its own frame cap, so
105/// the layers cannot disagree and a reply admitted by one is not rejected by the
106/// next. Changing it moves all of them in lockstep.
107pub const MAX_FRAME_BYTES: usize = 64 * 1024 * 1024;
108/// Hard ceiling on a single query reply's serialized bytes. A reply rides the
109/// managed-command socket as one `[len: u32 LE][bytes]` frame, buffered whole,
110/// so it is bounded by [`MAX_FRAME_BYTES`] by construction (the two are held
111/// equal on purpose so anything a backend admits to a reply, the socket can
112/// frame). Larger result sets are not returned as one oversized reply: they
113/// page via [`MAX_PAGE_SIZE`] rows plus `offset`. Raising this means raising
114/// `MAX_FRAME_BYTES` in lockstep across the server, LaserData Cloud, and the
115/// socket buffer, since it is the same frame.
116pub const MAX_QUERY_REPLY_BYTES: usize = MAX_FRAME_BYTES;
117/// Maximum UTF-8 bytes in an operational index name or query engine label.
118pub const MAX_QUERY_NAME_BYTES: usize = 255;
119/// Maximum UTF-8 bytes in one read-only raw SQL statement.
120pub const MAX_RAW_SQL_BYTES: usize = 64 * 1024;
121/// Maximum number of positional raw SQL parameters.
122pub const MAX_QUERY_PARAMETERS: usize = 1024;
123/// Maximum UTF-8 bytes in an opaque query page cursor.
124pub const MAX_QUERY_CURSOR_BYTES: usize = 512;
125/// Maximum predicate nodes in one structured filter tree.
126pub const MAX_QUERY_PREDICATES: usize = 4096;
127/// Maximum selected, grouped, sorted, or exact-match fields in one query.
128pub const MAX_QUERY_FIELDS: usize = 4096;
129/// Maximum vector dimensions accepted by the generic query contract.
130pub const MAX_VECTOR_DIMENSIONS: usize = 65_536;
131
132/// The most managed requests one mixed-operation batch
133/// ([`AGDX_BATCH_CODE`](crate::codes::AGDX_BATCH_CODE)) may carry. Bounds the
134/// server work one frame can demand. The assembled reply is additionally
135/// bounded by [`MAX_FRAME_BYTES`] like any other.
136pub const MAX_BATCH_OPS: usize = 64;
137
138// Agent Data Exchange Protocol (AGDX) envelope caps, sized to sit inside the existing cap
139// family. The metadata caps are the load-bearing ones: that field is
140// bridge-injected, so a hostile or buggy edge gets a publish-time rejection
141// instead of inflating every record on a topic.
142/// Cap on the envelope's vocabulary strings (`operation`, `tool`,
143/// `finish_reason`), each.
144pub const MAX_AGENT_STRING_BYTES: usize = 256;
145/// Cap on a producer-supplied idempotency key.
146pub const MAX_IDEMPOTENCY_KEY_BYTES: usize = 64;
147/// Max entries in an envelope's `metadata` map.
148pub const MAX_METADATA_ENTRIES: usize = 32;
149/// Max bytes in one `metadata` key.
150pub const MAX_METADATA_KEY_BYTES: usize = 256;
151/// Max bytes in one `metadata` value (scalar/text size).
152pub const MAX_METADATA_VALUE_BYTES: usize = 1024;
153/// Max total bytes across the whole `metadata` map.
154pub const MAX_METADATA_TOTAL_BYTES: usize = 8192;
155/// Cap on a [`BodyRef`](crate::agent::BodyRef) `reference` string (a URI,
156/// object key, or KV key naming where the externalized body lives).
157pub const MAX_BODY_REFERENCE_BYTES: usize = 1024;
158/// Max capability entries on an [`AgentCard`](crate::agent::AgentCard).
159pub const MAX_CARD_CAPABILITIES: usize = 64;
160/// Ceiling on a connection's advertised metadata (`AGDX_SET_CLIENT_METADATA`).
161/// The metadata is an opaque byte payload any client may set, not agent-only: an
162/// agent advertises its card under the AGDX schema, a regular app sets whatever
163/// blob its own tooling and the console interpret. The ceiling bounds the
164/// per-connection state the streaming server holds and, because the discovery
165/// read (`AGDX_GET_CLIENTS_METADATA`) returns a page of N connections at once, it
166/// also bounds a page at `N * this`. 64 KiB is generous for any card or app blob
167/// while keeping a page well under the frame cap even at the max page size.
168pub const MAX_CLIENT_METADATA: usize = 64 * 1024;
169
170// Memory and graph caps, sized inside the existing cap family. A memory body
171// shares the opaque-value ceiling. A recall page shares the query page cap.
172/// Max bytes in one memory item's body (shares the opaque-value ceiling).
173pub const MAX_MEMORY_BODY_BYTES: usize = MAX_VALUE_BYTES;
174/// Max items a single recall returns (shares the query page cap).
175pub const MAX_RECALL_LIMIT: usize = MAX_PAGE_SIZE;
176
177/// Byte cap on a lexical search's query text (`TextQuery.query`).
178pub const MAX_TEXT_QUERY_BYTES: usize = 1024;
179/// Recall page size when the caller sets none.
180pub const DEFAULT_RECALL_LIMIT: usize = DEFAULT_STREAM_PAGE_SIZE;
181/// Max tags on one memory item.
182pub const MAX_MEMORY_TAGS: usize = 16;
183/// Max bytes in one memory tag.
184pub const MAX_MEMORY_TAG_BYTES: usize = 64;
185/// Maximum graph name length, in bytes. Bounded and control-character-free
186/// like a namespace: the name keys the projection registry and every
187/// traversal request.
188pub const MAX_GRAPH_NAME_BYTES: usize = 128;
189/// Hard ceiling on the hop depth a single graph traversal may request.
190pub const MAX_GRAPH_TRAVERSE_DEPTH: u32 = 8;
191/// Hard ceiling on nodes plus edges in one graph reply.
192pub const MAX_GRAPH_RESULT_ELEMENTS: usize = 10_000;
193/// Max labels on one graph node.
194pub const MAX_GRAPH_NODE_LABELS: usize = 16;
195/// Max encoded bytes of a node or edge `source` provenance reference. A source
196/// names a stream, topic, key, or id, all of which are bounded inputs already,
197/// so this is a defensive ceiling against a hostile or buggy upsert inflating
198/// per-element state. Sized at two key-lengths, since the largest variant (a
199/// key-value source) names both a namespace and a key, far under the opaque
200/// value ceiling, as a source ref is a short pointer, not a payload.
201pub const MAX_SOURCE_REF_BYTES: usize = 2 * MAX_KEY_BYTES;
202
203// Consumer-filter caps. A filter is evaluated per record on a streaming server
204// shard, so its size and shape are bounded before it compiles.
205/// Maximum encoded writer schema used by a consumer-filter decoder.
206pub const MAX_FILTER_SCHEMA_BYTES: usize = 1024 * 1024;
207/// Maximum nesting in an Avro writer-schema document.
208pub const MAX_FILTER_SCHEMA_DEPTH: usize = 64;
209/// Max encoded bytes of one consumer filter.
210pub const MAX_FILTER_BYTES: usize = 8192;
211/// Max expression nodes in one filter, literals inside lists included.
212pub const MAX_FILTER_NODES: usize = 128;
213/// Max nesting depth of a filter expression.
214pub const MAX_FILTER_DEPTH: usize = 8;
215/// Max JSON nesting depth a payload decoder accepts. Every server bound and
216/// SDK guard uses this one value. It stays below the `serde_json` recursion
217/// limit, so an over-deep payload is reported as `too_deep`, not `malformed`.
218pub const MAX_FILTER_PARSE_DEPTH: usize = 127;
219/// Max segments in one field path.
220pub const MAX_FILTER_PATH_SEGMENTS: usize = 16;
221/// Max bytes in one field path or header key.
222pub const MAX_FILTER_PATH_BYTES: usize = 256;
223/// Max items in one `in` list.
224pub const MAX_FILTER_LIST_ITEMS: usize = 64;
225/// Max bytes in one string literal.
226pub const MAX_FILTER_STRING_BYTES: usize = 1024;
227/// Max bytes in a saved filter's name.
228pub const MAX_FILTER_NAME_BYTES: usize = 128;
229/// Max bytes in a saved filter's description.
230pub const MAX_FILTER_DESCRIPTION_BYTES: usize = 1024;
231/// Max bytes in a stream, topic, consumer, or group name a filtered read names.
232pub const MAX_FILTER_SOURCE_NAME_BYTES: usize = 255;
233/// Max records one filtered page returns.
234pub const MAX_FILTERED_PAGE_RECORDS: u32 = 1000;
235/// Max reply bytes one filtered page may carry.
236pub const MAX_FILTERED_PAGE_BYTES: u32 = 8 * 1024 * 1024;
237/// Max source records one filtered page may ask to examine. The server
238/// lowers it to its own budget.
239pub const MAX_FILTERED_PAGE_EXAMINED: u32 = 100_000;
240/// Max records one preview may examine.
241pub const MAX_FILTER_PREVIEW_EXAMINED: u32 = 10_000;
242/// Max records one preview returns.
243pub const MAX_FILTER_PREVIEW_RECORDS: u32 = 100;
244/// Max payload bytes a preview record carries as text.
245pub const MAX_FILTER_PREVIEW_PAYLOAD_BYTES: usize = 4096;
246/// Max payload bytes a supplied test sample may carry.
247pub const MAX_FILTER_SAMPLE_BYTES: usize = 1024 * 1024;
248/// Max headers a supplied test sample may carry.
249pub const MAX_FILTER_SAMPLE_HEADERS: usize = 64;
250/// Max page size of a catalog list.
251pub const MAX_FILTER_CATALOG_PAGE: u32 = 200;