diff --git a/.gitignore b/.gitignore index c0f9e21e..4aa18197 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ dist/ src-tauri/target/ .env *.local +.secrets +secrets.yml +secrets.yaml diff --git a/PLAN.md b/PLAN.md index 4a8941c5..e69de29b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,429 +0,0 @@ -# TFTSR — IT Triage & Root-Cause Analysis Desktop Application - -## Implementation Plan - -### Overview - -TFTSR is a **desktop-first, offline-capable** application that helps IT teams -perform structured incident triage using the *5-Whys* methodology, backed by -pluggable AI providers (Ollama local, OpenAI, Anthropic, Mistral, Gemini). -It automates PII redaction, guides engineers through root-cause analysis, and -produces post-mortem documents (Markdown / PDF / DOCX). - ---- - -## Architecture Decisions - -| Area | Choice | Rationale | -|------|--------|-----------| -| Desktop framework | **Tauri 2.x** | Small binary, native webview, Rust backend for security | -| Frontend framework | **React 18** | Large ecosystem, component model fits wizard-style UX | -| State management | **Zustand** | Minimal boilerplate, TypeScript-friendly, no context nesting | -| Local database | **SQLCipher** (via `rusqlite` + `bundled-sqlcipher`) | Encrypted SQLite — secrets and PII at rest | -| Secret storage | **Tauri Stronghold** | OS-keychain-grade encrypted vault for API keys | -| AI providers | Ollama (local), OpenAI, Anthropic, Mistral, Gemini | User choice; local-first with cloud fallback | -| Unit tests (frontend) | **Vitest** | Fast, Vite-native, first-class TS support | -| E2E tests | **WebdriverIO + tauri-driver** | Official Tauri E2E path, cross-platform | -| CI/CD | **Woodpecker CI** (Gogs at `172.0.0.29:3000`) | Self-hosted, Docker-native, YAML pipelines | -| Bundling | Vite 6 | Dev server + production build, used by Tauri CLI | - ---- - -## Directory Structure - -``` -tftsr/ -├── .woodpecker/ -│ ├── test.yml # lint + unit tests on push / PR -│ └── release.yml # multi-platform build on tag -├── cli/ -│ ├── package.json -│ └── src/ -│ └── main.ts # minimal CLI entry point -├── src/ # React frontend -│ ├── assets/ -│ ├── components/ -│ │ ├── common/ # Button, Card, Modal, DropZone … -│ │ ├── dashboard/ # IssueList, StatsCards -│ │ ├── triage/ # WhyStep, ChatBubble, ProgressBar -│ │ ├── rca/ # DocEditor, ExportBar -│ │ ├── settings/ # ProviderForm, ThemeToggle -│ │ └── pii/ # PiiHighlighter, RedactionPreview -│ ├── hooks/ # useInvoke, useListener, useTheme … -│ ├── lib/ -│ │ ├── tauriCommands.ts # typed invoke wrappers & TS types -│ │ └── utils.ts # date formatting, debounce, etc. -│ ├── pages/ -│ │ ├── DashboardPage.tsx -│ │ ├── NewIssuePage.tsx -│ │ ├── TriagePage.tsx -│ │ ├── RcaPage.tsx -│ │ ├── LogViewerPage.tsx -│ │ └── SettingsPage.tsx -│ ├── stores/ -│ │ ├── sessionStore.ts # current triage session state -│ │ └── settingsStore.ts # theme, providers, preferences -│ ├── App.tsx -│ └── main.tsx -├── src-tauri/ -│ ├── Cargo.toml -│ ├── tauri.conf.json -│ ├── capabilities/ -│ │ └── default.json -│ ├── icons/ -│ ├── src/ -│ │ ├── main.rs # Tauri entry point -│ │ ├── db.rs # SQLCipher connection & migrations -│ │ ├── commands/ # IPC command modules -│ │ │ ├── mod.rs -│ │ │ ├── issues.rs -│ │ │ ├── triage.rs -│ │ │ ├── logs.rs -│ │ │ ├── pii.rs -│ │ │ ├── rca.rs -│ │ │ ├── ai.rs -│ │ │ └── settings.rs -│ │ ├── ai/ # AI provider abstractions -│ │ │ ├── mod.rs -│ │ │ ├── ollama.rs -│ │ │ ├── openai_compat.rs -│ │ │ └── prompt_templates.rs -│ │ ├── pii/ # PII detection engine -│ │ │ ├── mod.rs -│ │ │ └── patterns.rs -│ │ └── export/ # Document export -│ │ ├── mod.rs -│ │ ├── markdown.rs -│ │ ├── pdf.rs -│ │ └── docx.rs -│ └── migrations/ -│ └── 001_init.sql -├── tests/ -│ ├── unit/ -│ │ ├── setup.ts -│ │ ├── pii.test.ts -│ │ ├── sessionStore.test.ts -│ │ └── settingsStore.test.ts -│ └── e2e/ -│ ├── wdio.conf.ts -│ ├── helpers/ -│ │ └── app.ts -│ └── specs/ -│ ├── onboarding.spec.ts -│ ├── log-upload.spec.ts -│ ├── triage-flow.spec.ts -│ └── rca-export.spec.ts -├── package.json -├── tsconfig.json -├── vite.config.ts -└── PLAN.md # ← this file -``` - ---- - -## Database Schema (SQLCipher) - -All tables live in a single encrypted `tftsr.db` file under the Tauri -app-data directory. - -### 1. `issues` -```sql -CREATE TABLE issues ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - domain TEXT NOT NULL CHECK(domain IN - ('linux','windows','network','k8s','db','virt','hw','obs')), - status TEXT NOT NULL DEFAULT 'open' - CHECK(status IN ('open','triaging','resolved','closed')), - severity TEXT CHECK(severity IN ('p1','p2','p3','p4')), - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL -); -``` - -### 2. `triage_messages` -```sql -CREATE TABLE triage_messages ( - id TEXT PRIMARY KEY, - issue_id TEXT NOT NULL REFERENCES issues(id), - role TEXT NOT NULL CHECK(role IN ('user','assistant','system')), - content TEXT NOT NULL, - why_level INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL -); -CREATE INDEX idx_triage_msg_issue ON triage_messages(issue_id); -``` - -### 3. `log_files` -```sql -CREATE TABLE log_files ( - id TEXT PRIMARY KEY, - issue_id TEXT NOT NULL REFERENCES issues(id), - filename TEXT NOT NULL, - content TEXT NOT NULL, - mime_type TEXT, - size_bytes INTEGER, - created_at INTEGER NOT NULL -); -``` - -### 4. `pii_spans` -```sql -CREATE TABLE pii_spans ( - id TEXT PRIMARY KEY, - log_file_id TEXT NOT NULL REFERENCES log_files(id), - pii_type TEXT NOT NULL, - start_pos INTEGER NOT NULL, - end_pos INTEGER NOT NULL, - original TEXT NOT NULL, - replacement TEXT NOT NULL -); -``` - -### 5. `rca_documents` -```sql -CREATE TABLE rca_documents ( - id TEXT PRIMARY KEY, - issue_id TEXT NOT NULL REFERENCES issues(id) UNIQUE, - content TEXT NOT NULL DEFAULT '', - format TEXT NOT NULL DEFAULT 'markdown', - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL -); -``` - -### 6. `ai_providers` -```sql -CREATE TABLE ai_providers ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - api_url TEXT NOT NULL, - model TEXT NOT NULL, - created_at INTEGER NOT NULL -); -``` - -### 7. `settings` -```sql -CREATE TABLE settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL -); -``` - -### 8. `export_history` -```sql -CREATE TABLE export_history ( - id TEXT PRIMARY KEY, - issue_id TEXT NOT NULL REFERENCES issues(id), - format TEXT NOT NULL CHECK(format IN ('md','pdf','docx')), - file_path TEXT NOT NULL, - created_at INTEGER NOT NULL -); -``` - ---- - -## IPC Command Interface - -All frontend ↔ backend communication goes through Tauri's `invoke()`. - -### Issue commands -| Command | Payload | Returns | -|---------|---------|---------| -| `create_issue` | `{ title, domain, severity }` | `Issue` | -| `list_issues` | `{ status?, domain? }` | `Issue[]` | -| `get_issue` | `{ id }` | `Issue` | -| `update_issue` | `{ id, title?, status?, severity? }` | `Issue` | -| `delete_issue` | `{ id }` | `void` | - -### Triage commands -| Command | Payload | Returns | -|---------|---------|---------| -| `send_triage_message` | `{ issueId, content, whyLevel }` | `TriageMessage` (assistant reply) | -| `get_triage_history` | `{ issueId }` | `TriageMessage[]` | -| `set_why_level` | `{ issueId, level }` | `void` | - -### Log commands -| Command | Payload | Returns | -|---------|---------|---------| -| `upload_log` | `{ issueId, filename, content }` | `LogFile` | -| `list_logs` | `{ issueId }` | `LogFile[]` | -| `delete_log` | `{ id }` | `void` | - -### PII commands -| Command | Payload | Returns | -|---------|---------|---------| -| `detect_pii` | `{ logFileId }` | `PiiDetectionResult` | -| `apply_redactions` | `{ logFileId, spanIds }` | `string` (redacted text) | - -### RCA / Export commands -| Command | Payload | Returns | -|---------|---------|---------| -| `generate_rca` | `{ issueId }` | `RcaDocument` | -| `update_rca` | `{ id, content }` | `RcaDocument` | -| `export_document` | `{ issueId, format }` | `string` (file path) | - -### AI / Settings commands -| Command | Payload | Returns | -|---------|---------|---------| -| `test_provider` | `{ name, apiUrl, apiKey?, model }` | `{ ok, message }` | -| `save_provider` | `{ provider }` | `void` | -| `get_settings` | `{}` | `Settings` | -| `update_settings` | `{ key, value }` | `void` | - ---- - -## CI/CD Approach - -### Infrastructure -- **Git server**: Gogs at `http://172.0.0.29:3000` -- **CI runner**: Woodpecker CI with Docker executor -- **Artifacts**: Uploaded to Gogs releases via API - -### Pipelines - -| Pipeline | Trigger | Steps | -|----------|---------|-------| -| `.woodpecker/test.yml` | push, PR | `rustfmt` check → Clippy → Rust tests → TS typecheck → Vitest → coverage (main only) | -| `.woodpecker/release.yml` | `v*` tag | Build linux-amd64 → Build linux-arm64 → Upload to Gogs release | - ---- - -## Security Implementation - -1. **Database encryption** — SQLCipher with a key derived from Tauri Stronghold. -2. **API key storage** — Stronghold vault, never stored in plaintext. -3. **PII redaction** — Regex + heuristic engine runs before any text leaves the device. -4. **CSP** — Strict Content-Security-Policy in `tauri.conf.json`; only allowlisted AI API origins. -5. **Least-privilege capabilities** — `capabilities/default.json` grants only required Tauri permissions. -6. **No remote code** — All assets bundled; no CDN scripts. - ---- - -## Testing Strategy - -| Layer | Tool | Location | What it covers | -|-------|------|----------|----------------| -| Rust unit | `cargo test` | `src-tauri/src/**` | DB operations, PII regex, AI prompt building | -| Frontend unit | Vitest | `tests/unit/` | Stores, command wrappers, component logic | -| E2E | WebdriverIO + tauri-driver | `tests/e2e/` | Full user flows: onboarding, triage, export | -| Lint | `rustfmt` + Clippy + `tsc --noEmit` | CI | Code style, type safety | - ---- - -## Implementation Phases - -### Phase 1 — Project Scaffold & CI ✅ COMPLETE -- [x] Initialise repo with Tauri 2.x + React 18 + Vite -- [x] Configure `tauri.conf.json` and capabilities -- [x] Set up Woodpecker CI pipelines (`test.yml`, `release.yml`) -- [x] Write Vitest setup and mock harness -- [x] Write initial unit tests (PII, sessionStore, settingsStore) — 13/13 passing -- [x] Write E2E scaffolding (wdio config, helpers, skeleton specs) -- [x] Create CLI stub (`cli/`) -- [x] Push to Gogs at http://172.0.0.29:3000/sarman/tftsr-devops_investigation -- [x] Write README.md -- [x] Deploy Woodpecker CI v0.15.4 (server + agent + nginx proxy) -- [ ] **BLOCKED**: Verify CI green on push (Woodpecker hook auth issue — see below) - -### Phase 2 — Database & Migrations ✅ COMPLETE -- [x] Integrate `rusqlite` + `bundled-sqlcipher` -- [x] Write migrations (10 tables: issues, log_files, pii_spans, ai_conversations, ai_messages, resolution_steps, documents, audit_log, settings, integration_publishes) -- [x] Implement migration runner in `db/migrations.rs` -- [x] DB models with all required types - -### Phase 3 — Stronghold Integration ✅ COMPLETE (scaffold) -- [x] `tauri-plugin-stronghold` registered in `lib.rs` -- [x] Password derivation function configured -- [ ] Full key lifecycle tests (deferred to Phase 3 proper) - -### Phase 4 — Issue CRUD ✅ COMPLETE -- [x] All issue CRUD commands: create, get, list, update, delete, search -- [x] 5-Whys tracking: add_five_why, update_five_why -- [x] Timeline events: add_timeline_event -- [x] Dashboard, NewIssue, History pages - -### Phase 5 — Log Ingestion & PII Detection ✅ COMPLETE -- [x] `upload_log_file`, `detect_pii`, `apply_redactions` commands -- [x] PII engine: 11 regex patterns (IPv4, IPv6, email, phone, SSN, CC, MAC, bearer, password, API key, URL) -- [x] PiiDiffViewer component -- [x] LogUpload page - -### Phase 6 — AI Provider Abstraction ✅ COMPLETE -- [x] OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama providers -- [x] `analyze_logs`, `chat_message`, `list_providers` IPC commands -- [x] Settings/AIProviders page -- [x] 8 IT domain system prompts - -### Phase 7 — 5-Whys Triage Engine ✅ COMPLETE -- [x] Triage page with ChatWindow -- [x] TriageProgress component (5-step indicator) -- [x] Auto-detection of why level from AI responses -- [x] Session store with message persistence - -### Phase 8 — RCA & Post-Mortem Generation ✅ COMPLETE -- [x] `generate_rca`, `generate_postmortem` commands -- [x] RCA and post-mortem Markdown templates -- [x] DocEditor component with export (MD, PDF) -- [x] RCA and Postmortem pages - -### Phase 9 — Document Export ✅ COMPLETE (MD + PDF) -- [x] Markdown export -- [x] PDF export via `printpdf` -- [ ] DOCX export (not yet implemented — docx-rs dep removed for simplicity) - -### Phase 10 — Polish & Settings ✅ COMPLETE -- [x] Dark/light theme via Tailwind + CSS variables -- [x] Ollama settings page with hardware detection + model management -- [x] Security page with audit log -- [x] Integrations page (v0.2 stubs) - -### Phase 11 — Woodpecker CI Integration 🔴 IN PROGRESS / BLOCKED -**What works:** -- [x] Woodpecker CI v0.15.4 deployed at http://172.0.0.29:8084 -- [x] Gogs SSRF bypass: webhook URL uses 172.0.0.29:8084 (not container IP) -- [x] Webhook delivery confirmed: Gogs IS sending webhooks to Woodpecker -- [x] Custom login proxy at http://172.0.0.29:8085 with correct `username=` form field - -**What's blocked:** -- [ ] Woodpecker hook authentication: 400 "failure to parse token" when Gogs delivers webhook - - Root cause: `token.ParseRequest()` in Woodpecker 0.15.4 doesn't read `?token=` URL param - - With `?access_token=`: gets 404 (JWT validated, but parsePushHook returns nil unexpectedly) - - Web UI login via http://172.0.0.29:8085/login: custom form works but SPA still intercepts -- [ ] Repo activation via Woodpecker API returns 401 (permission/token issue) -- [ ] CI build not yet triggering on push - -**Next steps:** -1. Fix Woodpecker token storage: ensure Woodpecker DB has the actual bearer token - (Gogs token `sha1` from CREATE response = actual token; DB `sha1` column = hash of it) -2. Delete old "woodpecker" Gogs tokens via PostgreSQL, then re-login to Woodpecker -3. Fix the `?access_token=` 404 — investigate parsePushHook nil return -4. Alternative: upgrade to Woodpecker 2.x and add OAuth2 to Gogs (or migrate to Gitea) - -### Phase 12 — Release Package 🔲 PENDING -- [ ] Tag v0.1.0-alpha -- [ ] Verify Woodpecker builds Linux amd64 + arm64 -- [ ] Verify artifacts upload to Gogs release -- [ ] Smoke-test installed packages - ---- - -## Known Issues & Gotchas - -### Gogs Token Authentication -- The `sha1` in the Gogs CREATE token API response IS the actual bearer token -- Gogs stores `sha1(token)` and `sha256(token)` in the DB — these are HASHES, not the token itself -- Known working tokens: `[REDACTED-ROTATED]` (woodpecker-setup) - -### Woodpecker CI + Gogs v0.15.4 Compatibility -- The SPA form login uses `login=` field but Gogs backend reads `username=` -- Workaround: nginx proxy at :8085 serves custom HTML login page -- The webhook `?token=` URL param is NOT read by Woodpecker's `token.ParseRequest()` -- Use `?access_token=` instead (JWT must be HS256 signed with `repo_hash` as key) -- Gogs 0.14 has no OAuth2 provider support — blocks upgrade to Woodpecker 2.x - -### Rust/DB Type Notes -- IssueDetail is NESTED: `{ issue: Issue, log_files, resolution_steps, conversations }` -- DB uses TEXT timestamps for created_at/updated_at (not INTEGER) -- All commands use the `and_then` pattern with rusqlite to avoid lifetime issues diff --git a/src-tauri/target/.rustc_info.json b/src-tauri/target/.rustc_info.json deleted file mode 100644 index 99921375..00000000 --- a/src-tauri/target/.rustc_info.json +++ /dev/null @@ -1 +0,0 @@ -{"rustc_fingerprint":3935335233893156513,"outputs":{"850482172296875736":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: aarch64-unknown-linux-gnu\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""},"4592665983954943194":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/sarman/.rustup/toolchains/stable-aarch64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"neon\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file