630 lines
27 KiB
HTML
630 lines
27 KiB
HTML
|
|
<!DOCTYPE HTML>
|
||
|
|
<html lang="en" class="rust sidebar-visible" dir="ltr">
|
||
|
|
<head>
|
||
|
|
<!-- Book generated using mdBook -->
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<title>ADR-003: Hybrid Storage Strategy - 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/adrs/003-hybrid-storage.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="adr-003-hybrid-storage-strategy"><a class="header" href="#adr-003-hybrid-storage-strategy">ADR-003: Hybrid Storage Strategy</a></h1>
|
||
|
|
<p><strong>Status</strong>: Accepted</p>
|
||
|
|
<p><strong>Date</strong>: 2026-01-17</p>
|
||
|
|
<p><strong>Deciders</strong>: Architecture Team</p>
|
||
|
|
<p><strong>Context</strong>: Storage Backend Strategy for Knowledge Base</p>
|
||
|
|
<hr />
|
||
|
|
<h2 id="context"><a class="header" href="#context">Context</a></h2>
|
||
|
|
<p>The KOGRAL needs to store knowledge graphs with these requirements:</p>
|
||
|
|
<ol>
|
||
|
|
<li><strong>Git-Friendly</strong>: Knowledge should version alongside code</li>
|
||
|
|
<li><strong>Scalable</strong>: Support small projects (10s of nodes) to large organizations (10,000+ nodes)</li>
|
||
|
|
<li><strong>Queryable</strong>: Efficient graph queries and relationship traversal</li>
|
||
|
|
<li><strong>Offline-Capable</strong>: Work without network access</li>
|
||
|
|
<li><strong>Collaborative</strong>: Support shared organizational knowledge</li>
|
||
|
|
<li><strong>Cost-Effective</strong>: Free for small projects, reasonable cost at scale</li>
|
||
|
|
</ol>
|
||
|
|
<p><strong>Constraints</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Developers want to edit knowledge in text editors</li>
|
||
|
|
<li>Organizations want centralized guideline management</li>
|
||
|
|
<li>Git workflows essential for code-adjacent knowledge</li>
|
||
|
|
<li>Large graphs need database performance</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="option-1-filesystem-only"><a class="header" href="#option-1-filesystem-only">Option 1: Filesystem Only</a></h3>
|
||
|
|
<p><strong>Approach</strong>: Store everything as markdown files</p>
|
||
|
|
<p><strong>Pros</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>✅ Git-native (perfect for versioning)</li>
|
||
|
|
<li>✅ Text editor friendly</li>
|
||
|
|
<li>✅ No dependencies</li>
|
||
|
|
<li>✅ Works offline</li>
|
||
|
|
<li>✅ Free</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Cons</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>❌ Poor performance for large graphs (100 0+ nodes)</li>
|
||
|
|
<li>❌ No efficient graph queries</li>
|
||
|
|
<li>❌ Difficult to share across projects</li>
|
||
|
|
<li>❌ Manual sync for collaboration</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Scalability</strong>: Good for < 100 nodes, poor beyond</p>
|
||
|
|
<h3 id="option-2-database-only-surrealdb"><a class="header" href="#option-2-database-only-surrealdb">Option 2: Database Only (SurrealDB)</a></h3>
|
||
|
|
<p><strong>Approach</strong>: Store all knowledge in SurrealDB graph database</p>
|
||
|
|
<p><strong>Pros</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>✅ Excellent query performance</li>
|
||
|
|
<li>✅ Native graph relationships</li>
|
||
|
|
<li>✅ Scalable to millions of nodes</li>
|
||
|
|
<li>✅ Centralized for collaboration</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Cons</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>❌ Not git-trackable</li>
|
||
|
|
<li>❌ Requires running database server</li>
|
||
|
|
<li>❌ Can't edit with text editor</li>
|
||
|
|
<li>❌ Network dependency</li>
|
||
|
|
<li>❌ Infrastructure cost</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Scalability</strong>: Excellent, but loses developer workflow benefits</p>
|
||
|
|
<h3 id="option-3-hybrid-filesystem--surrealdb"><a class="header" href="#option-3-hybrid-filesystem--surrealdb">Option 3: Hybrid (Filesystem + SurrealDB)</a></h3>
|
||
|
|
<p><strong>Approach</strong>: Filesystem for local project knowledge, SurrealDB for shared organizational knowledge</p>
|
||
|
|
<p><strong>Pros</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>✅ Git-friendly for project knowledge</li>
|
||
|
|
<li>✅ Text editor friendly</li>
|
||
|
|
<li>✅ Scalable for shared knowledge</li>
|
||
|
|
<li>✅ Works offline (local graph)</li>
|
||
|
|
<li>✅ Collaborative (shared graph)</li>
|
||
|
|
<li>✅ Cost-effective (DB only for shared)</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Cons</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>❌ More complex implementation</li>
|
||
|
|
<li>❌ Sync mechanism needed</li>
|
||
|
|
<li>❌ Two storage systems to manage</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Scalability</strong>: Excellent - best of both worlds</p>
|
||
|
|
<hr />
|
||
|
|
<h2 id="decision"><a class="header" href="#decision">Decision</a></h2>
|
||
|
|
<p><strong>We will use a hybrid storage strategy: Filesystem (local) + SurrealDB (shared).</strong></p>
|
||
|
|
<p><strong>Architecture</strong>:</p>
|
||
|
|
<pre><code>┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ Project A (.kogral/) │
|
||
|
|
│ Storage: Filesystem (git-tracked) │
|
||
|
|
│ Scope: Project-specific notes, decisions, patterns │
|
||
|
|
│ Access: Local only │
|
||
|
|
└──────────────────┬──────────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
│ [inherits]
|
||
|
|
↓
|
||
|
|
┌─────────────────────────────────────────────────────────────┐
|
||
|
|
│ Shared KB (SurrealDB or synced filesystem) │
|
||
|
|
│ Storage: SurrealDB (scalable) or filesystem (synced) │
|
||
|
|
│ Scope: Organization-wide guidelines, patterns │
|
||
|
|
│ Access: All projects │
|
||
|
|
└─────────────────────────────────────────────────────────────┘
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Implementation</strong>:</p>
|
||
|
|
<pre><code class="language-nickel"># Project config
|
||
|
|
{
|
||
|
|
storage = {
|
||
|
|
primary = 'filesystem, # Local project knowledge
|
||
|
|
secondary = {
|
||
|
|
enabled = true,
|
||
|
|
type = 'surrealdb, # Shared knowledge
|
||
|
|
url = "ws://kb-central.company.com:8000",
|
||
|
|
namespace = "organization",
|
||
|
|
database = "shared-kb",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
|
||
|
|
inheritance = {
|
||
|
|
base = "surrealdb://organization/shared-kb", # Inherit from shared
|
||
|
|
priority = 100, # Project overrides shared
|
||
|
|
},
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Sync Strategy</strong>:</p>
|
||
|
|
<pre><code>.kogral/ (Filesystem)
|
||
|
|
↓ [on save]
|
||
|
|
Watch for changes
|
||
|
|
↓ [debounced]
|
||
|
|
Sync to SurrealDB
|
||
|
|
↓
|
||
|
|
Shared graph updated
|
||
|
|
↓ [on query]
|
||
|
|
Merge local + shared results
|
||
|
|
</code></pre>
|
||
|
|
<hr />
|
||
|
|
<h2 id="consequences"><a class="header" href="#consequences">Consequences</a></h2>
|
||
|
|
<h3 id="positive"><a class="header" href="#positive">Positive</a></h3>
|
||
|
|
<p>✅ <strong>Developer Workflow Preserved</strong>:</p>
|
||
|
|
<pre><code class="language-bash"># Local knowledge workflow (unchanged)
|
||
|
|
vim .kogral/notes/my-note.md
|
||
|
|
git add .kogral/notes/my-note.md
|
||
|
|
git commit -m "Add implementation note"
|
||
|
|
git push
|
||
|
|
</code></pre>
|
||
|
|
<p>✅ <strong>Git Integration</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Project knowledge versioned with code</li>
|
||
|
|
<li>Branches include relevant knowledge</li>
|
||
|
|
<li>Merges resolve knowledge conflicts</li>
|
||
|
|
<li>PR reviews include knowledge changes</li>
|
||
|
|
</ul>
|
||
|
|
<p>✅ <strong>Offline Development</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Full functionality without network</li>
|
||
|
|
<li>Shared guidelines cached locally</li>
|
||
|
|
<li>Sync when reconnected</li>
|
||
|
|
</ul>
|
||
|
|
<p>✅ <strong>Scalability</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Projects: filesystem (100s of nodes, fine performance)</li>
|
||
|
|
<li>Organization: SurrealDB (10,000+ nodes, excellent performance)</li>
|
||
|
|
</ul>
|
||
|
|
<p>✅ <strong>Collaboration</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Shared guidelines accessible to all projects</li>
|
||
|
|
<li>Updates to shared knowledge propagate automatically</li>
|
||
|
|
<li>Consistent practices across organization</li>
|
||
|
|
</ul>
|
||
|
|
<p>✅ <strong>Cost-Effective</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Small projects: free (filesystem only)</li>
|
||
|
|
<li>Organizations: SurrealDB for shared only (not all project knowledge)</li>
|
||
|
|
</ul>
|
||
|
|
<p>✅ <strong>Gradual Adoption</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Start with filesystem only</li>
|
||
|
|
<li>Add SurrealDB when needed</li>
|
||
|
|
<li>Feature-gated (<code>--features surrealdb</code>)</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="negative"><a class="header" href="#negative">Negative</a></h3>
|
||
|
|
<p>❌ <strong>Complexity</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Two storage implementations</li>
|
||
|
|
<li>Sync mechanism required</li>
|
||
|
|
<li>Conflict resolution needed</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Mitigation</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Storage trait abstracts differences</li>
|
||
|
|
<li>Sync is optional (can disable)</li>
|
||
|
|
<li>Conflicts rare (guidelines change infrequently)</li>
|
||
|
|
</ul>
|
||
|
|
<p>❌ <strong>Sync Latency</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Changes to shared KB not instant in all projects</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Mitigation</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Acceptable latency (guidelines don't change rapidly)</li>
|
||
|
|
<li>Manual sync command available (<code>kb sync</code>)</li>
|
||
|
|
<li>Auto-sync on query (fetch latest)</li>
|
||
|
|
</ul>
|
||
|
|
<p>❌ <strong>Infrastructure Requirement</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>SurrealDB server needed for shared KB</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Mitigation</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Optional (can use synced filesystem instead)</li>
|
||
|
|
<li>Docker Compose for easy setup</li>
|
||
|
|
<li>Managed SurrealDB Cloud option</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="neutral"><a class="header" href="#neutral">Neutral</a></h3>
|
||
|
|
<p>⚪ <strong>Storage Trait Implementation</strong>:</p>
|
||
|
|
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
|
|
</span><span class="boring">fn main() {
|
||
|
|
</span>#[async_trait]
|
||
|
|
pub trait Storage {
|
||
|
|
async fn save_graph(&self, graph: &Graph) -> Result<()>;
|
||
|
|
async fn load_graph(&self, name: &str) -> Result<Graph>;
|
||
|
|
async fn list_graphs(&self) -> Result<Vec<String>>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Three implementations
|
||
|
|
impl Storage for FilesystemStorage { /* ... */ }
|
||
|
|
impl Storage for SurrealDbStorage { /* ... */ }
|
||
|
|
impl Storage for MemoryStorage { /* ... */ }
|
||
|
|
<span class="boring">}</span></code></pre></pre>
|
||
|
|
<p>Abstraction makes multi-backend manageable.</p>
|
||
|
|
<hr />
|
||
|
|
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
|
||
|
|
<h3 id="small-project-solo-developer"><a class="header" href="#small-project-solo-developer">Small Project (Solo Developer)</a></h3>
|
||
|
|
<p><strong>Config</strong>:</p>
|
||
|
|
<pre><code class="language-nickel">{ storage = { primary = 'filesystem } }
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Behavior</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>All knowledge in <code>.kogral/</code> directory</li>
|
||
|
|
<li>Git-tracked with code</li>
|
||
|
|
<li>No database required</li>
|
||
|
|
<li>Works offline</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="medium-project-team"><a class="header" href="#medium-project-team">Medium Project (Team)</a></h3>
|
||
|
|
<p><strong>Config</strong>:</p>
|
||
|
|
<pre><code class="language-nickel">{
|
||
|
|
storage = {
|
||
|
|
primary = 'filesystem,
|
||
|
|
secondary = {
|
||
|
|
enabled = true,
|
||
|
|
type = 'surrealdb,
|
||
|
|
url = "ws://team-kb.local:8000",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Behavior</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Project knowledge in <code>.kogral/</code> (git-tracked)</li>
|
||
|
|
<li>Shared team patterns in SurrealDB</li>
|
||
|
|
<li>Automatic sync</li>
|
||
|
|
<li>Offline fallback to cached</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="large-organization"><a class="header" href="#large-organization">Large Organization</a></h3>
|
||
|
|
<p><strong>Config</strong>:</p>
|
||
|
|
<pre><code class="language-nickel">{
|
||
|
|
storage = {
|
||
|
|
primary = 'filesystem,
|
||
|
|
secondary = {
|
||
|
|
enabled = true,
|
||
|
|
type = 'surrealdb,
|
||
|
|
url = "ws://kb.company.com:8000",
|
||
|
|
namespace = "engineering",
|
||
|
|
database = "shared-kb",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
|
||
|
|
inheritance = {
|
||
|
|
base = "surrealdb://engineering/shared-kb",
|
||
|
|
guidelines = [
|
||
|
|
"surrealdb://engineering/rust-guidelines",
|
||
|
|
"surrealdb://engineering/security-policies",
|
||
|
|
],
|
||
|
|
},
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Behavior</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Project-specific in <code>.kogral/</code></li>
|
||
|
|
<li>Organization guidelines in SurrealDB</li>
|
||
|
|
<li>Security policies enforced</li>
|
||
|
|
<li>Automatic guideline updates</li>
|
||
|
|
</ul>
|
||
|
|
<hr />
|
||
|
|
<h2 id="sync-mechanism"><a class="header" href="#sync-mechanism">Sync Mechanism</a></h2>
|
||
|
|
<h3 id="filesystem--surrealdb"><a class="header" href="#filesystem--surrealdb">Filesystem → SurrealDB</a></h3>
|
||
|
|
<p><strong>Trigger</strong>: File changes detected (via <code>notify</code> crate)</p>
|
||
|
|
<p><strong>Process</strong>:</p>
|
||
|
|
<ol>
|
||
|
|
<li>Parse changed markdown file</li>
|
||
|
|
<li>Convert to Node struct</li>
|
||
|
|
<li>Upsert to SurrealDB</li>
|
||
|
|
<li>Update relationships</li>
|
||
|
|
</ol>
|
||
|
|
<p><strong>Debouncing</strong>: 500ms (configurable)</p>
|
||
|
|
<h3 id="surrealdb--filesystem"><a class="header" href="#surrealdb--filesystem">SurrealDB → Filesystem</a></h3>
|
||
|
|
<p><strong>Trigger</strong>: Query for shared knowledge</p>
|
||
|
|
<p><strong>Process</strong>:</p>
|
||
|
|
<ol>
|
||
|
|
<li>Query SurrealDB for shared nodes</li>
|
||
|
|
<li>Cache locally (in-memory or filesystem)</li>
|
||
|
|
<li>Merge with local results</li>
|
||
|
|
<li>Return combined</li>
|
||
|
|
</ol>
|
||
|
|
<p><strong>Caching</strong>: TTL-based (5 minutes default)</p>
|
||
|
|
<h3 id="conflict-resolution"><a class="header" href="#conflict-resolution">Conflict Resolution</a></h3>
|
||
|
|
<p><strong>Strategy</strong>: Last-write-wins with version tracking</p>
|
||
|
|
<p><strong>Example</strong>:</p>
|
||
|
|
<pre><code>Project A: Updates shared guideline (v1 → v2)
|
||
|
|
Project B: Has cached v1
|
||
|
|
|
||
|
|
On Project B query:
|
||
|
|
- Detects v2 available
|
||
|
|
- Fetches v2
|
||
|
|
- Updates cache
|
||
|
|
- Uses v2 going forward
|
||
|
|
</code></pre>
|
||
|
|
<hr />
|
||
|
|
<h2 id="alternatives-considered"><a class="header" href="#alternatives-considered">Alternatives Considered</a></h2>
|
||
|
|
<h3 id="git-submodules-for-shared-knowledge"><a class="header" href="#git-submodules-for-shared-knowledge">Git Submodules for Shared Knowledge</a></h3>
|
||
|
|
<p><strong>Rejected</strong>: Cumbersome workflow</p>
|
||
|
|
<ul>
|
||
|
|
<li>Requires manual submodule update</li>
|
||
|
|
<li>Merge conflicts in shared submodule</li>
|
||
|
|
<li>Not discoverable (need to know submodule exists)</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="syncthing-for-filesystem-sync"><a class="header" href="#syncthing-for-filesystem-sync">Syncthing for Filesystem Sync</a></h3>
|
||
|
|
<p><strong>Rejected</strong>: Not designed for this use case</p>
|
||
|
|
<ul>
|
||
|
|
<li>No query optimization</li>
|
||
|
|
<li>No relationship indexes</li>
|
||
|
|
<li>Sync conflicts difficult to resolve</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="postgresql-with-json"><a class="header" href="#postgresql-with-json">PostgreSQL with JSON</a></h3>
|
||
|
|
<p><strong>Rejected</strong>: Not a graph database</p>
|
||
|
|
<ul>
|
||
|
|
<li>Poor graph query performance</li>
|
||
|
|
<li>Relationship traversal requires complex SQL joins</li>
|
||
|
|
<li>No native graph features</li>
|
||
|
|
</ul>
|
||
|
|
<hr />
|
||
|
|
<h2 id="migration-path"><a class="header" href="#migration-path">Migration Path</a></h2>
|
||
|
|
<h3 id="phase-1-filesystem-only-current"><a class="header" href="#phase-1-filesystem-only-current">Phase 1: Filesystem Only (Current)</a></h3>
|
||
|
|
<ul>
|
||
|
|
<li>All storage via filesystem</li>
|
||
|
|
<li>Git-tracked</li>
|
||
|
|
<li>No database required</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="phase-2-optional-surrealdb"><a class="header" href="#phase-2-optional-surrealdb">Phase 2: Optional SurrealDB</a></h3>
|
||
|
|
<ul>
|
||
|
|
<li>Add SurrealDB support (feature-gated)</li>
|
||
|
|
<li>Manual sync command</li>
|
||
|
|
<li>Shared KB opt-in</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="phase-3-automatic-sync"><a class="header" href="#phase-3-automatic-sync">Phase 3: Automatic Sync</a></h3>
|
||
|
|
<ul>
|
||
|
|
<li>File watching</li>
|
||
|
|
<li>Auto-sync on changes</li>
|
||
|
|
<li>Background sync</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="phase-4-multi-tenant-surrealdb"><a class="header" href="#phase-4-multi-tenant-surrealdb">Phase 4: Multi-Tenant SurrealDB</a></h3>
|
||
|
|
<ul>
|
||
|
|
<li>Organization namespaces</li>
|
||
|
|
<li>Access control</li>
|
||
|
|
<li>Audit logs</li>
|
||
|
|
</ul>
|
||
|
|
<hr />
|
||
|
|
<h2 id="monitoring"><a class="header" href="#monitoring">Monitoring</a></h2>
|
||
|
|
<p><strong>Success Criteria</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Developers don't notice hybrid complexity</li>
|
||
|
|
<li>Sync completes < 1 second for typical changes</li>
|
||
|
|
<li>Shared guidelines accessible in < 100ms</li>
|
||
|
|
<li>Zero data loss in sync</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Metrics</strong>:</p>
|
||
|
|
<ul>
|
||
|
|
<li>Sync latency (P50, P95, P99)</li>
|
||
|
|
<li>Cache hit rate (shared knowledge)</li>
|
||
|
|
<li>Conflict rate (expect < 0.1%)</li>
|
||
|
|
<li>User satisfaction</li>
|
||
|
|
</ul>
|
||
|
|
<hr />
|
||
|
|
<h2 id="references"><a class="header" href="#references">References</a></h2>
|
||
|
|
<ul>
|
||
|
|
<li><a href="https://surrealdb.com/docs">SurrealDB Documentation</a></li>
|
||
|
|
<li><a href="../../crates/kb-core/src/storage/mod.rs">Storage Trait Implementation</a></li>
|
||
|
|
<li><a href="../../crates/kb-core/src/storage/filesystem.rs">FilesystemStorage</a></li>
|
||
|
|
<li><a href="../../crates/kb-core/src/storage/surrealdb.rs">SurrealDbStorage</a></li>
|
||
|
|
<li><a href="../../scripts/kb-sync.nu">Sync Mechanism</a></li>
|
||
|
|
</ul>
|
||
|
|
<hr />
|
||
|
|
<h2 id="revision-history"><a class="header" href="#revision-history">Revision History</a></h2>
|
||
|
|
<div class="table-wrapper"><table><thead><tr><th>Date</th><th>Author</th><th>Change</th></tr></thead><tbody>
|
||
|
|
<tr><td>2026-01-17</td><td>Architecture Team</td><td>Initial decision</td></tr>
|
||
|
|
</tbody></table>
|
||
|
|
</div>
|
||
|
|
<hr />
|
||
|
|
<p><strong>Previous ADR</strong>: <a href="002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a>
|
||
|
|
<strong>Next ADR</strong>: <a href="004-logseq-compatibility.html">ADR-004: Logseq Compatibility</a></p>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
|
|
<!-- Mobile navigation buttons -->
|
||
|
|
<a rel="prev" href="../../architecture/adrs/002-fastembed-ai-providers.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/adrs/004-logseq-blocks-support.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/adrs/002-fastembed-ai-providers.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/adrs/004-logseq-blocks-support.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>
|