Skip to main content

everruns_core/capabilities/
data_knowledge.rs

1//! Data Knowledge Capability
2//!
3//! Mounts a `/knowledge/` scaffold in the session filesystem for curated
4//! data context: table docs, business rules, and validated query patterns.
5//! Used by the Data Analyst harness to ground SQL generation in organizational
6//! knowledge (layers 1-3 of the six-layer context pattern).
7//!
8//! The scaffold is shaped as an Open Knowledge Format (OKF) bundle: a directory
9//! tree of markdown files with YAML frontmatter, navigated via `index.md`
10//! files. Concept documents added under `tables/`, `business/`, and `queries/`
11//! should carry frontmatter with at least a `type` field, so the same content
12//! is portable to any OKF consumer. See specs/okf-adoption.md.
13
14use super::{
15    Capability, CapabilityLocalization, CapabilityStatus, MountDirectoryBuilder, MountPoint,
16};
17
18pub const DATA_KNOWLEDGE_CAPABILITY_ID: &str = "data_knowledge";
19
20pub struct DataKnowledgeCapability;
21
22impl DataKnowledgeCapability {
23    /// Root OKF index. `index.md` is the one OKF file permitted to carry
24    /// frontmatter, used to declare the bundle's format version.
25    const ROOT_INDEX: &'static str = "\
26---
27okf_version: \"0.1\"
28type: Index
29---
30# Data Knowledge
31
32This directory is an Open Knowledge Format (OKF) bundle: curated context for
33data analysis, as markdown files with YAML frontmatter. See specs/okf-adoption.md.
34
35- [Tables & data sources](/tables/index.md)
36- [Business rules & metrics](/business/index.md)
37- [Validated query patterns](/queries/index.md)
38";
39
40    const TABLES_INDEX: &'static str = "\
41# Table Documentation
42
43Add one OKF concept document per table or data source. Each file needs YAML
44frontmatter with at least a `type` (e.g. `type: Table`), plus a `title` and an
45optional `resource` URI. The body should include:
46
47- Column names and types
48- Primary/foreign key relationships
49- Known gotchas (NULLs, enums, timezone handling)
50- Freshness: how often the data updates
51
52Example: `orders.md`, `customers.md`. The data analyst agent reads these files
53before writing SQL.
54";
55
56    const BUSINESS_INDEX: &'static str = "\
57# Business Rules & Metric Definitions
58
59Add OKF concept documents with organizational knowledge (frontmatter `type`,
60e.g. `type: Metric` or `type: Business Definition`):
61
62- Metric definitions (e.g. \"active user\" = logged in within 30 days)
63- Business rules (e.g. revenue = net of refunds, not gross)
64- Domain-specific terminology
65- KPI calculation methods
66
67The data analyst agent checks these before interpreting results.
68";
69
70    const QUERIES_INDEX: &'static str = "\
71# Validated Query Patterns
72
73Add OKF concept documents for known-good queries (frontmatter `type: Query`),
74with the SQL in a fenced code block. For each:
75
76- Explain what the query does
77- Mark the expected grain (one row per customer, per day, etc.)
78- Note any filters or assumptions
79
80The data analyst agent uses these as templates for similar questions.
81";
82}
83
84impl Capability for DataKnowledgeCapability {
85    fn id(&self) -> &str {
86        DATA_KNOWLEDGE_CAPABILITY_ID
87    }
88
89    fn name(&self) -> &str {
90        "Data Knowledge"
91    }
92
93    fn description(&self) -> &str {
94        "Mounts a `/knowledge/` Open Knowledge Format (OKF) bundle with directories for table docs, business rules, and validated SQL patterns. Provides curated ground truth for data analysis, portable to any OKF consumer."
95    }
96
97    fn localizations(&self) -> Vec<CapabilityLocalization> {
98        vec![CapabilityLocalization::text(
99            "uk",
100            "Знання про дані",
101            "Монтує каркас `/knowledge/` у форматі Open Knowledge Format (OKF) з каталогами для документації таблиць, бізнес-правил і перевірених SQL-шаблонів. Надає кураторське достовірне джерело для аналізу даних.",
102        )]
103    }
104
105    fn status(&self) -> CapabilityStatus {
106        CapabilityStatus::Available
107    }
108
109    fn icon(&self) -> Option<&str> {
110        Some("book-open")
111    }
112
113    fn category(&self) -> Option<&str> {
114        Some("Data")
115    }
116
117    fn system_prompt_addition(&self) -> Option<&str> {
118        Some(
119            "Curated data knowledge is mounted at `/knowledge/{tables,business,queries}` as an Open Knowledge Format (OKF) bundle: markdown files with YAML frontmatter, navigable via `index.md` files. Read it before writing SQL; it is ground truth for schema semantics, metrics, and validated query patterns.",
120        )
121    }
122
123    fn mounts(&self) -> Vec<MountPoint> {
124        let knowledge_dir = MountDirectoryBuilder::new()
125            .file("index.md", Self::ROOT_INDEX)
126            .dir(
127                "tables",
128                MountDirectoryBuilder::new().file("index.md", Self::TABLES_INDEX),
129            )
130            .dir(
131                "business",
132                MountDirectoryBuilder::new().file("index.md", Self::BUSINESS_INDEX),
133            )
134            .dir(
135                "queries",
136                MountDirectoryBuilder::new().file("index.md", Self::QUERIES_INDEX),
137            )
138            .build();
139
140        vec![MountPoint::readonly("/knowledge", knowledge_dir, self.id())]
141    }
142
143    fn dependencies(&self) -> Vec<&'static str> {
144        vec!["session_file_system"]
145    }
146
147    fn features(&self) -> Vec<&'static str> {
148        vec!["file_system"]
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::capability_types::MountSource;
156
157    // Metadata constants covered by builtin_capabilities_satisfy_registry_invariants.
158
159    #[test]
160    fn test_has_system_prompt() {
161        let cap = DataKnowledgeCapability;
162        let prompt = cap.system_prompt_addition().unwrap();
163        assert!(prompt.contains("/knowledge/{tables,business,queries}"));
164        assert!(prompt.contains("ground truth"));
165        // Framed as an OKF bundle so agents navigate it accordingly.
166        assert!(prompt.contains("Open Knowledge Format"));
167    }
168
169    #[test]
170    fn test_mounts_knowledge_scaffold() {
171        let cap = DataKnowledgeCapability;
172        let mounts = cap.mounts();
173        assert_eq!(mounts.len(), 1);
174
175        let mount = &mounts[0];
176        assert_eq!(mount.path, "/knowledge");
177        assert!(mount.is_readonly());
178        assert_eq!(mount.capability_id, "data_knowledge");
179
180        match &mount.source {
181            MountSource::InlineDirectory { entries } => {
182                // Three concept directories plus the OKF root index.md.
183                assert_eq!(entries.len(), 4);
184                assert!(entries.contains_key("tables"));
185                assert!(entries.contains_key("business"));
186                assert!(entries.contains_key("queries"));
187                assert!(entries.contains_key("index.md"));
188            }
189            _ => panic!("Expected InlineDirectory"),
190        }
191    }
192
193    #[test]
194    fn test_root_index_declares_okf_version() {
195        let cap = DataKnowledgeCapability;
196        let mounts = cap.mounts();
197        match &mounts[0].source {
198            MountSource::InlineDirectory { entries } => {
199                match entries.get("index.md").map(|e| &e.source) {
200                    Some(MountSource::InlineFile { content, .. }) => {
201                        assert!(content.contains("okf_version: \"0.1\""));
202                    }
203                    other => panic!("expected index.md inline file, got {other:?}"),
204                }
205            }
206            _ => panic!("Expected InlineDirectory"),
207        }
208    }
209}