Skip to main content

systemprompt_models/documents/
access.rs

1//! Document access levels, the actions they gate, the provenance
2//! vocabulary recorded on every revision, and the sharing audit and access
3//! request vocabulary.
4//!
5//! [`AccessLevel`] is totally ordered: holding a level grants every level
6//! below it, so a grant check is a single comparison against
7//! [`DocumentAction::required_level`].
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use std::fmt;
13use std::str::FromStr;
14
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
19#[error("unknown {kind} `{value}`")]
20pub struct UnknownDocumentValue {
21    pub kind: &'static str,
22    pub value: String,
23}
24
25macro_rules! wire_enum {
26    ($name:ident, $kind:literal, { $($variant:ident => $wire:literal),+ $(,)? }) => {
27        impl $name {
28            pub const ALL: &'static [Self] = &[$(Self::$variant),+];
29
30            #[must_use]
31            pub const fn as_str(self) -> &'static str {
32                match self {
33                    $(Self::$variant => $wire),+
34                }
35            }
36        }
37
38        impl fmt::Display for $name {
39            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40                f.write_str(self.as_str())
41            }
42        }
43
44        impl FromStr for $name {
45            type Err = UnknownDocumentValue;
46
47            fn from_str(s: &str) -> Result<Self, Self::Err> {
48                match s {
49                    $($wire => Ok(Self::$variant),)+
50                    other => Err(UnknownDocumentValue {
51                        kind: $kind,
52                        value: other.to_owned(),
53                    }),
54                }
55            }
56        }
57    };
58}
59
60#[derive(
61    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
62)]
63#[serde(rename_all = "snake_case")]
64pub enum AccessLevel {
65    View,
66    Comment,
67    Edit,
68    Share,
69    Own,
70}
71
72wire_enum!(AccessLevel, "access level", {
73    View => "view",
74    Comment => "comment",
75    Edit => "edit",
76    Share => "share",
77    Own => "own",
78});
79
80/// An operation on a document or folder, as named in an authz request.
81///
82/// `Create` is checked against the destination folder; every other action
83/// is checked against the document itself.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
85#[serde(rename_all = "snake_case")]
86pub enum DocumentAction {
87    View,
88    Comment,
89    Edit,
90    Share,
91    Own,
92    Create,
93    Delete,
94}
95
96wire_enum!(DocumentAction, "document action", {
97    View => "view",
98    Comment => "comment",
99    Edit => "edit",
100    Share => "share",
101    Own => "own",
102    Create => "create",
103    Delete => "delete",
104});
105
106impl DocumentAction {
107    #[must_use]
108    pub const fn required_level(self) -> AccessLevel {
109        match self {
110            Self::View => AccessLevel::View,
111            Self::Comment => AccessLevel::Comment,
112            Self::Edit | Self::Create => AccessLevel::Edit,
113            Self::Share => AccessLevel::Share,
114            Self::Own | Self::Delete => AccessLevel::Own,
115        }
116    }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "snake_case")]
121pub enum ActorKind {
122    User,
123    Agent,
124    Bridge,
125    Import,
126    System,
127}
128
129wire_enum!(ActorKind, "document actor kind", {
130    User => "user",
131    Agent => "agent",
132    Bridge => "bridge",
133    Import => "import",
134    System => "system",
135});
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "snake_case")]
139pub enum RevisionOriginKind {
140    McpExecution,
141    BridgeDevice,
142    WebSession,
143    EditSession,
144    Api,
145}
146
147wire_enum!(RevisionOriginKind, "revision origin kind", {
148    McpExecution => "mcp_execution",
149    BridgeDevice => "bridge_device",
150    WebSession => "web_session",
151    EditSession => "edit_session",
152    Api => "api",
153});
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
156#[serde(rename_all = "snake_case")]
157pub enum ProposalStatus {
158    Open,
159    Accepted,
160    Rejected,
161    Superseded,
162}
163
164wire_enum!(ProposalStatus, "proposal status", {
165    Open => "open",
166    Accepted => "accepted",
167    Rejected => "rejected",
168    Superseded => "superseded",
169});
170
171/// How an agent's edit under a folder lands: straight onto the head, or as
172/// a proposal a reviewer accepts. A folder inherits `propose` from the
173/// nearest ancestor that sets it.
174#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
175#[serde(rename_all = "snake_case")]
176pub enum AgentEditPolicy {
177    #[default]
178    Direct,
179    Propose,
180}
181
182wire_enum!(AgentEditPolicy, "agent edit policy", {
183    Direct => "direct",
184    Propose => "propose",
185});
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
188#[serde(rename_all = "snake_case")]
189pub enum EditMode {
190    Land,
191    Propose,
192}
193
194wire_enum!(EditMode, "edit mode", {
195    Land => "land",
196    Propose => "propose",
197});
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
200#[serde(rename_all = "snake_case")]
201pub enum GrantEventKind {
202    Granted,
203    Changed,
204    Revoked,
205    RevokedAll,
206    Transferred,
207    Expired,
208}
209
210wire_enum!(GrantEventKind, "grant event", {
211    Granted => "granted",
212    Changed => "changed",
213    Revoked => "revoked",
214    RevokedAll => "revoked_all",
215    Transferred => "transferred",
216    Expired => "expired",
217});
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
220#[serde(rename_all = "snake_case")]
221pub enum AccessRequestStatus {
222    Open,
223    Granted,
224    Declined,
225}
226
227wire_enum!(AccessRequestStatus, "access request status", {
228    Open => "open",
229    Granted => "granted",
230    Declined => "declined",
231});