knowledge_runtime/config.rs
1use crate::error::RuntimeError;
2use crate::ids::Scope;
3use serde::{Deserialize, Serialize};
4
5/// Runtime-level hint for semantic-memory candidate backend selection.
6///
7/// This is intentionally a hint/trace value only. Knowledge Runtime does not
8/// construct or depend on proveKV/poly-kv backends directly.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "snake_case")]
11pub enum RuntimeCandidateBackendHint {
12 SemanticMemoryDefault,
13 ProveKvPoolCandidate,
14}
15
16impl Default for RuntimeCandidateBackendHint {
17 fn default() -> Self {
18 Self::SemanticMemoryDefault
19 }
20}
21
22/// Top-level runtime configuration.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct RuntimeConfig {
25 /// Default scope for queries that don't supply one.
26 pub default_scope: Scope,
27
28 /// Query pipeline settings.
29 #[serde(default)]
30 pub query: QueryConfig,
31
32 /// Entity registry settings.
33 #[serde(default)]
34 pub entity: EntityConfig,
35
36 /// Projection lifecycle settings.
37 #[serde(default)]
38 pub projection: ProjectionConfig,
39
40 /// When true, temporal queries that cannot be executed on a supported
41 /// projection-backed route return `RuntimeError::TemporalNotSupported`
42 /// instead of downgrading to hybrid retrieval with a warning.
43 ///
44 /// Default: `false` (downgrade with warning).
45 #[serde(default)]
46 pub strict_temporal: bool,
47
48 /// When true, queries whose scope includes dimensions beyond namespace
49 /// (domain, workspace_id, repo_id) return `RuntimeError::ScopeNotFullyEnforced`
50 /// instead of proceeding with namespace-only hybrid fallback.
51 ///
52 /// Default: `false` (warn but proceed).
53 #[serde(default)]
54 pub strict_scope: bool,
55}
56
57impl RuntimeConfig {
58 /// Validate and normalize configuration. Returns error on invalid values.
59 pub fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
60 if self.default_scope.namespace.is_empty() {
61 return Err(RuntimeError::InvalidConfig {
62 field: "default_scope.namespace",
63 reason: "namespace must not be empty".into(),
64 });
65 }
66 self.query.normalize_and_validate()?;
67 self.projection.normalize_and_validate()?;
68 Ok(())
69 }
70}
71
72/// Query pipeline tuning.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct QueryConfig {
75 /// Maximum number of search results per route leg.
76 pub max_results_per_leg: usize,
77 /// Maximum number of route legs.
78 pub max_route_legs: usize,
79 /// Default result limit when caller does not specify.
80 pub default_limit: usize,
81 /// Semantic-memory candidate backend hint to disclose in runtime traces.
82 ///
83 /// The owning semantic-memory store remains authoritative for actual
84 /// backend configuration and exact rerank enforcement.
85 #[serde(default)]
86 pub candidate_backend_hint: RuntimeCandidateBackendHint,
87}
88
89impl Default for QueryConfig {
90 fn default() -> Self {
91 Self {
92 max_results_per_leg: 20,
93 max_route_legs: 4,
94 default_limit: 10,
95 candidate_backend_hint: RuntimeCandidateBackendHint::default(),
96 }
97 }
98}
99
100impl QueryConfig {
101 fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
102 if self.max_results_per_leg == 0 {
103 self.max_results_per_leg = 20;
104 }
105 if self.max_route_legs == 0 {
106 self.max_route_legs = 4;
107 }
108 if self.default_limit == 0 {
109 self.default_limit = 10;
110 }
111 Ok(())
112 }
113}
114
115/// Entity registry settings.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct EntityConfig {
118 /// Maximum number of aliases per entity.
119 pub max_aliases: usize,
120 /// Maximum number of entities in the registry (across all scopes).
121 pub max_entities: usize,
122}
123
124impl Default for EntityConfig {
125 fn default() -> Self {
126 Self {
127 max_aliases: 16,
128 max_entities: 10_000,
129 }
130 }
131}
132
133/// Projection lifecycle settings.
134///
135/// **Note**: Projection persistence is not implemented in this crate today.
136/// The `persist` field is retained solely for serde backward compatibility
137/// with existing config files; setting it to `true` is rejected at validation
138/// time because runtime projections remain derived and rebuildable.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ProjectionConfig {
141 /// Staleness threshold in seconds. Projections older than this are
142 /// candidates for rebuild.
143 pub staleness_threshold_secs: u64,
144 /// Import staleness threshold in seconds. If the last successful import
145 /// for a namespace is older than this, the runtime emits a
146 /// `ProjectionImportStale` warning during query execution.
147 /// Set to 0 to disable import staleness checking.
148 pub import_staleness_threshold_secs: u64,
149 /// Whether to persist projections to SQLite.
150 ///
151 /// Not implemented in this crate today. The runtime's projections
152 /// are derived, non-authoritative, and rebuildable from upstream
153 /// `semantic-memory` state, so enabling durable persistence here is
154 /// outside the current contract.
155 ///
156 /// This field is retained only for serde backward compatibility with
157 /// config files that already include it. Setting `persist: true` will
158 /// cause config validation to return `InvalidConfig`.
159 #[serde(default)]
160 pub persist: bool,
161}
162
163impl Default for ProjectionConfig {
164 fn default() -> Self {
165 Self {
166 staleness_threshold_secs: 3600,
167 import_staleness_threshold_secs: 3600,
168 persist: false,
169 }
170 }
171}
172
173impl ProjectionConfig {
174 fn normalize_and_validate(&mut self) -> Result<(), RuntimeError> {
175 if self.persist {
176 return Err(RuntimeError::InvalidConfig {
177 field: "projection.persist",
178 reason: "projection persistence is not implemented in this crate today; \
179 projections are derived, non-authoritative, and rebuildable \
180 from upstream semantic-memory state; \
181 set persist=false or omit the field"
182 .into(),
183 });
184 }
185 if self.staleness_threshold_secs == 0 {
186 self.staleness_threshold_secs = 3600;
187 }
188 Ok(())
189 }
190}