# Module Breakdown — Theseus

## Rust Backend (`src/`)

### `src/main.rs` — CLI Binary (2656 lines)

Single binary with clap derive. Contains all CLI subcommands and a full HTTP client.

**Key symbols:**
- `Cli` → `TopLevel` enum: `Serve`, `Project`, `Node`, `Edge`, `Credential`, `Import`, `Search`, `Tell`, `Ask`
- `--api-key` (global): API key for server authentication; passed to `build_http_client()` and `serve()`
- `ProjectAction`: `Create`, `List`, `Delete`, `Status`, `Timeline`, `Stats`, `Analysis`, `Report`
- `NodeAction`: `Add`, `List`, `Update`, `Delete`, `CheckDuplicate`
- `EdgeAction`: `Add`, `List`, `Update`, `Delete`
- `CredentialAction`: `Add`, `List`, `Update`, `Delete`
- `serve` subcommand calls `crate::serve(port, deepseek_api_key, api_key)` from lib
- CLI type abbreviations: `-t` accepts `entry/ep`, `obj`, `recon`, `privesc`, `vuln`, `svc`, `cred`, `net`, `exploit`, `exfil`
- CLI edge by label: `--from-label`/`--to-label` for label-based node matching
- `build_http_client(api_key)`: constructs `reqwest::Client` with `x-api-key` header injected

### `src/lib.rs` — Server Core (900 lines)

axum server setup, middleware, error types, ID resolution helpers, DeepSeek integration.

**Key symbols:**
- `AppError` (line 27): structured error with bilingual messages, error codes, entity/field/hint/source metadata; builder methods: `new`, `bad_request`, `not_found`, `conflict`, `internal`, `from_db`, `with_entity`, `with_field`, `with_hint`, `with_source`, `fallback_for_status`, `log`, `to_api_response`
- `error_response(err)` / `db_error_status(operation, entity, err)` / `db_error_response(operation, entity, err)`: helper functions that log + convert `AppError`/`rusqlite::Error` to HTTP responses
- `AppState` (line 427): `{ db: &'static Database, tx: broadcast::Sender<String>, config: Config, api_key: Option<String> }`
- `Config` (line 347): `{ api_key, deepseek_api_key, cors_origins }` — loaded from `theseus.toml` / `~/.config/theseus/config.toml` / env vars
- `serve(port, deepseek_api_key, api_key)` (line 783): builds router, registers all route modules via `merge()`, applies CORS + API key auth + JSON error middleware
- `api_key_auth_middleware()`: checks `x-api-key` header / `Authorization: Bearer` / `?api_key=` query param; skipped if no key configured
- `json_error_middleware()`: converts empty error responses to structured `ApiResponse` JSON
- `resolve_project_id()` / `resolve_node_id()` / `resolve_edge_id()`: full UUID or ≥6-char prefix matching; all now return structured `AppError` on failure
- `deepseek_chat()`: calls DeepSeek API, returns response content

**Route registration order** (line 814–829):
```
ws → projects → nodes → edges → credentials → timeline → search → evidence → import → analysis → check → report → chat → agent → daily_report
```
All routes are protected by `api_key_auth_middleware` via `route_layer`.

### `src/types.rs` — Shared DTOs (1147 lines)

Single source of truth for all request/response types. Also contains unit tests (line 650+).

