use async_nats::jetstream::kv::Config;
use futures_util::StreamExt;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CapabilityEntry {
pub clearance: String,
pub epistemic_status: String,
pub capability_metadata: serde_json::Value,
pub content_hash: String,
}
pub fn urn_to_key(urn: &str) -> String {
urn.replace(':', ".")
}
pub fn key_to_urn(key: &str) -> String {
let mut urn = String::new();
let mut dots_count = 0;
for c in key.chars() {
if c == '.' && dots_count < 5 {
urn.push(':');
dots_count += 1;
} else {
urn.push(c);
}
}
urn
}
pub fn validate_urn(urn: &str) -> Result<(), String> {
static RE: OnceLock<Regex> = OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(r"^urn:[a-z0-9_]+:actionspace:(oracle|solver|effector|substrate|sensory|node):[a-z0-9_]+:v[0-9]+$").unwrap()
});
if !re.is_match(urn) {
return Err(format!(
"URN Topology Breach: URN {} does not conform to the CoReason manifest modern actionspace taxonomy. Rejecting capability.",
urn
));
}
Ok(())
}
pub async fn connect_and_get_kv(
nats_url: &str,
bucket: &str,
) -> Result<async_nats::jetstream::kv::Store, String> {
let client = async_nats::connect(nats_url)
.await
.map_err(|e| format!("Failed to connect to NATS: {}", e))?;
let js = async_nats::jetstream::new(client);
match js.get_key_value(bucket).await {
Ok(kv) => Ok(kv),
Err(_) => {
js.create_key_value(Config {
bucket: bucket.to_string(),
description: "CoReason URN Capability Registry".to_string(),
history: 5,
..Default::default()
})
.await
.map_err(|e| format!("Failed to create KV bucket: {}", e))
}
}
}
pub async fn register_capability(
nats_url: &str,
bucket: &str,
urn: &str,
clearance: &str,
epistemic_status: &str,
metadata: serde_json::Value,
content_hash: &str,
) -> Result<(), String> {
validate_urn(urn)?;
let kv = connect_and_get_kv(nats_url, bucket).await?;
let key = urn_to_key(urn);
let entry = CapabilityEntry {
clearance: clearance.to_string(),
epistemic_status: epistemic_status.to_string(),
capability_metadata: metadata,
content_hash: content_hash.to_string(),
};
let val_bytes = serde_json::to_vec(&entry)
.map_err(|e| format!("Failed to serialize capability entry: {}", e))?;
kv.put(key, val_bytes.into())
.await
.map_err(|e| format!("Failed to put capability: {}", e))?;
Ok(())
}
pub async fn resolve_urn(
nats_url: &str,
bucket: &str,
urn: &str,
) -> Result<CapabilityEntry, String> {
let kv = connect_and_get_kv(nats_url, bucket).await?;
let key = urn_to_key(urn);
let entry_val = kv
.get(key)
.await
.map_err(|e| format!("Failed to get capability: {}", e))?
.ok_or_else(|| format!("Geometrical topology fault: unregistered URN {}", urn))?;
let entry: CapabilityEntry = serde_json::from_slice(&entry_val)
.map_err(|e| format!("Failed to deserialize capability: {}", e))?;
Ok(entry)
}
pub async fn list_all_capabilities(
nats_url: &str,
bucket: &str,
) -> Result<HashMap<String, CapabilityEntry>, String> {
let kv = connect_and_get_kv(nats_url, bucket).await?;
let mut keys = kv
.keys()
.await
.map_err(|e| format!("Failed to list KV keys: {}", e))?;
let mut result = HashMap::new();
while let Some(key_res) = keys.next().await {
if let Ok(key) = key_res {
if let Ok(Some(entry_bytes)) = kv.get(&key).await {
if let Ok(entry) = serde_json::from_slice(&entry_bytes) {
let urn = key_to_urn(&key);
result.insert(urn, entry);
}
}
}
}
Ok(result)
}