# KOGRAL Configuration Architecture

Complete reference showing how form fragments, templates, and Nickel schemas work together.

## Three-Tier Configuration Pipeline

```
┌─────────────────────────────────────────────────────────────────┐
│                  USER INTERACTION LAYER                         │
│           typedialog-web UI (interactive configuration)         │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                   FORM LAYER (forms/)                           │
│  - kogral-config.toml (composes fragments)                      │
│  - fragments/*.toml (field definitions with nickel_path)        │
│     ├─ graph.toml              [nickel_path: ["graph", ...]]    │
│     ├─ storage.toml            [nickel_path: ["storage", ...]]  │
│     ├─ embeddings.toml         [nickel_path: ["embeddings", ...]]
│     ├─ query.toml              [nickel_path: ["query", ...]]    │
│     ├─ mcp.toml                [nickel_path: ["mcp", ...]]      │
│     └─ sync.toml               [nickel_path: ["sync", ...]]     │
└─────────────────────────────────────────────────────────────────┘
                              ↓
          typedialog-web writes changes to .ncl files
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                NICKEL LAYER (core/ + modes/)                    │
│                                                                 │
│  core/contracts.ncl (type definitions)                          │
│    ├─ GraphConfig { name, version, description }               │
│    ├─ StorageConfig { primary, secondary }                     │
│    ├─ EmbeddingConfig { enabled, provider, model, ... }        │
│    ├─ QueryConfig { similarity_threshold, max_results, ... }   │
│    ├─ McpConfig { server, tools, resources }                   │
│    └─ SyncConfig { auto_index, watch_paths, debounce_ms }      │
│                                                                 │
│  core/defaults.ncl (base values for all environments)           │
│    └─ base = { graph, storage, embeddings, query, mcp, sync }  │
│                                                                 │
│  modes/dev.ncl   (override specific values for dev)             │
│  modes/prod.ncl  (override specific values for production)      │
│  modes/test.ncl  (override specific values for testing)         │
└─────────────────────────────────────────────────────────────────┘
                              ↓
              nickel export --format json
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                  TEMPLATE LAYER (templates/)                    │
│                                                                 │
│  config.json.tera (Tera template)                               │
│    - {{ graph.name }}           ← renders from Nickel export    │
│    - {{ storage.primary }}                                       │
│    - {{ embeddings.enabled }}                                    │
│    - {{ query.similarity_threshold }}                            │
│    - {{ mcp.tools.search }}                                      │
│    - {{ sync.auto_index }}                                       │
└─────────────────────────────────────────────────────────────────┘
                              ↓
              just nickel::generate-config dev
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│              JSON OUTPUT (.kogral/config.json)                  │
│                                                                 │
│  {                                                               │
│    "graph": { "name": "...", "version": "...", ... },           │
│    "storage": { "primary": "filesystem", ... },                 │
│    "embeddings": { "enabled": true, "provider": "fastembed" },  │
│    "query": { "similarity_threshold": 0.4, ... },               │
│    "mcp": { "server": {...}, "tools": {...}, ... },             │
│    "sync": { "auto_index": true, ... }                          │
│  }                                                               │
└─────────────────────────────────────────────────────────────────┘
                              ↓
                   Rust Deserialization
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│              RUST LAYER (crates/kogral-core/)                   │
│                                                                 │
│  KbConfig struct (serde deserialization)                        │
│    ├─ graph: GraphConfig                                        │
│    ├─ storage: StorageConfig                                    │
│    ├─ embeddings: EmbeddingConfig                               │
│    ├─ query: QueryConfig                                        │
│    ├─ mcp: McpConfig                                            │
│    └─ sync: SyncConfig                                          │
│                                                                 │
│  Runtime Usage:                                                  │
│    if config.embeddings.enabled { ... }                         │
│    create_storage(&config.storage)?                             │
│    initialize_mcp(&config.mcp)                                  │
└─────────────────────────────────────────────────────────────────┘
```

## Field Synchronization Example

### Storage Configuration

User edits "Primary Storage" field in form:

