dig_rpc_types/tier.rs
1//! RPC access tiers and the peer-surface allowlist.
2//!
3//! Every DIG-node RPC method belongs to exactly one [`Tier`], its PRIMARY tier.
4//! Three tiers, matching the dual-transport model:
5//!
6//! - [`Tier::PublicRead`] — anonymous, self-verified content + discovery reads
7//! over plain HTTPS (browser) or mTLS. A browser resolves these without a
8//! client cert.
9//! - [`Tier::Peer`] — the mTLS peer surface between DIG nodes (discovery,
10//! availability, range serving). `peer_id = SHA-256(TLS SPKI DER)`.
11//! - [`Tier::Control`] — loopback / in-process only (cache + node lifecycle +
12//! staging). NEVER reachable over the peer surface.
13//!
14//! # The peer allowlist is separate from the tier
15//!
16//! Being `PublicRead` does NOT make a method peer-reachable — the peer surface
17//! is an **allowlist**, not a denylist. Three chain-anchored public reads
18//! (`getAnchoredRoot`, `getCollection`, `listCollectionItems`) are PRIMARY
19//! `PublicRead` yet ALSO answered on the peer surface. The allowlist mirrors the
20//! canonical node's `is_peer_reachable_method` exactly; see
21//! [`Method::is_peer_reachable`](crate::method::Method::is_peer_reachable).
22
23use serde::{Deserialize, Serialize};
24
25/// A method's primary access tier.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
29pub enum Tier {
30 /// Anonymous, self-verified content + discovery reads (plain HTTPS / mTLS).
31 PublicRead,
32 /// The mTLS peer surface between DIG nodes.
33 Peer,
34 /// Loopback / in-process only (cache, lifecycle, staging).
35 Control,
36}
37
38impl Tier {
39 /// A stable, lower-case identifier for the tier (for OpenRPC tags / logs).
40 pub const fn as_str(self) -> &'static str {
41 match self {
42 Tier::PublicRead => "public-read",
43 Tier::Peer => "peer",
44 Tier::Control => "control",
45 }
46 }
47}
48
49impl std::fmt::Display for Tier {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.write_str(self.as_str())
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 /// **Proves:** each tier renders its stable lower-case identifier and
60 /// serializes camelCase, matching the OpenRPC `x-dig-tier` extension.
61 #[test]
62 fn tier_strings_and_serde() {
63 assert_eq!(Tier::PublicRead.as_str(), "public-read");
64 assert_eq!(Tier::Peer.to_string(), "peer");
65 assert_eq!(Tier::Control.as_str(), "control");
66 assert_eq!(
67 serde_json::to_string(&Tier::PublicRead).unwrap(),
68 "\"publicRead\""
69 );
70 assert_eq!(
71 serde_json::from_str::<Tier>("\"peer\"").unwrap(),
72 Tier::Peer
73 );
74 }
75}