**Core entities:**
| Type | Line | Purpose |
|------|------|---------|
| `Project` / `CreateProjectRequest` / `ProjectResponse` / `ProjectStats` | 6–39 | Project CRUD |
| `NodeType` (enum, 11 variants + `Custom(String)`) | 42 | `#[serde(untagged)]` — Custom must be LAST |
| `Node` / `CreateNodeRequest` / `UpdateNodeRequest` / `NodeResponse` / `NodePosition` / `NodeSummary` | 78–215 | Node CRUD; `NodeResponse`/`EdgeResponse`/`NodeSummary` now `Clone` |
| `Edge` / `CreateEdgeRequest` / `EdgeUpdateRequest` / `EdgeResponse` | 163–210 | Edge CRUD |
| `Credential` / `CredentialType` / `CredentialStatus` / request/response DTOs | 324–430 | Credential management |
| `TimelineItem` / `TimelineEdgeInfo` | 434–456 | Timeline view |
| `SearchResult` | 459 | Cross-project search hit |
| `EvidenceFile` / `EvidenceFileResponse` | 473–514 | Evidence file metadata |
| `ImportRequest` / `ImportNode` / `ImportEdge` / `ImportResult` | 516–548 | Bulk import |
| `ApiResponse<T>` / `ApiErrorDetails` / `LocalizedErrorMessages` | 247–318 | Generic API envelope; `ApiResponse` now has `error_code`, `error_details`, `error_messages` fields; `err_with_details()` constructor |
| `AgentEntry` / `AgentRunRequest` / `AgentRunResponse` / `ToolCallRecord` | 557–596 | Agent system |
| `LlmToolCall` / `LlmResponse` / `ToolDefinition` / `ToolResult` / `SideEffect` | 600–636 | LLM tool protocol |
| `AgentMemoryMessage` | 640 | Agent memory persistence |

### `src/db.rs` — Database Layer (1992 lines)

SQLite wrapper. `Database.conn` is `Mutex<Connection>`. All ops go through `fn c()` which recovers from poisoned locks.

**Tables:** `projects`, `nodes`, `edges`, `credentials`, `evidence_files`, `agent_memory`

**Methods by entity:**

| Entity | Methods |
|--------|---------|
| **Projects** | `create_project`, `list_projects`, `get_project`, `delete_project`, `find_project_by_name`, `count_nodes_for_project`, `count_edges_for_project` |
| **Nodes** | `insert_node`, `node_exists`, `list_nodes`, `get_node`, `update_node`, `count_edges_for_node`, `delete_node`, `search_nodes`, `nodes_by_date` (CST), `find_node_by_label`, `find_nodes_by_label_fuzzy` |
| **Edges** | `insert_edge`, `list_edges`, `update_edge`, `delete_edge`, `edge_exists` |
| **Credentials** | `create_credential`, `list_credentials`, `get_credential`, `update_credential`, `delete_credential` |
| **Evidence** | `insert_evidence_file`, `list_evidence_files`, `get_evidence_file`, `delete_evidence_file` |
| **Analysis** | `check` → `CheckResult`, `analyze` → `AttackAnalysis`, `global_search`, `project_timeline` |
| **Agent memory** | `save_memory`, `load_recent_memory`, `prune_memory` |

**Analysis-only types defined in db.rs** (not in types.rs): `AttackPath`, `PathNode`, `TaggedNode`, `NextStep`, `CrossConnection`, `HealthIssue`, `CheckIssue`, `CheckResult`, `AttackAnalysis` (lines 725–799)

**Tests:** `#[cfg(test)] mod tests` (line 1265+) — in-memory DB integration tests for CRUD, conflict handling, cascade deletes, `nodes_by_date` CST conversion, analysis, agent memory, and `CheckResult`.

### `src/routes/` — 16 Route Modules (3892 lines total)

Each module exports `pub fn router() -> Router<AppState>` and is merged in `lib.rs:serve()`.
All modules now use `crate::db_error_status` / `crate::db_error_response` for structured error responses instead of bare `"db error"` strings.

