use crate::error::RuntimeError;
use crate::ids::Scope;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeCandidateBackendHint {
SemanticMemoryDefault,
ProveKvPoolCandidate,
}
impl Default for RuntimeCandidateBackendHint {
fn default() -> Self {
Self::SemanticMemoryDefault
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
pub default_scope: Scope,
#[serde(default)]
pub query: QueryConfig,
#[serde(default)]
pub entity: EntityConfig,
#[serde(default)]
pub projection: ProjectionConfig,
#[serde(default)]
pub strict_temporal: bool,
#[serde(default)]
pub strict_scope: bool,
}
impl RuntimeConfig {
pub fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
if self.default_scope.namespace.is_empty() {
return Err(RuntimeError::InvalidConfig {
field: "default_scope.namespace",
reason: "namespace must not be empty".into(),
});
}
self.query.normalize_and_validate()?;
self.projection.normalize_and_validate()?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryConfig {
pub max_results_per_leg: usize,
pub max_route_legs: usize,
pub default_limit: usize,
#[serde(default)]
pub candidate_backend_hint: RuntimeCandidateBackendHint,
}
impl Default for QueryConfig {
fn default() -> Self {
Self {
max_results_per_leg: 20,
max_route_legs: 4,
default_limit: 10,
candidate_backend_hint: RuntimeCandidateBackendHint::default(),
}
}
}
impl QueryConfig {
fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
if self.max_results_per_leg == 0 {
self.max_results_per_leg = 20;
}
if self.max_route_legs == 0 {
self.max_route_legs = 4;
}
if self.default_limit == 0 {
self.default_limit = 10;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityConfig {
pub max_aliases: usize,
pub max_entities: usize,
}
impl Default for EntityConfig {
fn default() -> Self {
Self {
max_aliases: 16,
max_entities: 10_000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionConfig {
pub staleness_threshold_secs: u64,
pub import_staleness_threshold_secs: u64,
#[serde(default)]
pub persist: bool,
}
impl Default for ProjectionConfig {
fn default() -> Self {
Self {
staleness_threshold_secs: 3600,
import_staleness_threshold_secs: 3600,
persist: false,
}
}
}
impl ProjectionConfig {
fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
if self.persist {
return Err(RuntimeError::InvalidConfig {
field: "projection.persist",
reason: "projection persistence is not implemented in this crate today; \
projections are derived, non-authoritative, and rebuildable \
from upstream semantic-memory state; \
set persist=false or omit the field"
.into(),
});
}
if self.staleness_threshold_secs == 0 {
self.staleness_threshold_secs = 3600;
}
Ok(())
}
}