kogral/docs/book/architecture/config-driven.html

661 lines
30 KiB
HTML
Raw Normal View History

2026-01-23 16:11:07 +00:00
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Config-Driven Design - KOGRAL Documentation</title>
<!-- Custom HTML head -->
<meta name="description" content="Complete documentation for KOGRAL - Git-native knowledge graphs for developer teams">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../";
const default_light_theme = "rust";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('rust')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">KOGRAL Documentation</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/your-org/knowledge-base" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/config-driven.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="config-driven-architecture"><a class="header" href="#config-driven-architecture">Config-Driven Architecture</a></h1>
<p>The KOGRAL follows a <strong>config-driven architecture</strong> where all behavior is defined through Nickel configuration files rather than hardcoded in Rust.</p>
<h2 id="philosophy"><a class="header" href="#philosophy">Philosophy</a></h2>
<p><strong>"Configuration, not code, defines behavior"</strong></p>
<p>Instead of hardcoding storage backends, embedding providers, or query parameters, KB uses a layered configuration system that composes settings from multiple sources:</p>
<ol>
<li><strong>Schema contracts</strong> (type definitions)</li>
<li><strong>Defaults</strong> (base values)</li>
<li><strong>Mode overlays</strong> (dev/prod/test optimizations)</li>
<li><strong>User customizations</strong> (project-specific overrides)</li>
</ol>
<p>This approach provides:</p>
<ul>
<li><strong>Type safety</strong> - Nickel contracts validate configuration before runtime</li>
<li><strong>Composability</strong> - Mix and match configurations for different environments</li>
<li><strong>Discoverability</strong> - Self-documenting schemas with inline documentation</li>
<li><strong>Hot-reload</strong> - Change behavior without recompiling Rust code</li>
<li><strong>Double validation</strong> - Nickel contracts + serde ensure correctness</li>
</ul>
<h2 id="configuration-composition-flow"><a class="header" href="#configuration-composition-flow">Configuration Composition Flow</a></h2>
<p><img src="../diagrams/config-composition.svg" alt="Configuration Composition" /></p>
<p>The configuration system uses a <strong>four-layer composition pattern</strong>:</p>
<h3 id="layer-1-schema-contracts"><a class="header" href="#layer-1-schema-contracts">Layer 1: Schema Contracts</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/contracts.ncl</code></p>
<p><strong>Purpose</strong>: Define types and validation rules using Nickel contracts.</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-nickel">{
StorageType = [| 'filesystem, 'memory, 'surrealdb |],
StorageConfig = {
primary | StorageType
| doc "Primary storage backend"
| default = 'filesystem,
secondary | SecondaryStorageConfig
| doc "Optional secondary storage"
| default = { enabled = false },
},
}
</code></pre>
<p><strong>Benefits</strong>:</p>
<ul>
<li>Enum validation (only valid storage types accepted)</li>
<li>Required vs optional fields</li>
<li>Default values for optional fields</li>
<li>Documentation attached to types</li>
</ul>
<h3 id="layer-2-defaults"><a class="header" href="#layer-2-defaults">Layer 2: Defaults</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/defaults.ncl</code></p>
<p><strong>Purpose</strong>: Provide sensible base values for all configuration options.</p>
<p><strong>Example</strong>:</p>
<pre><code class="language-nickel">{
base = {
storage = {
primary = 'filesystem,
secondary = {
enabled = false,
type = 'surrealdb,
url = "ws://localhost:8000",
},
},
embeddings = {
enabled = true,
provider = 'fastembed,
model = "BAAI/bge-small-en-v1.5",
dimensions = 384,
},
} | contracts.KbConfig,
}
</code></pre>
<p><strong>Validated by</strong>: <code>contracts.KbConfig</code> contract ensures defaults are valid.</p>
<h3 id="layer-3-mode-overlays"><a class="header" href="#layer-3-mode-overlays">Layer 3: Mode Overlays</a></h3>
<p><strong>Location</strong>: <code>schemas/kb/modes/{dev,prod,test}.ncl</code></p>
<p><strong>Purpose</strong>: Environment-specific optimizations and tuning.</p>
<h4 id="development-mode-devncl"><a class="header" href="#development-mode-devncl">Development Mode (<code>dev.ncl</code>)</a></h4>
<p>Optimized for: Fast iteration, local development, debugging</p>
<pre><code class="language-nickel">{
storage = {
primary = 'filesystem,
secondary = { enabled = false }, # No database overhead
},
embeddings = {
provider = 'fastembed, # Local, no API costs
},
sync = {
auto_index = false, # Manual control
},
}
</code></pre>
<h4 id="production-mode-prodncl"><a class="header" href="#production-mode-prodncl">Production Mode (<code>prod.ncl</code>)</a></h4>
<p>Optimized for: Performance, reliability, scalability</p>
<pre><code class="language-nickel">{
storage = {
secondary = { enabled = true }, # SurrealDB for scale
},
embeddings = {
provider = 'openai, # High-quality cloud embeddings
model = "text-embedding-3-small",
dimensions = 1536,
},
sync = {
auto_index = true,
debounce_ms = 300, # Fast response
},
}
</code></pre>
<h4 id="test-mode-testncl"><a class="header" href="#test-mode-testncl">Test Mode (<code>test.ncl</code>)</a></h4>
<p>Optimized for: Fast tests, isolation, determinism</p>
<pre><code class="language-nickel">{
storage = {
primary = 'memory, # Ephemeral, no disk I/O
},
embeddings = {
enabled = false, # Disable for speed
},
sync = {
auto_index = false,
debounce_ms = 0, # No delays in tests
},
}
</code></pre>
<h3 id="layer-4-user-customizations"><a class="header" href="#layer-4-user-customizations">Layer 4: User Customizations</a></h3>
<p><strong>Location</strong>: <code>.kb-config/core/kb.ncl</code> or <code>.kb-config/platform/{dev,prod,test}.ncl</code></p>
<p><strong>Purpose</strong>: Project-specific or deployment-specific overrides.</p>
<p><strong>Example</strong> (user project config):</p>
<pre><code class="language-nickel">let mode = import "../../schemas/kb/modes/dev.ncl" in
let user_custom = {
graph = {
name = "my-project",
},
embeddings = {
provider = 'claude, # Override to use Claude
model = "claude-3-haiku-20240307",
},
query = {
similarity_threshold = 0.7, # Stricter threshold
},
} in
helpers.compose_config defaults.base mode user_custom
| contracts.KbConfig
</code></pre>
<h2 id="composition-mechanism"><a class="header" href="#composition-mechanism">Composition Mechanism</a></h2>
<p>The <code>helpers.ncl</code> module provides the composition function:</p>
<pre><code class="language-nickel">{
# Recursively merge with override precedence
merge_with_override = fun base override =&gt; /* ... */,
# Compose three layers
compose_config = fun defaults mode_config user_custom =&gt;
let with_mode = merge_with_override defaults mode_config in
merge_with_override with_mode user_custom,
}
</code></pre>
<p><strong>Merge behavior</strong>:</p>
<ul>
<li>Records are merged recursively</li>
<li>Override values take precedence over base values</li>
<li>Arrays are not merged, override replaces base</li>
<li>Null in override keeps base value</li>
</ul>
<p><strong>Example merge</strong>:</p>
<pre><code class="language-nickel">base = { storage = { primary = 'filesystem }, embeddings = { enabled = true } }
override = { storage = { primary = 'memory } }
# Result: { storage = { primary = 'memory }, embeddings = { enabled = true } }
</code></pre>
<h2 id="export-to-json"><a class="header" href="#export-to-json">Export to JSON</a></h2>
<p>Once composed, the Nickel configuration is exported to JSON for Rust consumption:</p>
<pre><code class="language-bash">nickel export --format json .kb-config/core/kb.ncl &gt; .kb-config/targets/kb-core.json
</code></pre>
<p><strong>Output</strong> (<code>.kb-config/targets/kb-core.json</code>):</p>
<pre><code class="language-json">{
"graph": {
"name": "my-project",
"version": "1.0.0"
},
"storage": {
"primary": "memory",
"secondary": {
"enabled": false
}
},
"embeddings": {
"enabled": true,
"provider": "claude",
"model": "claude-3-haiku-20240307",
"dimensions": 768
}
}
</code></pre>
<h2 id="rust-integration"><a class="header" href="#rust-integration">Rust Integration</a></h2>
<p>The Rust code deserializes the JSON into typed structs using serde:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct KbConfig {
pub graph: GraphConfig,
pub storage: StorageConfig,
pub embeddings: EmbeddingConfig,
pub templates: TemplateConfig,
pub query: QueryConfig,
pub mcp: McpConfig,
pub sync: SyncConfig,
}
impl KbConfig {
pub fn from_file(path: &amp;Path) -&gt; Result&lt;Self&gt; {
let json = std::fs::read_to_string(path)?;
let config: KbConfig = serde_json::from_str(&amp;json)?;
Ok(config)
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Usage in kb-core</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let config = KbConfig::from_file(".kb-config/targets/kb-core.json")?;
// Config drives behavior
let storage: Box&lt;dyn Storage&gt; = match config.storage.primary {
StorageType::Filesystem =&gt; Box::new(FilesystemStorage::new(&amp;config)?),
StorageType::Memory =&gt; Box::new(MemoryStorage::new()),
StorageType::SurrealDb =&gt; Box::new(SurrealDbStorage::new(&amp;config).await?),
};
let embeddings: Box&lt;dyn EmbeddingProvider&gt; = match config.embeddings.provider {
EmbeddingProviderType::FastEmbed =&gt; Box::new(FastEmbedProvider::new()?),
EmbeddingProviderType::OpenAI =&gt; Box::new(RigEmbeddingProvider::openai(&amp;config)?),
EmbeddingProviderType::Claude =&gt; Box::new(RigEmbeddingProvider::claude(&amp;config)?),
EmbeddingProviderType::Ollama =&gt; Box::new(RigEmbeddingProvider::ollama(&amp;config)?),
};
<span class="boring">}</span></code></pre></pre>
<h2 id="double-validation"><a class="header" href="#double-validation">Double Validation</a></h2>
<p>Configuration is validated <strong>twice</strong>:</p>
<h3 id="1-nickel-contract-validation"><a class="header" href="#1-nickel-contract-validation">1. Nickel Contract Validation</a></h3>
<p>At export time, Nickel validates:</p>
<ul>
<li>✅ Types match contracts (e.g., <code>primary | StorageType</code>)</li>
<li>✅ Required fields are present</li>
<li>✅ Enums have valid values</li>
<li>✅ Nested structure is correct</li>
</ul>
<p><strong>Error example</strong>:</p>
<pre><code>error: contract broken by a value
┌─ .kb-config/core/kb.ncl:15:5
15│ primary = 'invalid,
│ ^^^^^^^^^^^^^^^^^^^ applied to this expression
= This value is not in the enum ['filesystem, 'memory, 'surrealdb]
</code></pre>
<h3 id="2-serde-deserialization-validation"><a class="header" href="#2-serde-deserialization-validation">2. Serde Deserialization Validation</a></h3>
<p>At runtime, serde validates:</p>
<ul>
<li>✅ JSON structure matches Rust types</li>
<li>✅ Field names match (with rename support)</li>
<li>✅ Values can be converted to Rust types</li>
<li>✅ Required fields are not null</li>
</ul>
<p><strong>Error example</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>Error: missing field `graph` at line 1 column 123
<span class="boring">}</span></code></pre></pre>
<h2 id="benefits-of-config-driven-architecture"><a class="header" href="#benefits-of-config-driven-architecture">Benefits of Config-Driven Architecture</a></h2>
<h3 id="1-zero-hardcoding"><a class="header" href="#1-zero-hardcoding">1. Zero Hardcoding</a></h3>
<p><strong>Bad</strong> (hardcoded):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Hardcoded - requires recompilation to change
let storage = FilesystemStorage::new("/fixed/path");
let threshold = 0.6;
<span class="boring">}</span></code></pre></pre>
<p><strong>Good</strong> (config-driven):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Config-driven - change via .ncl file
let storage = create_storage(&amp;config)?;
let threshold = config.query.similarity_threshold;
<span class="boring">}</span></code></pre></pre>
<h3 id="2-environment-flexibility"><a class="header" href="#2-environment-flexibility">2. Environment Flexibility</a></h3>
<p>Same codebase, different behavior:</p>
<pre><code class="language-bash"># Development
nickel export .kb-config/platform/dev.ncl &gt; targets/kb-core.json
# → Filesystem storage, fastembed, no auto-sync
# Production
nickel export .kb-config/platform/prod.ncl &gt; targets/kb-core.json
# → SurrealDB enabled, OpenAI embeddings, auto-sync
# Testing
nickel export .kb-config/platform/test.ncl &gt; targets/kb-core.json
# → In-memory storage, no embeddings, isolated
</code></pre>
<h3 id="3-self-documenting"><a class="header" href="#3-self-documenting">3. Self-Documenting</a></h3>
<p>Nickel contracts include inline documentation:</p>
<pre><code class="language-nickel">StorageType = [| 'filesystem, 'memory, 'surrealdb |]
| doc "Storage backend type: filesystem (git-tracked), memory (ephemeral), surrealdb (scalable)",
</code></pre>
<p>IDEs can show this documentation when editing <code>.ncl</code> files.</p>
<h3 id="4-type-safe-evolution"><a class="header" href="#4-type-safe-evolution">4. Type-Safe Evolution</a></h3>
<p>When adding new features:</p>
<ol>
<li>Update contract in <code>contracts.ncl</code></li>
<li>Add default in <code>defaults.ncl</code></li>
<li>Export validates existing configs</li>
<li>Rust compilation validates deserialization</li>
</ol>
<p>Breaking changes are caught <strong>before runtime</strong>.</p>
<h3 id="5-testability"><a class="header" href="#5-testability">5. Testability</a></h3>
<p>Different test scenarios without code changes:</p>
<pre><code class="language-nickel"># test-semantic-search.ncl
let test_config = defaults.base &amp; {
embeddings = { enabled = true, provider = 'fastembed },
query = { similarity_threshold = 0.3 },
} in test_config
</code></pre>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[test]
fn test_semantic_search() {
let config = KbConfig::from_file("test-semantic-search.json")?;
// Config drives test behavior
}
<span class="boring">}</span></code></pre></pre>
<h2 id="configuration-discovery"><a class="header" href="#configuration-discovery">Configuration Discovery</a></h2>
<p>KB tools automatically discover configuration:</p>
<ol>
<li><strong>Check <code>.kb-config/targets/kb-core.json</code></strong> (pre-exported)</li>
<li><strong>Check <code>.kb-config/core/kb.ncl</code></strong> (export on-demand)</li>
<li><strong>Check environment variable <code>KB_CONFIG</code></strong></li>
<li><strong>Fall back to embedded defaults</strong></li>
</ol>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl KbConfig {
pub fn discover() -&gt; Result&lt;Self&gt; {
if let Ok(config) = Self::from_file(".kb-config/targets/kb-core.json") {
return Ok(config);
}
if Path::new(".kb-config/core/kb.ncl").exists() {
// Export and load
let output = Command::new("nickel")
.args(["export", "--format", "json", ".kb-config/core/kb.ncl"])
.output()?;
return serde_json::from_slice(&amp;output.stdout)?;
}
if let Ok(path) = std::env::var("KB_CONFIG") {
return Self::from_file(&amp;path);
}
Ok(Self::default()) // Embedded defaults
}
}
<span class="boring">}</span></code></pre></pre>
<h2 id="integration-with-justfile"><a class="header" href="#integration-with-justfile">Integration with justfile</a></h2>
<p>The <code>justfile</code> integrates configuration validation:</p>
<pre><code class="language-just"># Validate all Nickel configs
nickel-validate-all:
@echo "Validating Nickel schemas..."
nickel typecheck schemas/kb/contracts.ncl
nickel typecheck schemas/kb/defaults.ncl
nickel typecheck schemas/kb/helpers.ncl
nickel typecheck schemas/kb/modes/dev.ncl
nickel typecheck schemas/kb/modes/prod.ncl
nickel typecheck schemas/kb/modes/test.ncl
nickel typecheck .kb-config/core/kb.ncl
# Export all platform configs
nickel-export-all:
@echo "Exporting platform configs to JSON..."
@mkdir -p .kb-config/targets
nickel export --format json .kb-config/platform/dev.ncl &gt; .kb-config/targets/kb-dev.json
nickel export --format json .kb-config/platform/prod.ncl &gt; .kb-config/targets/kb-prod.json
nickel export --format json .kb-config/platform/test.ncl &gt; .kb-config/targets/kb-test.json
@echo "Exported 3 configurations to .kb-config/targets/"
</code></pre>
<p>Usage:</p>
<pre><code class="language-bash">just nickel::validate-all # Check configs are valid
just nickel::export-all # Generate JSON for all environments
</code></pre>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-never-hardcode"><a class="header" href="#1-never-hardcode">1. Never Hardcode</a></h3>
<p>If it's behavior, it's config:</p>
<ul>
<li>Storage paths</li>
<li>API endpoints</li>
<li>Thresholds</li>
<li>Timeouts</li>
<li>Feature flags</li>
<li>Provider selection</li>
</ul>
<h3 id="2-use-modes-for-environment-differences"><a class="header" href="#2-use-modes-for-environment-differences">2. Use Modes for Environment Differences</a></h3>
<p>Don't put environment-specific values in user config:</p>
<p><strong>Bad</strong>:</p>
<pre><code class="language-nickel"># user config with env-specific values
{
storage = {
url = if env == "prod" then "prod-url" else "dev-url" # Don't do this
}
}
</code></pre>
<p><strong>Good</strong>:</p>
<pre><code class="language-nickel"># modes/prod.ncl
{ storage = { url = "prod-url" } }
# modes/dev.ncl
{ storage = { url = "dev-url" } }
# user config is environment-agnostic
{ graph = { name = "my-project" } }
</code></pre>
<h3 id="3-document-complex-fields"><a class="header" href="#3-document-complex-fields">3. Document Complex Fields</a></h3>
<p>Use Nickel's <code>doc</code> metadata:</p>
<pre><code class="language-nickel">similarity_threshold | Number
| doc "Minimum cosine similarity (0-1) for semantic search matches. Higher = stricter."
| default = 0.6,
</code></pre>
<h3 id="4-validate-early"><a class="header" href="#4-validate-early">4. Validate Early</a></h3>
<p>Run <code>nickel typecheck</code> in CI/CD before building Rust code.</p>
<h3 id="5-version-configs"><a class="header" href="#5-version-configs">5. Version Configs</a></h3>
<p>Track <code>.ncl</code> files in git, ignore <code>.kb-config/targets/*.json</code> (generated).</p>
<h2 id="see-also"><a class="header" href="#see-also">See Also</a></h2>
<ul>
<li><strong>Schema Reference</strong>: <a href="../config/schema.html">Configuration Schema</a></li>
<li><strong>User Guide</strong>: <a href="../config/overview.html">Configuration Guide</a></li>
<li><strong>ADR</strong>: <a href="adrs/001-nickel-vs-toml.html">Why Nickel vs TOML</a></li>
<li><strong>Examples</strong>: <code>.kb-config/core/kb.ncl</code>, <code>.kb-config/platform/*.ncl</code></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../architecture/graph-model.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/storage-architecture.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../architecture/graph-model.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/storage-architecture.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>