coreason-urn-authority 0.45.1

Epistemic Ledger & OCI Trust Anchor for CoReason URNs.
Documentation
// Copyright (c) 2026 CoReason, Inc.
// All rights reserved.

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);
    }

    // Check various common relative locations
    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));
    }

    // Limit files larger than 10MB
    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()),
    };

    // Lock file logic - atomic creation loop
    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());
    }

    // Read and parse current ledger
    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())
    };

    // Insert new entry into capabilities array
    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);

    // Save back to YAML
    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);

    // Git operations
    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
}