use serde::{Deserialize, Serialize};
pub type CycleId = u64;
pub type RoundId = u64;
pub type TileId = u8;
pub type VertexId = u64;
pub type EdgeId = u64;
pub type ActionId = String;
pub type SequenceId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GateDecision {
Safe,
Cautious,
Unsafe,
}
impl GateDecision {
#[inline]
pub fn permits_action(&self) -> bool {
matches!(self, GateDecision::Safe)
}
#[inline]
pub fn requires_escalation(&self) -> bool {
matches!(self, GateDecision::Cautious | GateDecision::Unsafe)
}
#[inline]
pub fn requires_quarantine(&self) -> bool {
matches!(self, GateDecision::Unsafe)
}
#[cfg(feature = "tilezero")]
pub fn to_tilezero(&self) -> cognitum_gate_tilezero::GateDecision {
match self {
GateDecision::Safe => cognitum_gate_tilezero::GateDecision::Permit,
GateDecision::Cautious => cognitum_gate_tilezero::GateDecision::Defer,
GateDecision::Unsafe => cognitum_gate_tilezero::GateDecision::Deny,
}
}
}
impl std::fmt::Display for GateDecision {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GateDecision::Safe => write!(f, "SAFE"),
GateDecision::Cautious => write!(f, "CAUTIOUS"),
GateDecision::Unsafe => write!(f, "UNSAFE"),
}
}
}
impl Default for GateDecision {
fn default() -> Self {
GateDecision::Cautious }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Verdict {
Permit,
Defer,
Deny,
}
impl Verdict {
pub fn to_gate_decision(&self) -> GateDecision {
match self {
Verdict::Permit => GateDecision::Safe,
Verdict::Defer => GateDecision::Cautious,
Verdict::Deny => GateDecision::Unsafe,
}
}
}
impl std::fmt::Display for Verdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Verdict::Permit => write!(f, "permit"),
Verdict::Defer => write!(f, "defer"),
Verdict::Deny => write!(f, "deny"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RegionMask {
bits: [u64; 4],
}
impl RegionMask {
#[inline]
pub const fn none() -> Self {
Self { bits: [0; 4] }
}
#[inline]
pub const fn all() -> Self {
Self { bits: [u64::MAX; 4] }
}
pub fn from_tiles(tiles: &[TileId]) -> Self {
let mut mask = Self::none();
for &tile in tiles {
mask.set(tile);
}
mask
}
#[inline]
pub const fn from_bits(bits: [u64; 4]) -> Self {
Self { bits }
}
#[inline]
pub const fn bits(&self) -> [u64; 4] {
self.bits
}
#[inline]
pub fn set(&mut self, tile: TileId) {
let word = (tile / 64) as usize;
let bit = tile % 64;
self.bits[word] |= 1u64 << bit;
}
#[inline]
pub fn clear(&mut self, tile: TileId) {
let word = (tile / 64) as usize;
let bit = tile % 64;
self.bits[word] &= !(1u64 << bit);
}
#[inline]
pub fn is_set(&self, tile: TileId) -> bool {
let word = (tile / 64) as usize;
let bit = tile % 64;
(self.bits[word] & (1u64 << bit)) != 0
}
#[inline]
pub fn count(&self) -> u32 {
self.bits.iter().map(|w| w.count_ones()).sum()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&w| w == 0)
}
#[inline]
pub fn is_full(&self) -> bool {
self.bits.iter().all(|&w| w == u64::MAX)
}
#[inline]
pub fn union(&self, other: &RegionMask) -> RegionMask {
RegionMask {
bits: [
self.bits[0] | other.bits[0],
self.bits[1] | other.bits[1],
self.bits[2] | other.bits[2],
self.bits[3] | other.bits[3],
],
}
}
#[inline]
pub fn intersection(&self, other: &RegionMask) -> RegionMask {
RegionMask {
bits: [
self.bits[0] & other.bits[0],
self.bits[1] & other.bits[1],
self.bits[2] & other.bits[2],
self.bits[3] & other.bits[3],
],
}
}
#[inline]
pub fn intersects(&self, other: &RegionMask) -> bool {
!self.intersection(other).is_empty()
}
#[inline]
pub fn complement(&self) -> RegionMask {
RegionMask {
bits: [!self.bits[0], !self.bits[1], !self.bits[2], !self.bits[3]],
}
}
pub fn iter_set(&self) -> impl Iterator<Item = TileId> + '_ {
(0u8..=255).filter(|&t| self.is_set(t))
}
}
impl Default for RegionMask {
fn default() -> Self {
Self::none()
}
}
impl std::fmt::Display for RegionMask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RegionMask({} tiles)",
self.count()
)
}
}
impl std::ops::BitOr for RegionMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
self.union(&rhs)
}
}
impl std::ops::BitAnd for RegionMask {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
self.intersection(&rhs)
}
}
impl std::ops::Not for RegionMask {
type Output = Self;
fn not(self) -> Self::Output {
self.complement()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermitToken {
pub decision: GateDecision,
pub action_id: ActionId,
pub region_mask: RegionMask,
pub issued_at: u64,
pub expires_at: u64,
pub sequence: SequenceId,
#[serde(with = "hex_array")]
pub witness_hash: [u8; 32],
#[serde(with = "hex_array")]
pub signature: [u8; 64],
}
impl PermitToken {
pub fn is_valid(&self, now_ns: u64) -> bool {
now_ns >= self.issued_at && now_ns < self.expires_at
}
pub fn ttl_ns(&self) -> u64 {
self.expires_at.saturating_sub(self.issued_at)
}
pub fn covers_tile(&self, tile: TileId) -> bool {
self.region_mask.is_set(tile)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterResults {
pub structural: StructuralResult,
pub shift: ShiftResult,
pub evidence: EvidenceResult,
}
impl FilterResults {
pub fn verdict(&self) -> Verdict {
if self.structural.verdict == Verdict::Deny
|| self.shift.verdict == Verdict::Deny
|| self.evidence.verdict == Verdict::Deny
{
return Verdict::Deny;
}
if self.structural.verdict == Verdict::Defer
|| self.shift.verdict == Verdict::Defer
|| self.evidence.verdict == Verdict::Defer
{
return Verdict::Defer;
}
Verdict::Permit
}
pub fn confidence(&self) -> f64 {
(self.structural.confidence + self.shift.confidence + self.evidence.confidence) / 3.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StructuralResult {
pub verdict: Verdict,
pub confidence: f64,
pub cut_value: f64,
pub threshold: f64,
pub boundary_edges: Vec<EdgeId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShiftResult {
pub verdict: Verdict,
pub confidence: f64,
pub shift_pressure: f64,
pub threshold: f64,
pub affected_regions: RegionMask,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvidenceResult {
pub verdict: Verdict,
pub confidence: f64,
pub e_value: f64,
pub tau_deny: f64,
pub tau_permit: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateThresholds {
pub min_cut: f64,
pub max_shift: f64,
pub tau_deny: f64,
pub tau_permit: f64,
pub permit_ttl_ns: u64,
pub decision_budget_ns: u64,
}
impl Default for GateThresholds {
fn default() -> Self {
Self {
min_cut: 5.0,
max_shift: 0.5,
tau_deny: 0.01,
tau_permit: 100.0,
permit_ttl_ns: 60_000_000_000, decision_budget_ns: 4_000, }
}
}
const MIN_PERMIT_TTL_NS: u64 = 1_000_000;
const MAX_PERMIT_TTL_NS: u64 = 3_600_000_000_000;
const MIN_DECISION_BUDGET_NS: u64 = 100;
const MAX_DECISION_BUDGET_NS: u64 = 1_000_000_000;
impl GateThresholds {
pub fn validate(&self) -> crate::error::Result<()> {
if self.min_cut <= 0.0 {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "min_cut".to_string(),
value: self.min_cut,
constraint: "> 0".to_string(),
});
}
if self.max_shift <= 0.0 || self.max_shift > 1.0 {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "max_shift".to_string(),
value: self.max_shift,
constraint: "in (0, 1]".to_string(),
});
}
if self.tau_deny <= 0.0 {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "tau_deny".to_string(),
value: self.tau_deny,
constraint: "> 0".to_string(),
});
}
if self.tau_permit <= self.tau_deny {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "tau_permit".to_string(),
value: self.tau_permit,
constraint: format!("> tau_deny ({})", self.tau_deny),
});
}
if self.permit_ttl_ns < MIN_PERMIT_TTL_NS || self.permit_ttl_ns > MAX_PERMIT_TTL_NS {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "permit_ttl_ns".to_string(),
value: self.permit_ttl_ns as f64,
constraint: format!("in [{}, {}]", MIN_PERMIT_TTL_NS, MAX_PERMIT_TTL_NS),
});
}
if self.decision_budget_ns < MIN_DECISION_BUDGET_NS || self.decision_budget_ns > MAX_DECISION_BUDGET_NS {
return Err(crate::error::RuQuError::InvalidThreshold {
name: "decision_budget_ns".to_string(),
value: self.decision_budget_ns as f64,
constraint: format!("in [{}, {}]", MIN_DECISION_BUDGET_NS, MAX_DECISION_BUDGET_NS),
});
}
Ok(())
}
}
mod hex_array {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S, const N: usize>(bytes: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_string: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
serializer.serialize_str(&hex_string)
}
pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.len() % 2 != 0 {
return Err(serde::de::Error::custom(format!(
"hex string must have even length, got {}",
s.len()
)));
}
let bytes: Vec<u8> = (0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
.collect::<Result<Vec<_>, _>>()
.map_err(serde::de::Error::custom)?;
bytes.try_into().map_err(|_| {
serde::de::Error::custom(format!("expected {} bytes, got {}", N, s.len() / 2))
})
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StructuralSignal {
pub cut: f64,
pub velocity: f64,
pub curvature: f64,
pub baseline_mean: f64,
pub baseline_std: f64,
}
impl StructuralSignal {
#[inline]
pub fn is_degrading(&self) -> bool {
self.velocity < 0.0
}
#[inline]
pub fn is_below_threshold(&self, k: f64) -> bool {
self.cut < self.baseline_mean - k * self.baseline_std
}
#[inline]
pub fn z_score(&self) -> f64 {
if self.baseline_std == 0.0 {
return 0.0;
}
(self.cut - self.baseline_mean) / self.baseline_std
}
pub fn time_to_threshold(&self, threshold: f64) -> Option<f64> {
if self.velocity >= 0.0 || self.cut <= threshold {
return None;
}
Some((self.cut - threshold) / (-self.velocity))
}
}
impl std::fmt::Display for StructuralSignal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let trend = if self.velocity > 0.1 {
"↑"
} else if self.velocity < -0.1 {
"↓"
} else {
"→"
};
write!(
f,
"λ={:.2}{} (v={:+.2}, z={:+.1}σ)",
self.cut,
trend,
self.velocity,
self.z_score()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gate_decision() {
assert!(GateDecision::Safe.permits_action());
assert!(!GateDecision::Cautious.permits_action());
assert!(!GateDecision::Unsafe.permits_action());
assert!(!GateDecision::Safe.requires_escalation());
assert!(GateDecision::Cautious.requires_escalation());
assert!(GateDecision::Unsafe.requires_escalation());
assert!(!GateDecision::Safe.requires_quarantine());
assert!(!GateDecision::Cautious.requires_quarantine());
assert!(GateDecision::Unsafe.requires_quarantine());
}
#[test]
fn test_region_mask_basic() {
let mut mask = RegionMask::none();
assert!(mask.is_empty());
assert_eq!(mask.count(), 0);
mask.set(0);
mask.set(127);
mask.set(255);
assert_eq!(mask.count(), 3);
assert!(mask.is_set(0));
assert!(mask.is_set(127));
assert!(mask.is_set(255));
assert!(!mask.is_set(1));
mask.clear(127);
assert_eq!(mask.count(), 2);
assert!(!mask.is_set(127));
}
#[test]
fn test_region_mask_from_tiles() {
let mask = RegionMask::from_tiles(&[1, 5, 10, 200]);
assert_eq!(mask.count(), 4);
assert!(mask.is_set(1));
assert!(mask.is_set(5));
assert!(mask.is_set(10));
assert!(mask.is_set(200));
assert!(!mask.is_set(0));
}
#[test]
fn test_region_mask_operations() {
let a = RegionMask::from_tiles(&[1, 2, 3]);
let b = RegionMask::from_tiles(&[2, 3, 4]);
let union = a | b;
assert_eq!(union.count(), 4);
assert!(union.is_set(1));
assert!(union.is_set(4));
let intersection = a & b;
assert_eq!(intersection.count(), 2);
assert!(intersection.is_set(2));
assert!(intersection.is_set(3));
assert!(!intersection.is_set(1));
assert!(a.intersects(&b));
}
#[test]
fn test_region_mask_all_none() {
let all = RegionMask::all();
assert!(all.is_full());
assert_eq!(all.count(), 256);
assert!(all.is_set(0));
assert!(all.is_set(255));
let none = RegionMask::none();
assert!(none.is_empty());
assert_eq!(none.count(), 0);
let complement = !none;
assert!(complement.is_full());
}
#[test]
fn test_gate_thresholds_default() {
let thresholds = GateThresholds::default();
assert!(thresholds.validate().is_ok());
}
#[test]
fn test_gate_thresholds_invalid() {
let mut thresholds = GateThresholds::default();
thresholds.min_cut = -1.0;
assert!(thresholds.validate().is_err());
let mut thresholds = GateThresholds::default();
thresholds.tau_permit = 0.001; assert!(thresholds.validate().is_err());
}
#[test]
fn test_filter_results_verdict() {
let results = FilterResults {
structural: StructuralResult {
verdict: Verdict::Permit,
confidence: 1.0,
cut_value: 10.0,
threshold: 5.0,
boundary_edges: vec![],
},
shift: ShiftResult {
verdict: Verdict::Permit,
confidence: 0.9,
shift_pressure: 0.1,
threshold: 0.5,
affected_regions: RegionMask::none(),
},
evidence: EvidenceResult {
verdict: Verdict::Permit,
confidence: 0.95,
e_value: 150.0,
tau_deny: 0.01,
tau_permit: 100.0,
},
};
assert_eq!(results.verdict(), Verdict::Permit);
assert!(results.confidence() > 0.9);
}
#[test]
fn test_permit_token_validity() {
let token = PermitToken {
decision: GateDecision::Safe,
action_id: "test-action".to_string(),
region_mask: RegionMask::all(),
issued_at: 1000,
expires_at: 2000,
sequence: 0,
witness_hash: [0u8; 32],
signature: [0u8; 64],
};
assert!(token.is_valid(1500));
assert!(!token.is_valid(500));
assert!(!token.is_valid(2500));
assert_eq!(token.ttl_ns(), 1000);
}
}