didbot_lexicon/nsid.rs
1//! Namespace identifiers for this project's lexicons.
2//!
3//! What an account *is* is this server's to define and lives under
4//! [`IDENTITY_AUTHORITY`].
5//!
6//! A record's authority is a fact about who defines its shape, not about who
7//! writes it: this server stores records it did not define, which is
8//! ordinary — a lexicon is the world's rather than its author's.
9
10/// Reversed-domain authority for the records this server defines.
11pub const IDENTITY_AUTHORITY: &str = "bot.did";
12
13/// Every authority whose records this project holds a schema for.
14pub const AUTHORITIES: &[&str] = &[IDENTITY_AUTHORITY];
15
16/// Builds an NSID under [`IDENTITY_AUTHORITY`].
17///
18/// ```
19/// use didbot_lexicon::nsid;
20/// assert_eq!(nsid::under("registration"), "bot.did.registration");
21/// assert_eq!(nsid::under("operator"), "bot.did.operator");
22/// ```
23pub fn under(name: &str) -> String {
24 format!("{IDENTITY_AUTHORITY}.{name}")
25}
26
27/// The hosting server's record of registering an account and keeping it,
28/// whatever kind of account it is. Not an identity — that is the DID and the
29/// keys in its document — and not a credential. It includes how the account
30/// was admitted.
31pub const REGISTRATION: &str = "bot.did.registration";
32
33/// A claim that the writer operates one server, written in the operator's
34/// own repository. See `plan/handshake.md`: this is the record a server
35/// polls for to learn who operates it, and whose disappearance (past a
36/// grace window) ends the relationship.
37pub const OPERATOR: &str = "bot.did.operator";
38
39/// The server's record of one way a process logs in as an account.
40pub const CREDENTIAL: &str = "bot.did.credential";
41
42/// A policy that determines which actions a DID may take.
43pub const POLICY: &str = "bot.did.policy";
44
45/// A binding of one or more policies to one or more subject DIDs.
46pub const POLICY_BINDING: &str = "bot.did.policyBinding";
47
48/// Every record NSID this project holds a schema for.
49///
50/// Not the set of collections an account may write. A repository is the
51/// account's and the lexicons are the world's, so the server stores a record
52/// in any collection that is a well-formed NSID and validates it against a
53/// schema only when it holds one. This list is what "holds one" means,
54/// and nothing else.
55pub const ALL: &[&str] = &[REGISTRATION, OPERATOR, CREDENTIAL, POLICY, POLICY_BINDING];
56
57/// Every XRPC method NSID this project defines a lexicon document for.
58///
59/// One per `bot.did.*` route the server answers, in the order
60/// `didbot_serve::BOT_DID_METHODS` lists them. Separate from [`ALL`] because
61/// the two answer different questions: [`ALL`] is the set of collections a
62/// record can be validated against, and a method is not a collection.
63pub const METHODS: &[&str] = &[
64 "bot.did.activateAccount",
65 "bot.did.approveAuthorization",
66 "bot.did.createAccount",
67 "bot.did.createBrowserSignIn",
68 "bot.did.createSession",
69 "bot.did.deactivateAccount",
70 "bot.did.declineAuthorization",
71 "bot.did.deleteAccount",
72 "bot.did.describeParkedKey",
73 "bot.did.freezeAccount",
74 "bot.did.getAccountLedger",
75 "bot.did.getAuthorization",
76 "bot.did.listAccountLedgers",
77 "bot.did.listAccounts",
78 "bot.did.listPendingAuthorizations",
79 "bot.did.parkKey",
80 "bot.did.pollOperatorClaim",
81 "bot.did.stats",
82 "bot.did.unfreezeAccount",
83 "bot.did.updateCredentials",
84];
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn every_constant_sits_under_a_known_authority() {
92 for id in ALL.iter().chain(METHODS) {
93 assert!(
94 AUTHORITIES.iter().any(|a| id.starts_with(&format!("{a}."))),
95 "{id} is under no authority this crate names"
96 );
97 }
98 }
99
100 #[test]
101 fn under_agrees_with_the_constants() {
102 assert_eq!(under("registration"), REGISTRATION);
103 assert_eq!(under("operator"), OPERATOR);
104 assert_eq!(under("policy"), POLICY);
105 assert_eq!(under("policyBinding"), POLICY_BINDING);
106 }
107
108 #[test]
109 fn every_authority_is_a_reversed_domain_with_at_least_two_segments() {
110 // An NSID authority must have at least two segments, so that a record
111 // collection always contains at least two dots. atproto Spaces relies
112 // on exactly this to tell a collection apart from a space marker.
113 for authority in AUTHORITIES {
114 assert!(authority.split('.').count() >= 2);
115 }
116 }
117}