soma_domain/scopes.rs
1//! Canonical Soma authorization scope constants.
2//!
3//! Single source of truth for every scope name Soma issues or checks, and for
4//! the write-implies-read satisfaction rule. Formerly split across
5//! `soma-contracts`' `actions.rs` (`READ_SCOPE`/`WRITE_SCOPE`/`DENY_SCOPE`/
6//! `scopes_satisfy`) and `scopes.rs` (`ADMIN_SCOPE`/`has_admin_scope`);
7//! merged here since they are the same invariant-value concept.
8
9/// Read scope: grants access to read-only actions.
10pub const READ_SCOPE: &str = "soma:read";
11/// Write scope: grants mutating actions and satisfies [`READ_SCOPE`].
12pub const WRITE_SCOPE: &str = "soma:write";
13/// Sentinel scope no token can hold; assigned to unknown actions to deny them.
14pub const DENY_SCOPE: &str = "soma:__deny__";
15/// Admin scope: grants administrative actions.
16pub const ADMIN_SCOPE: &str = "soma:admin";
17
18/// Returns true if `token_scopes` satisfy `required`.
19/// Write scope satisfies read (write includes read).
20/// Single source of truth - called from both REST and MCP enforcement paths.
21pub fn scopes_satisfy(token_scopes: &[String], required: &str) -> bool {
22 token_scopes
23 .iter()
24 .any(|s| s == required || (required == READ_SCOPE && s == WRITE_SCOPE))
25}
26
27/// Returns true if `scopes` contains [`ADMIN_SCOPE`].
28pub fn has_admin_scope(scopes: &[String]) -> bool {
29 scopes.iter().any(|scope| scope == ADMIN_SCOPE)
30}
31
32#[cfg(test)]
33#[path = "scopes_tests.rs"]
34mod tests;