use crate::error::Result;
use crate::model::GrainRecord;
use serde_json::{Map, Value};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Capabilities {
pub forks: bool,
pub telemetry: bool,
pub embeddings: bool,
pub plans: bool,
pub code: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ReadOpts {
pub live_only: bool,
pub since_ms: Option<i64>,
}
impl Default for ReadOpts {
fn default() -> Self {
ReadOpts {
live_only: true,
since_ms: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GrainSpec {
pub grain_type: String,
pub namespace: String,
pub fields: Map<String, Value>,
}
impl GrainSpec {
pub fn new(grain_type: impl Into<String>, namespace: impl Into<String>) -> Self {
GrainSpec {
grain_type: grain_type.into(),
namespace: namespace.into(),
fields: Map::new(),
}
}
pub fn with_field(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.fields.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HeadGroup {
pub entity: String,
pub heads: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TelemetryView {
pub access: Vec<GrainAccess>,
pub queries: Vec<QueryUsage>,
pub budget: BudgetUsage,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GrainAccess {
pub hash: String,
pub recall_count: i64,
pub last_ms: i64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryUsage {
pub sample: String,
pub run_count: i64,
pub empty_count: i64,
pub sum_results: i64,
pub last_ms: i64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BudgetUsage {
pub sample_count: i64,
pub overflow_count: i64,
}
pub trait SubstrateRead {
fn capabilities(&self) -> Capabilities;
fn grains_of_type(
&self,
grain_type: &str,
namespace: Option<&str>,
opts: ReadOpts,
) -> Result<Vec<GrainRecord>>;
fn grain(&self, hash: &str) -> Result<Option<GrainRecord>>;
fn heads(&self, _namespace: Option<&str>) -> Result<Vec<HeadGroup>> {
Err(crate::error::Error::CapabilityMissing("forks".into()))
}
fn telemetry(&self, _namespace: Option<&str>) -> Result<Option<TelemetryView>> {
Ok(None)
}
fn validate_plan(&self, _workflow: &Value) -> Result<()> {
Err(crate::error::Error::CapabilityMissing("plans".into()))
}
fn tool_evalset(&self, _tool: &str) -> Result<Option<String>> {
Ok(None)
}
}
pub trait OmsSubstrate: SubstrateRead {
fn put_grain(&mut self, spec: &GrainSpec) -> Result<String>;
fn supersede(
&mut self,
target_hash: &str,
spec: &GrainSpec,
justification: &str,
) -> Result<String>;
fn retract(&mut self, hash: &str, reason: &str) -> Result<()> {
Err(crate::error::Error::Substrate(format!(
"retract not supported by this substrate ({hash}: {reason})"
)))
}
fn put_blob(&mut self, bytes: &[u8]) -> Result<String> {
let _ = bytes;
Err(crate::error::Error::Substrate(
"put_blob not supported by this substrate (code-carrying loops \
need an opted-in blob seam)"
.into(),
))
}
fn get_blob(&mut self, address: &str) -> Result<Vec<u8>> {
Err(crate::error::Error::Substrate(format!(
"get_blob not supported by this substrate ({address})"
)))
}
fn execute_cal(&mut self, cal: &str) -> Result<Vec<Value>>;
fn definition_inverse(&self, _statement: &str) -> Result<Option<String>> {
Ok(None)
}
fn validate_cal(&self, cal: &str) -> Result<()>;
fn load_state(&self) -> Result<Value>;
fn store_state(&mut self, state: &Value) -> Result<()>;
}