lemma/limits.rs
1use crate::error::Error;
2use crate::parsing::source::Source;
3
4pub const MAX_SPEC_NAME_LENGTH: usize = 128;
5pub const MAX_DATA_NAME_LENGTH: usize = 256;
6pub const MAX_RULE_NAME_LENGTH: usize = 256;
7
8/// Maximum character length for a text value (data/runtime input).
9pub const MAX_TEXT_VALUE_LENGTH: usize = 1024;
10
11/// Validate that a name does not exceed the given character limit.
12/// `kind` is a human-readable noun like "spec", "data", "rule", or "type".
13pub fn check_max_length(
14 name: &str,
15 limit: usize,
16 kind: &str,
17 source: Option<Source>,
18) -> Result<(), Error> {
19 if name.len() > limit {
20 return Err(Error::resource_limit_exceeded(
21 format!("max_{kind}_name_length"),
22 format!("{limit} characters"),
23 format!("{} characters", name.len()),
24 format!("Shorten the {kind} name to at most {limit} characters"),
25 source,
26 None,
27 None,
28 ));
29 }
30 Ok(())
31}
32
33/// Limits to prevent abuse and enable predictable resource usage
34///
35/// These limits protect against malicious inputs while being generous enough
36/// for all legitimate use cases.
37#[derive(Debug, Clone)]
38pub struct ResourceLimits {
39 /// Maximum size of one loaded source text in bytes.
40 /// Real usage: ~5KB, Limit: 5MB (1000x)
41 pub max_source_size_bytes: usize,
42
43 /// Maximum expression nesting depth
44 /// Real usage: ~3 levels, Limit: 7. Deeper logic via rule composition.
45 pub max_expression_depth: usize,
46
47 /// Maximum expression nodes per source (parser-level)
48 /// Quick-reject for pathological single sources.
49 pub max_expression_count: usize,
50
51 /// Maximum total expression nodes across all sources (engine-level)
52 /// The real capacity ceiling. pi (~3.1M) — generous for national-scale
53 /// regulatory systems while bounding total engine workload.
54 pub max_total_expression_count: usize,
55
56 /// Maximum size of a single data value in bytes
57 /// Real usage: ~100 bytes, Limit: 1KB (10x)
58 /// Enables server pre-allocation for zero-allocation evaluation
59 pub max_data_value_bytes: usize,
60
61 /// Maximum total bytes to load in one batch (and/or in-memory size of loaded specs)
62 pub max_loaded_bytes: usize,
63
64 /// Maximum number of sources in one load batch (e.g. after expanding paths on disk)
65 pub max_sources: usize,
66
67 /// Maximum expression nodes for one rule after transitive rule inlining
68 /// during planning. Inlining materializes shared subtrees, so a short
69 /// chain of self-doubling rules grows exponentially; this limit rejects
70 /// such chains with a planning error before any tree is materialized.
71 ///
72 /// The default is chosen so the compiled instruction operands always fit
73 /// `u16`: compilation allocates at most two registers (and at most one
74 /// constant/data/veto table entry) per node, and normalization passes
75 /// grow the tree by at most a small constant factor, so 30,000 nodes
76 /// stays well below the 65,535 register ceiling.
77 pub max_normalized_expression_nodes: usize,
78
79 /// Maximum depth of the spec dependency chain (`uses` imports) from the
80 /// root spec. Bounds recursion in dependency discovery and graph building.
81 /// Real usage: ~3 levels, Limit: 32 (10x).
82 pub max_spec_dependency_depth: usize,
83
84 /// Maximum number of specs in one dependency DAG (the root spec plus all
85 /// transitive dependencies). Bounds per-plan memory and planning work.
86 pub max_dag_specs: usize,
87}
88
89impl Default for ResourceLimits {
90 fn default() -> Self {
91 Self {
92 max_source_size_bytes: 5 * 1024 * 1024, // 5 MB
93 max_expression_depth: 7,
94 max_expression_count: 65_536,
95 max_total_expression_count: 3_141_592,
96 max_data_value_bytes: 1024, // 1 KB
97 max_loaded_bytes: 50 * 1024 * 1024, // 50 MB
98 max_sources: 4096,
99 max_normalized_expression_nodes: 30_000,
100 max_spec_dependency_depth: 32,
101 max_dag_specs: 4096,
102 }
103 }
104}