use core::marker::PhantomData;
use crate::proto::Nsid;
use crate::sealed;
pub use kryphocron_lexicons::{Tier, UnknownNsid, Visibility};
#[must_use]
pub fn visible_to(tier: Tier, ctx: &crate::ingress::AuthContext<'_>) -> Visibility {
use crate::ingress::Requester;
match (tier, ctx.requester()) {
(Tier::Public, _) => Visibility::Visible,
(Tier::Private, Requester::Service(_)) => Visibility::Visible,
(Tier::Private, _) => Visibility::Hidden,
(_, _) => Visibility::Hidden,
}
}
pub trait TierWitness: sealed::Sealed + 'static {
const TIER: Tier;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PublicTier;
impl sealed::Sealed for PublicTier {}
impl TierWitness for PublicTier {
const TIER: Tier = Tier::Public;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PrivateTier;
impl sealed::Sealed for PrivateTier {}
impl TierWitness for PrivateTier {
const TIER: Tier = Tier::Private;
}
pub trait HasNsid: sealed::Sealed + 'static {
const NSID: &'static str;
type Tier: TierWitness;
fn nsid(&self) -> Nsid {
Nsid::new(Self::NSID).expect("HasNsid::NSID must be a valid NSID literal")
}
}
pub struct Tiered<T, Ti: TierWitness> {
inner: T,
_tier: PhantomData<Ti>,
_private: PhantomData<sealed::Token>,
}
impl<T, Ti: TierWitness> Tiered<T, Ti>
where
T: HasNsid<Tier = Ti>,
{
#[must_use]
pub fn wrap(record: T) -> Self {
Tiered {
inner: record,
_tier: PhantomData,
_private: PhantomData,
}
}
#[must_use]
pub fn inner(&self) -> &T {
&self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: core::fmt::Debug, Ti: TierWitness> core::fmt::Debug for Tiered<T, Ti> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Tiered")
.field("tier", &Ti::TIER)
.field("inner", &self.inner)
.finish()
}
}
#[non_exhaustive]
pub enum MixedTier<P, Q>
where
P: HasNsid<Tier = PublicTier>,
Q: HasNsid<Tier = PrivateTier>,
{
Public(Tiered<P, PublicTier>),
Private(Tiered<Q, PrivateTier>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tier_v1_variant_set_pinned() {
let t = Tier::Public;
match t {
Tier::Public | Tier::Private => {}
_ => panic!("unexpected non-v1 Tier variant"),
}
}
#[test]
fn visibility_allows_read_only_visible() {
assert!(Visibility::Visible.allows_read());
assert!(!Visibility::Hidden.allows_read());
assert!(!Visibility::Forbidden.allows_read());
}
#[test]
fn tier_witnesses_carry_the_right_constants() {
assert_eq!(PublicTier::TIER, Tier::Public);
assert_eq!(PrivateTier::TIER, Tier::Private);
}
#[test]
fn from_nsid_resolves_v1_lexicons() {
let cases: &[(&str, Tier)] = &[
("tools.kryphocron.feed.postPublic", Tier::Public),
("tools.kryphocron.feed.postPrivate", Tier::Private),
("tools.kryphocron.feed.like", Tier::Public),
("tools.kryphocron.feed.repost", Tier::Public),
("tools.kryphocron.feed.threadgate", Tier::Public),
("tools.kryphocron.graph.block", Tier::Private),
("tools.kryphocron.graph.mute", Tier::Private),
("tools.kryphocron.policy.audience", Tier::Private),
];
for (nsid_str, expected_tier) in cases {
let nsid = Nsid::new(nsid_str).unwrap();
let resolved = Tier::from_nsid(&nsid).unwrap_or_else(|_| {
panic!("registered NSID `{nsid_str}` did not resolve");
});
assert_eq!(resolved, *expected_tier, "{nsid_str}");
}
}
#[test]
fn from_nsid_unknown_returns_not_registered() {
let nsid = Nsid::new("com.example.unknown.lexicon").unwrap();
assert!(matches!(
Tier::from_nsid(&nsid),
Err(UnknownNsid::NotRegistered(_))
));
}
mod visible_to_fixture {
use crate::audit::*;
use crate::authority::moderation::InspectionNotificationQueueImpl;
use crate::oracle::*;
use std::time::{Duration, SystemTime};
pub(super) struct NoSink;
impl UserAuditSink for NoSink {
fn record(&self, _: UserAuditEvent) -> Result<(), AuditError> {
Ok(())
}
}
impl ChannelAuditSink for NoSink {
fn record(&self, _: ChannelAuditEvent) -> Result<(), AuditError> {
Ok(())
}
}
impl SubstrateAuditSink for NoSink {
fn record(&self, _: SubstrateAuditEvent) -> Result<(), AuditError> {
Ok(())
}
}
impl ModerationAuditSink for NoSink {
fn record(&self, _: ModerationAuditEvent) -> Result<(), AuditError> {
Ok(())
}
}
impl FallbackAuditSink for NoSink {
fn record_panic(
&self,
_: SinkKind,
_: crate::identity::TraceId,
_: crate::authority::CapabilityKind,
_: SystemTime,
) {
}
fn record_composite_failure(
&self,
_: crate::identity::TraceId,
_: CompositeOpId,
_: &[SinkKind],
_: &[SinkKind],
_: SystemTime,
) {
}
fn record_event(&self, _: FallbackAuditEvent) {}
}
impl InspectionNotificationQueueImpl for NoSink {
fn enqueue(
&self,
_: &crate::proto::Did,
_: crate::authority::InspectionNotification,
) {
}
}
pub(super) struct NoOracle;
impl BlockOracle for NoOracle {
fn block_state(&self, _: &crate::proto::Did, _: &crate::proto::Did) -> BlockState {
BlockState::None
}
fn last_synced_at(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
}
fn data_freshness_bound(&self) -> Duration {
Duration::from_secs(60)
}
fn worst_case_latency_for(&self, _: BlockOracleQuery) -> Duration {
Duration::ZERO
}
}
impl AudienceOracle for NoOracle {
fn audience_state(
&self,
_: &crate::proto::Did,
_: &crate::authority::ResourceId,
) -> AudienceState {
AudienceState::NoAudienceConfigured
}
fn last_synced_at(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
}
fn data_freshness_bound(&self) -> Duration {
Duration::from_secs(60)
}
fn worst_case_latency_for(&self, _: AudienceOracleQuery) -> Duration {
Duration::ZERO
}
}
impl MuteOracle for NoOracle {
fn mute_state(&self, _: &crate::proto::Did, _: &crate::proto::Did) -> MuteState {
MuteState::None
}
fn last_synced_at(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
}
fn data_freshness_bound(&self) -> Duration {
Duration::from_secs(60)
}
fn worst_case_latency_for(&self, _: MuteOracleQuery) -> Duration {
Duration::ZERO
}
}
}
use crate::ingress::{
AttributionChain, AuditSinks, AuthContext, OracleSet, Requester,
};
use crate::proto::Did;
fn build_ctx<'a>(
sink: &'a visible_to_fixture::NoSink,
oracle: &'a visible_to_fixture::NoOracle,
correlation_key: &'a crate::identity::CorrelationKey,
requester: Requester,
) -> AuthContext<'a> {
AuthContext::new_internal(
requester,
crate::identity::TraceId::from_bytes([0u8; 16]),
AuditSinks {
user: sink,
channel: sink,
substrate: sink,
moderation: sink,
fallback: sink,
inspection_queue: sink,
correlation_key,
},
OracleSet {
block: oracle,
audience: oracle,
mute: oracle,
},
AttributionChain::empty(),
crate::authority::capability::CapabilitySet::empty(),
)
}
fn sample_did() -> Did {
Did::new("did:plc:phase7e").unwrap()
}
#[test]
fn public_visible_to_anonymous() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Anonymous);
assert_eq!(visible_to(Tier::Public, &ctx), Visibility::Visible);
}
#[test]
fn public_visible_to_did() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Did(sample_did()));
assert_eq!(visible_to(Tier::Public, &ctx), Visibility::Visible);
}
#[test]
fn public_visible_to_service() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let svc = crate::identity::ServiceIdentity::new_internal(
sample_did(),
crate::identity::KeyId::from_bytes([0u8; 32]),
crate::identity::PublicKey {
algorithm: crate::identity::SignatureAlgorithm::Ed25519,
bytes: [0u8; 32],
},
None,
);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Service(svc));
assert_eq!(visible_to(Tier::Public, &ctx), Visibility::Visible);
}
#[test]
fn private_hidden_from_anonymous() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Anonymous);
assert_eq!(visible_to(Tier::Private, &ctx), Visibility::Hidden);
}
#[test]
fn private_hidden_from_did_conservative() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Did(sample_did()));
assert_eq!(visible_to(Tier::Private, &ctx), Visibility::Hidden);
}
#[test]
fn private_visible_to_service() {
let sink = visible_to_fixture::NoSink;
let oracle = visible_to_fixture::NoOracle;
let ck = crate::identity::CorrelationKey::from_bytes([0u8; 32]);
let svc = crate::identity::ServiceIdentity::new_internal(
sample_did(),
crate::identity::KeyId::from_bytes([0u8; 32]),
crate::identity::PublicKey {
algorithm: crate::identity::SignatureAlgorithm::Ed25519,
bytes: [0u8; 32],
},
None,
);
let ctx = build_ctx(&sink, &oracle, &ck, Requester::Service(svc));
assert_eq!(visible_to(Tier::Private, &ctx), Visibility::Visible);
}
}