Skip to main content

oxibrain_cli/cmd/
predicate.rs

1//! `oxibrain predicate list` — print the core/v1 predicate registry (DESIGN §5.5, P4).
2//!
3//! No store access needed: the registry is the in-process core ontology.
4
5use oxibrain::Brain;
6use oxibrain::BrainConfig;
7use oxibrain_core::registry::{CORE_V1_MAJOR, CORE_V1_MINOR, LiteralType, ObjectKind, core_v1};
8use oxibrain_store::project::Declaration;
9use std::path::Path;
10
11pub fn run() -> anyhow::Result<()> {
12    let preds = core_v1();
13    println!(
14        "core/v1 registry — {} predicates (major={}, minor={})",
15        preds.len(),
16        CORE_V1_MAJOR,
17        CORE_V1_MINOR
18    );
19    for p in preds {
20        println!("  {}", p.name);
21        println!(
22            "    object={} | cardinality={} | temporality={} | invalidation={} | symmetric={}",
23            format_object_kind(&p.object_kind),
24            p.cardinality.as_db(),
25            p.temporality.as_db(),
26            p.invalidation.as_db(),
27            p.symmetric,
28        );
29        if !p.subject_types.is_empty() {
30            println!("    subjects: {}", p.subject_types.join(", "));
31        }
32        if let Some(inv) = &p.inverse_of {
33            println!("    inverse_of: {inv}");
34        }
35        if !p.description.is_empty() {
36            println!("    {}", p.description);
37        }
38    }
39    Ok(())
40}
41
42pub async fn run_add(dir: &Path, json: &str, space: &str) -> anyhow::Result<()> {
43    let brain = Brain::open(BrainConfig::at(dir)).await?;
44    let space_id = brain.ensure_space(space).await?;
45    // Parse to extract name for the declaration.
46    let v: serde_json::Value =
47        serde_json::from_str(json).map_err(|e| anyhow::anyhow!("parse predicate def: {e}"))?;
48    let name = v
49        .get("name")
50        .and_then(|x| x.as_str())
51        .ok_or_else(|| anyhow::anyhow!("predicate def must have 'name' field"))?
52        .to_string();
53    let decl = Declaration::RegisterPredicate {
54        name,
55        def_json: json.to_string(),
56    };
57    let ep_id = brain.declare(&space_id, decl).await?;
58    println!("predicate registered as episode: {ep_id}");
59    Ok(())
60}
61
62fn format_object_kind(k: &ObjectKind) -> String {
63    match k {
64        ObjectKind::Entity(types) => format!("entity:{{{}}}", types.0.join("|")),
65        ObjectKind::Literal(LiteralType::Text) => "literal:text".into(),
66        ObjectKind::Literal(LiteralType::Date) => "literal:date".into(),
67        ObjectKind::Literal(LiteralType::DateTime) => "literal:datetime".into(),
68        ObjectKind::Literal(LiteralType::Number) => "literal:number".into(),
69        ObjectKind::Literal(LiteralType::Bool) => "literal:bool".into(),
70        ObjectKind::Literal(LiteralType::Quantity { unit }) => format!("literal:quantity[{unit}]"),
71        ObjectKind::Enum { variants: vals } => format!("enum:{{{}}}", vals.join("|")),
72    }
73}