use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MCPRegistryRef {
pub namespace: String,
pub registry_url: String,
pub verified: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LedgerEntry {
pub urn: String,
pub oci_uri: String,
pub authorized_signer_did: String,
#[serde(default = "default_tenant_cid")]
pub tenant_cid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcp_registry: Option<MCPRegistryRef>,
#[serde(default = "default_license_tier")]
pub license_tier: String,
#[serde(default = "default_minimum_tier")]
pub minimum_tier: String,
#[serde(default)]
pub cost_per_invocation: f64,
#[serde(default)]
pub dependencies: Vec<String>,
#[serde(default)]
pub royalty_shares: HashMap<String, u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_splitter_address: Option<String>,
}
fn default_tenant_cid() -> String {
"urn:tenant:coreason:global:authority".to_string()
}
fn default_license_tier() -> String {
"prosperity-3.0".to_string()
}
fn default_minimum_tier() -> String {
"free".to_string()
}
#[derive(Serialize, Deserialize, Debug)]
struct Ledger {
capabilities: Option<Vec<serde_yaml::Value>>,
}
pub fn default_ledger_path() -> PathBuf {
if let Ok(env_path) = std::env::var("COREASON_LEDGER_PATH") {
return PathBuf::from(env_path);
}
let paths = [
"src/coreason_urn_authority/ledger/index.yaml",
"coreason_urn_authority/ledger/index.yaml",
"ledger/index.yaml",
"index.yaml",
];
for p in &paths {
if Path::new(p).exists() {
return PathBuf::from(p);
}
}
PathBuf::from("src/coreason_urn_authority/ledger/index.yaml")
}
pub fn load_ledger(ledger_path: Option<PathBuf>) -> Result<Vec<LedgerEntry>, String> {
let path = ledger_path.unwrap_or_else(default_ledger_path);
if !path.exists() {
return Err(format!("OCI Ledger not found at {:?}", path));
}
let metadata = fs::metadata(&path).map_err(|e| e.to_string())?;
if metadata.len() > 10 * 1024 * 1024 {
return Err("Epistemic Boundary Violation: Ledger size exceeds 10MB budget.".to_string());
}
let content = fs::read_to_string(&path).map_err(|e| e.to_string())?;
let ledger: Ledger =
serde_yaml::from_str(&content).map_err(|e| format!("Malformed ledger format: {}", e))?;
let mut entries = Vec::new();
if let Some(caps) = ledger.capabilities {
for cap_val in caps {
let entry: LedgerEntry = serde_yaml::from_value(cap_val)
.map_err(|e| format!("Malformed ledger entry: {}", e))?;
entries.push(entry);
}
}
Ok(entries)
}
#[allow(clippy::too_many_arguments)]
pub fn promote_capability(
urn: &str,
oci_uri: &str,
did: &str,
tenant_cid: &str,
mcp_namespace: Option<&str>,
dependencies: Vec<String>,
royalty_shares: HashMap<String, u32>,
payment_splitter_address: Option<&str>,
ledger_path: Option<PathBuf>,
skip_git: bool,
) -> Result<(), String> {
let path = ledger_path.unwrap_or_else(default_ledger_path);
println!("Promoting {} to ledger at {:?}", urn, path);
let mcp_registry = mcp_namespace.map(|ns| MCPRegistryRef {
namespace: ns.to_string(),
registry_url: "https://registry.modelcontextprotocol.io".to_string(),
verified: true,
});
let new_entry = LedgerEntry {
urn: urn.to_string(),
oci_uri: oci_uri.to_string(),
authorized_signer_did: did.to_string(),
tenant_cid: tenant_cid.to_string(),
mcp_registry,
license_tier: "prosperity-3.0".to_string(),
minimum_tier: "free".to_string(),
cost_per_invocation: 0.0,
dependencies,
royalty_shares,
payment_splitter_address: payment_splitter_address.map(|a| a.to_string()),
};
let lock_path = path.with_extension("yaml.lock");
let mut acquired = false;
for _ in 0..100 {
if fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&lock_path)
.is_ok()
{
acquired = true;
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
if !acquired {
return Err("Could not acquire file lock for ledger update".to_string());
}
let mut ledger_val: serde_yaml::Value = if path.exists() {
let content = fs::read_to_string(&path).map_err(|e| e.to_string())?;
serde_yaml::from_str(&content).map_err(|e| e.to_string())?
} else {
serde_yaml::Value::Mapping(serde_yaml::Mapping::new())
};
let mapping = ledger_val
.as_mapping_mut()
.ok_or_else(|| "Ledger top level is not a YAML mapping".to_string())?;
let caps_key = serde_yaml::Value::String("capabilities".to_string());
if !mapping.contains_key(&caps_key) {
mapping.insert(caps_key.clone(), serde_yaml::Value::Sequence(Vec::new()));
}
let caps_seq = mapping
.get_mut(&caps_key)
.and_then(|v| v.as_sequence_mut())
.ok_or_else(|| "capabilities field is not a sequence".to_string())?;
let new_entry_val = serde_yaml::to_value(&new_entry).map_err(|e| e.to_string())?;
caps_seq.push(new_entry_val);
let serialized = serde_yaml::to_string(&ledger_val).map_err(|e| e.to_string())?;
let header = "# CoReason URN Authority — OCI Artifact Ledger\n\
# Copyright (c) 2026 CoReason, Inc. Licensed under the Prosperity Public License 3.0.\n\
#\n\
# This file maps CoReason URNs to their authoritative OCI artifact URIs\n\
# and the Decentralized Identifier (DID) of the authorized signer.\n\
#\n\
# To register a new capability:\n\
# 1. Build and push your MCP container to an OCI registry.\n\
# 2. Sign it with Cosign using your Ed25519 key.\n\
# 3. Add an entry below with the URN, OCI URI, and your did:key.\n\
# 4. Submit a PR — the CI pipeline will validate the signature.\n\n";
let mut output_str = String::new();
output_str.push_str(header);
output_str.push_str(&serialized);
fs::write(&path, output_str).map_err(|e| e.to_string())?;
let _ = fs::remove_file(lock_path);
if !skip_git {
let safe_urn = urn.replace(':', "-");
let branch_name = format!("promote/{}", safe_urn);
let repo_dir = path.parent().unwrap_or_else(|| Path::new("."));
println!("Git checkout branch {}", branch_name);
std::process::Command::new("git")
.args(["checkout", "-b", &branch_name])
.current_dir(repo_dir)
.status()
.map_err(|e| format!("Failed to create git branch: {}", e))?;
println!("Git add");
std::process::Command::new("git")
.args(["add", path.file_name().unwrap().to_str().unwrap()])
.current_dir(repo_dir)
.status()
.map_err(|e| format!("Failed to add files to git: {}", e))?;
println!("Git commit");
std::process::Command::new("git")
.args(["commit", "-m", &format!("feat: promote {}", urn)])
.current_dir(repo_dir)
.status()
.map_err(|e| format!("Failed to commit: {}", e))?;
println!("\n--- Promotion Staged Successfully ---");
println!("Locally committed to branch: {}", branch_name);
println!("To complete the GitOps Release Protocol, run:");
println!(" git push origin {}", branch_name);
println!(" gh pr create --title \"feat: promote capability\" --body \"Human Oracle Review Requested.\"");
} else {
println!("\n--- Promotion Completed Successfully ---");
println!("Locally updated ledger file. Subprocess git operations skipped.");
}
Ok(())
}
#[allow(dead_code)]
pub fn check_ledger_access(
entries: &[LedgerEntry],
capability_urn: &str,
tenant_tier: &str,
tenant_license: &str,
) -> bool {
let entry = match entries.iter().find(|e| e.urn == capability_urn) {
Some(e) => e,
None => return false,
};
let tier_hierarchy = |tier: &str| match tier {
"free" => 0,
"standard" => 1,
"enterprise" => 2,
_ => 0,
};
let tenant_level = tier_hierarchy(tenant_tier);
let required_level = tier_hierarchy(&entry.minimum_tier);
if tenant_level < required_level {
return false;
}
if entry.license_tier == "commercial" && tenant_license != "commercial" {
return false;
}
true
}