| Module | Lines | Endpoints | Notes |
|--------|-------|-----------|-------|
| `mod.rs` | 15 | — | Re-exports all submodules (including `agent`) |
| `projects.rs` | 247 | CRUD + list | `CreateProjectRequest` validation (empty name, >200 chars, newlines → 409 for dupes); integration tests |
| `nodes.rs` | 726 | CRUD + list + filter + check-duplicate + leaves | Verdict/evidence validation, properties must be JSON object; integration tests |
| `edges.rs` | 396 | CRUD + list | Self-loop → 400, duplicate → 409; integration tests |
| `credentials.rs` | 226 | CRUD | Source node SET NULL on delete; integration tests |
| `evidence.rs` | 355 | Upload/download/list/delete | Multipart upload, UUID filename, path traversal check; structured `AppError` for filesystem failures; integration tests |
| `analysis.rs` | 576 | GET analysis with optional AI | BFS paths + suggestions + optional DeepSeek hint; integration tests |
| `check.rs` | 26 | GET health checks | Deterministic graph checks |
| `timeline.rs` | 121 | GET timeline | Nodes + edge info, chronological; integration tests |
| `search.rs` | 109 | GET cross-project search | LIKE on label+content, LIMIT 50; integration tests |
| `import.rs` | 128 | POST bulk import | Label dedup for nodes, label-based edge matching |
| `report.rs` | 312 | GET AI report | Markdown generation via DeepSeek; `build_report_context()` tested; integration tests |
| `daily_report.rs` | 91 | GET daily report | Nodes filtered by CST date |
| `chat.rs` | 479 | POST chat | Multi-turn conversation with context; uses `AppError::internal()` for AI failures |
| `agent.rs` | 47 | POST agent run | Agent loop entry point |
| `ws.rs` | 36 | WebSocket upgrade | Broadcasts `graph:<pid>` on mutations |

### `src/agent/` — Agent System (1446 lines)

LLM-powered agent with tool execution loop.

| Module | Lines | Purpose |
|--------|-------|---------|
| `mod.rs` | 5 | Re-exports all submodules |
| `llm.rs` | 185 | `LlmClient` — wraps DeepSeek API, handles `ChatMessage` types (system/user/assistant/tool), parses `LlmToolCall` from response |
| `loop.rs` | 250 | `AgentRuntime` — agentic loop: send messages → get response → if tool_calls → execute → repeat until `finish_reason == "stop"` or max turns |
| `memory.rs` | 69 | `MemoryStore` — `save_turn`, `load_recent`, `prune` via `db.save_memory` / `db.load_recent_memory` |
| `prompts.rs` | 167 | `system_prompt()` — builds system prompt from `AgentEntry` (Tell/Ask/Analysis/Report) + project context; `build_project_context()` — summarizes project graph for LLM |
| `tools.rs` | 770 | `registry()` → `Vec<Tool>`, `tool_definitions()` → `Vec<ToolDefinition>`, `execute_tool()` — dispatches tool calls (add_node, add_edge, add_credential, list_nodes, list_edges, search, get_node, update_node, etc.) |

## Frontend (`static/`)

### `static/index.html` (369 lines)

Pure HTML skeleton. References external CSS/JS. Embedded into binary via `include_str!`. Contains: **API key login overlay** (`#auth-overlay` with key input, login/clear buttons), topbar, sidebar (64px rail), Cytoscape container `#cy`, legend, node panel, credentials panel, search dropdown, 4 modals (create/analysis/import/daily-report), toast container. Topbar includes 🔐 button to reopen auth overlay.

### `static/css/style.css` (2645 lines)

Dark theme with 30+ CSS variables. 5-level background system. **Auth overlay styles**: `.auth-overlay`, `.auth-card`, `.auth-error`, `.auth-actions` with backdrop blur and centered card layout.

### `static/js/` — 14 Modules (1998 lines total)

All vanilla JS, no framework. Global functions called from HTML `onclick`/`oninput` handlers.