```
FORM FRAGMENT (forms/fragments/storage.toml)
──────────────────────────────────────────
[[elements]]
name = "storage_primary"
nickel_path = ["storage", "primary"]
type = "select"
options = ["filesystem", "memory", "surrealdb"]
default = "filesystem"
    ↓ typedialog-web writes to ↓

NICKEL CONTRACT (core/contracts.ncl)
────────────────────────────────────
StorageConfig = {
  primary | StorageType
    | doc "Primary storage backend"
    | default = 'filesystem,
}
    ↓ user modifies, nickel export ↓

NICKEL MODE (modes/dev.ncl)
───────────────────────────
{
  storage = {
    primary = 'filesystem,  # ← User changed to 'surrealdb
  }
}
    ↓ export renders ↓

TERA TEMPLATE (templates/config.json.tera)
──────────────────────────────────────────
{
  "storage": {
    "primary": "{{ storage.primary }}"  # renders: "surrealdb"
  }
}
    ↓ generates ↓

JSON OUTPUT (.kogral/config.json)
─────────────────────────────────
{
  "storage": {
    "primary": "surrealdb"
  }
}
    ↓ Rust deserializes ↓

RUST CODE (crates/kogral-core/src/config.rs)
─────────────────────────────────────────────
pub struct StorageConfig {
    pub primary: StorageType,  // enum: Filesystem | Memory | SurrealDB
    ...
}

// Usage
match config.storage.primary {
    StorageType::SurrealDB => create_surrealdb_storage()?,
    StorageType::Filesystem => create_filesystem_storage()?,
    ...
}
```

## Component Relationships

```
Form Fragments ←→ Nickel Contracts
      ↓                   ↓
      └─→ Tera Template ←┘
            ↓
        JSON Output
            ↓
       Rust Struct
```

### Consistency Rules

1. **Fragment → Nickel**: Every `nickel_path` must have corresponding type in contracts
2. **Nickel → Template**: Every Nickel field must have corresponding Tera variable
3. **Template → JSON**: Every Tera variable must render valid JSON
4. **JSON → Rust**: Every JSON field must deserialize to KbConfig

## Validation Pipeline

```
User edits form
     ↓
typedialog-web validates against fragment constraints
     ↓
Writes to .ncl files
     ↓
just nickel::validate-config
     ├─→ Nickel typechecks contracts
     └─→ Exports to JSON
     ↓
just nickel::generate-config
     ├─→ Tera renders template
     └─→ Produces .kogral/config.json
     ↓
Rust deserializes KbConfig
     ├─→ Serde validates JSON structure
     └─→ Type mismatch = compile error
```

## Adding a New Configuration Field

To add a new field (e.g., logging level):

### 1. Add to Nickel Contract

```nickel
# core/contracts.ncl
LoggingConfig = {
  level | [| 'debug, 'info, 'warn, 'error |]
    | doc "Logging verbosity level"
    | default = 'info,
}

KbConfig = {
  ...
  logging | LoggingConfig,
  ...
}
```

### 2. Add Default Value

```nickel
# core/defaults.ncl
logging = {
  level = 'info,
}
```

### 3. Create Form Fragment

```toml
# forms/fragments/logging.toml
[[elements]]
name = "logging_section_header"
title = "📝 Logging"
type = "section_header"

[[elements]]
name = "logging_level"
nickel_path = ["logging", "level"]
type = "select"
options = ["debug", "info", "warn", "error"]
default = "info"
prompt = "Log Level"
help = "Logging verbosity level"
```

### 4. Add to Main Form

```toml
# forms/kogral-config.toml
[[items]]
includes = ["fragments/logging.toml"]
name = "logging_group"
title = "Logging"
type = "group"
```

### 5. Update Template

```tera
# templates/config.json.tera
"logging": {
  "level": "{{ logging.level }}"
}
```

### 6. Verify

```bash
just nickel::validate-config      # Contracts valid?
just nickel::export-modes         # JSON valid?
just nickel::generate-config dev  # Config generates?
```

## Integration Checklist

- [ ] Fragment defines field with `nickel_path`
- [ ] Contract defines type for that path
- [ ] Default provides value
- [ ] Template has corresponding Tera variable
- [ ] Mode overlays include any environment-specific values
- [ ] Validation passes
- [ ] JSON output contains field
- [ ] Rust struct deserializes successfully
