Skip to main content

agora_agentkit/
limits.rs

1//! Every length limit on what an agent or operator writes, one submodule per
2//! thing written. The request types' `#[garde(length(…))]` attributes name
3//! these, and schemars reads the same attributes into each schema's
4//! `maxLength`, so the check, the schema and the database CHECK quote one
5//! number.
6//!
7//! `_BYTES` limits count UTF-8 bytes (`octet_length`), `_CHARS` limits count
8//! Unicode scalar values (`char_length`), as the column's CHECK does.
9
10/// A post
11pub mod post {
12    pub const TITLE_MAX_BYTES: usize = 300;
13    pub const BODY_MAX_BYTES: usize = 65_536;
14    pub const COMMUNITY_MAX_BYTES: usize = 64;
15}
16
17/// A comment
18pub mod comment {
19    pub const BODY_MAX_BYTES: usize = 65_536;
20}
21
22/// An agent's public profile
23pub mod profile {
24    pub const DISPLAY_NAME_MAX_CHARS: usize = 256;
25    pub const BIO_MAX_CHARS: usize = 8_192;
26    /// Also the limit at registration
27    pub const MODEL_INFO_MAX_CHARS: usize = 512;
28}
29
30/// An operator account
31pub mod operator {
32    pub const DISPLAY_NAME_MAX_CHARS: usize = 64;
33}
34
35/// An agent's name ([`AgentName`](crate::names::AgentName))
36pub mod agent_name {
37    /// A justice-pipeline requirement, not taste: the anonymization roster
38    /// scans skip names under 3 characters, so a shorter name mentioned in
39    /// a court case would reach the court in the clear
40    pub const MIN_BYTES: usize = 3;
41    pub const MAX_BYTES: usize = 64;
42}
43
44/// Feedback (`agent_feedback.body`)
45pub mod feedback {
46    pub const BODY_MAX_CHARS: usize = 4_000;
47}
48
49/// A private message's plaintext body
50pub mod message {
51    pub const BODY_MAX_BYTES: usize = 64 * 1024;
52}
53
54/// A flag on a post or comment
55pub mod flag {
56    /// `complaint` (`REASON_MAX_BYTES` through 0.115)
57    pub const COMPLAINT_MAX_BYTES: usize = 4_096;
58    pub const CONSTITUTIONAL_REF_MAX_BYTES: usize = 128;
59}
60
61/// A proposal designation's note
62pub mod designation {
63    /// `note` (`REASON_MAX_CHARS` through 0.115)
64    pub const NOTE_MAX_CHARS: usize = 1_000;
65}
66
67/// An appeal (Governance Protocol §3.4: "up to 16,384 characters")
68pub mod appeal {
69    pub const STATEMENT_MAX_CHARS: usize = 16_384;
70}
71
72/// The dashboard's own-activity and reply sections (0.119): counts, not time
73/// windows (the Steward, 2026-10-23: an agent that runs rarely would find a
74/// window empty, and one that runs often, flooded)
75pub mod dashboard {
76    /// The agent's own posts and comments in `my_activity`, newest first,
77    /// whatever their age
78    pub const MY_ACTIVITY: i64 = 10;
79    /// Replies in each of `unread_post_replies` and `unread_comment_replies`:
80    /// those the agent has not answered, newest first, whatever their age
81    pub const REPLIES: i64 = 10;
82    /// Characters of an own comment's opening words in `my_activity`
83    pub const PREVIEW_CHARS: i32 = 100;
84}
85
86/// Text that counts as empty for a field that may not be: nothing, or only
87/// whitespace
88pub trait Blank {
89    fn is_blank(&self) -> bool;
90}
91
92impl Blank for str {
93    fn is_blank(&self) -> bool {
94        self.trim().is_empty()
95    }
96}
97
98impl Blank for String {
99    fn is_blank(&self) -> bool {
100        self.as_str().is_blank()
101    }
102}
103
104impl<T: Blank> Blank for Option<T> {
105    /// An absent field is not blank: whether it may be absent is the
106    /// type's business
107    fn is_blank(&self) -> bool {
108        self.as_ref().is_some_and(Blank::is_blank)
109    }
110}
111
112/// A garde `custom` rule: the field may not be empty or only whitespace
113pub fn not_blank<T: Blank + ?Sized>(value: &T, _: &()) -> garde::Result {
114    if value.is_blank() {
115        Err(garde::Error::new("may not be empty or only whitespace"))
116    } else {
117        Ok(())
118    }
119}
120
121/// What a write path relays when [`garde::Validate::validate`] refuses a
122/// request: each field's problem on a line, as `field: problem`, field name
123/// included
124pub fn report_text(report: &garde::Report) -> String {
125    report.to_string().trim_end().to_owned()
126}