| Module | Lines | Responsibility |
|--------|-------|----------------|
| `api.js` | 105 | `authFetch()`, `toast()`, `getApiKey()`/`setApiKey()` (localStorage), `authHeaders()`, `showAuthOverlay()`/`hideAuthOverlay()`, `saveApiKeyFromLogin()`, `clearApiKey()`, date/size formatters |
| `state.js` | 13 | Global state: `window._currentProjectId`, `_currentNodeId`, etc.; `wsClient` for dedup connectWs |
| `graph.js` | 744 | Cytoscape.js init, `nodeColor()` (6-status priority), `cornerIcon()` (SVG), context menu, export PNG, node panel rendering; uses `authFetch` for position saves, screenshot uploads, graph loads; 401 triggers `showAuthOverlay()` |
| `projects.js` | 358 | Sidebar rendering, project CRUD, project manager modal with bulk select/delete; all fetches use `authFetch`; 401 triggers auth overlay |
| `nodes.js` | 82 | `editNode()`, `cancelEdit()` — inline node editing |
| `edges.js` | 5 | Edge label update stub |
| `credentials.js` | 155 | Credential panel, CRUD cards, secret toggle |
| `evidence.js` | 76 | `uploadEvidence()`, `downloadEvidence()` |
| `analysis.js` | 216 | Analysis modal: stat cards, attack paths, next steps, health checks, AI hint |
| `timeline.js` | 82 | `switchView('graph'|'timeline')`, `selectTimelineNode()` |
| `search.js` | 88 | `toggleSearch()`, `onSearchInput()` debounced, `jumpToSearchResult()` cross-project |
| `import.js` | 84 | `showImportModal()`, `previewImport()`, bulk JSON import |
| `daily_report.js` | 59 | Daily report modal: load, copy, download |
| `ws.js` | 36 | `connectWs()` — appends `?api_key=` to WS URL; deduplicates via `wsClient` state; reconnects only if key is set |

**Key interactions:**
- `api.js` provides `authFetch()` — all modules use it for authenticated API calls (injects `X-API-Key` header)
- On 401, `authFetch` → `showAuthOverlay()` prompts user for API key
- `ws.js` broadcasts → `graph.js:loadGraph()` reloads the Cytoscape graph
- `projects.js:selectProject()` sets `window._currentProjectId` → triggers graph load + sidebar highlight
- `graph.js:nodeColor()` priority: `dead → objective → vuln → user_info → process → neutral`

## Data Flow

```
CLI (main.rs) → HTTP client (with x-api-key header) → Server (lib.rs)
                                    ↓
                          api_key_auth_middleware
                                    ↓
                                routes/* → db.rs → SQLite
                                    ↓
                          AppError → structured ApiResponse JSON
                                    ↓
                          WebSocket broadcast → Frontend (ws.js → graph.js)
                                    ↓
                          Agent (agent/loop.rs) → LLM (agent/llm.rs) + Tools (agent/tools.rs) → db.rs
```

## Dependencies Between Modules

- **routes/** depends on: `db.rs`, `types.rs`, `lib.rs` (AppState, error helpers, ID resolution)
- **agent/** depends on: `db.rs`, `types.rs`, `lib.rs` (deepseek_chat, AppState)
- **lib.rs** depends on: `db.rs`, `types.rs`, all `routes/*`
- **types.rs** has no internal dependencies (leaf module)
- **Frontend** communicates with backend only via HTTP API (`api.js:authFetch`) and WebSocket (`ws.js`)

## Notes

- Analysis types (`AttackPath`, `CheckResult`, `AttackAnalysis`, etc.) are defined in `db.rs`, not `types.rs` — inconsistent with the "single source of truth" pattern
- `NodeType::Custom(String)` uses `#[serde(untagged)]` — specific variants MUST come before Custom in enum definition
- Row deserializers in `db.rs` use hardcoded column indices — SELECT reordering without updating deserializers is a silent bug
- Evidence storage: files at `data/<pid>/evidence/<nid>/<uuid>.<ext>`, DB holds metadata only, deleting DB record does NOT delete file
- `AppError::from_db()` maps SQLite extended codes to HTTP status: 2067→CONFLICT (unique), 1555→CONFLICT (PK), 787→CONFLICT (FK), others→500
- API key auth: `Config.api_key` set via `--api-key`, `THESEUS_API_KEY` env, or `theseus.toml`; when unset, server is open (no auth)
- Integration tests (`#[cfg(test)]`) in routes spawn real axum servers on `127.0.0.1:0` with in-memory SQLite; 9 route modules have tests
