# Sofiia CTO Agent — Gaps & Recovery Plan (E) > Generated: 2026-02-26 | P0 = блокуюче | P1 = критичне для vNext | P2 = покращення --- ## Критичне резюме **Що вже готово і може йти в UI:** Chat, Voice, Projects CRUD, File upload, Sessions, Dialog Map tree, Ops actions, Node health. **Що не готово і блокує vNext:** Tasks/Kanban, Meetings, Dialog Map canvas + Postgres schema, Doc versions, CTO Repo/Ops flow, Supervisor через BFF, Semantic search. --- ## Таблиця прогалин з пріоритетами | # | Gap | Пріоритет | Складність | Блокує | |---|-----|-----------|-----------|--------| | G1 | `dialog_nodes`/`dialog_edges` Postgres tables + API | P0 | Medium | Dialog Map vNext | | G2 | `tasks` table + CRUD API + Kanban UI | P0 | Medium | Projects Board | | G3 | `meetings` table + CRUD API | P0 | Medium | Projects Meetings tab | | G4 | Supervisor не проксюється через BFF | P0 | Low | CTO workflow access | | G5 | `docs_versions` table + API | P1 | Low | Doc history/rollback | | G6 | `entity_links` table + API | P1 | Low | Cross-entity linking | | G7 | `repo_changesets` + `repo_patches` + PR flow | P1 | High | CTO code workflow | | G8 | `ops_runs` job system (not one-shot) | P1 | Medium | CTO ops audit trail | | G9 | Semantic search (Qdrant/Meilisearch) | P1 | Medium | Doc/Project search | | G10 | NATS `attachment.created` on upload | P1 | Low | Parser pipeline hook | | G11 | `DELETE` endpoints (projects/docs) | P1 | Low | CRUD completeness | | G12 | Real-time WS events for map/tasks | P1 | Medium | Live UI updates | | G13 | E2EE / confidential mode | P2 | Very High | Privacy | | G14 | 2-step Plan → Apply for dangerous actions | P2 | High | Safe ops flow | | G15 | `agent_id="l"` vs `"sofiia"` inconsistency | P1 | Low | Config correctness | | G16 | `dialog_views` saved views | P2 | Low | UX | | G17 | NODA3 integration | P2 | Medium | AI/ML workstation | | G18 | Meilisearch deployment | P2 | Low | Full-text search | | G19 | Privacy Gate middleware (Router) | P2 | High | Confidential mode | | G20 | Wiki Markdown editor UI | P2 | Medium | Docs/Wiki experience | | G21 | `doc_index_state` table + reindex jobs | P2 | Low | AI doc indexing | | G22 | Meeting reminders (push/WS) | P2 | Medium | Meetings UX | | G23 | `DELETE /api/nodes/{id}` | P2 | Low | Node management | | G24 | S3/MinIO для file storage | P2 | High | Scale (replace volume) | --- ## P0 — Блокуючі прогалини (потрібні для vNext) ### G1: Dialog Map — Postgres schema + API **Що зроблено:** SQLite tree via `parent_msg_id`. Works for conversation branching. **Чого не вистачає:** - Postgres tables: `dialog_nodes`, `dialog_edges`, `dialog_views` - API: `GET /api/projects/{id}/dialog-map`, `POST /api/links` - WS event: `dialog_map.updated` - Auto-edge creation from NATS events **Recovery plan:** ```sql -- Step 1: Add to sofiia-console db.py (SQLite first, Postgres later) CREATE TABLE IF NOT EXISTS dialog_nodes ( node_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, node_type TEXT NOT NULL CHECK(node_type IN ('message','task','doc','meeting','agent_run','decision','goal')), ref_id TEXT NOT NULL, -- FK to actual entity title TEXT DEFAULT '', created_at TEXT NOT NULL, created_by TEXT DEFAULT 'system' ); CREATE TABLE IF NOT EXISTS dialog_edges ( edge_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, from_node_id TEXT NOT NULL REFERENCES dialog_nodes(node_id), to_node_id TEXT NOT NULL REFERENCES dialog_nodes(node_id), edge_type TEXT NOT NULL CHECK(edge_type IN ('references','resolves','derives_task','updates_doc','schedules','summarizes')), created_at TEXT NOT NULL, props TEXT DEFAULT '{}' -- JSON ); CREATE TABLE IF NOT EXISTS dialog_views ( view_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, name TEXT NOT NULL, filters TEXT DEFAULT '{}', layout TEXT DEFAULT '{}' ); ``` ```python # Step 2: New endpoint in docs_router.py @router.get("/api/projects/{project_id}/dialog-map") async def get_project_dialog_map(project_id: str): nodes = await db.get_dialog_nodes(project_id) edges = await db.get_dialog_edges(project_id) return {"nodes": nodes, "edges": edges} @router.post("/api/links") async def create_link(body: LinkCreate): # Creates dialog_edge between two entities ... ``` **Оцінка:** 4–6 годин роботи. --- ### G2: Tasks + Kanban **Що зроблено:** Немає. **Recovery plan:** ```sql CREATE TABLE IF NOT EXISTS tasks ( task_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(project_id), title TEXT NOT NULL, description TEXT DEFAULT '', status TEXT DEFAULT 'backlog' CHECK(status IN ('backlog','in_progress','review','done')), priority TEXT DEFAULT 'medium', assignee_id TEXT DEFAULT '', labels TEXT DEFAULT '[]', -- JSON due_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, msg_id TEXT -- Optional: link to originating message ); ``` - API: `GET/POST /api/projects/{id}/tasks`, `PATCH /api/tasks/{id}`, `DELETE /api/tasks/{id}` - UI: Kanban board з drag-drop (можна почати з простим list + status buttons) - Dialog Map auto-edge: `POST /api/links` after task creation **Оцінка:** 1–2 дні (backend + basic UI). --- ### G3: Meetings **Recovery plan:** ```sql CREATE TABLE IF NOT EXISTS meetings ( meeting_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(project_id), title TEXT NOT NULL, starts_at TEXT NOT NULL, duration_min INTEGER DEFAULT 60, attendees TEXT DEFAULT '[]', -- JSON location TEXT DEFAULT '', agenda TEXT DEFAULT '', created_at TEXT NOT NULL ); ``` - API: `GET/POST /api/projects/{id}/meetings`, `PATCH /api/meetings/{id}` - UI: simple form (title, date/time, duration, attendees) - Reminders: Phase 2 (WS push) **Оцінка:** 1 день. --- ### G4: Supervisor → BFF proxy **Що зроблено:** Supervisor API exists at `http://sofiia-supervisor:8080` (або port 9400). **Recovery plan:** ```python # Add to services/sofiia-console/app/main.py: SUPERVISOR_URL = os.getenv("SUPERVISOR_URL", "http://sofiia-supervisor:8080") @app.post("/api/supervisor/runs") async def run_supervisor_graph(body: dict, _auth: str = Depends(require_auth)): async with httpx.AsyncClient() as c: resp = await c.post(f"{SUPERVISOR_URL}/v1/graphs/{body['graph']}/runs", json=body, timeout=60) return resp.json() @app.get("/api/supervisor/runs/{run_id}") async def get_supervisor_run(run_id: str, _auth: str = Depends(require_auth)): async with httpx.AsyncClient() as c: resp = await c.get(f"{SUPERVISOR_URL}/v1/runs/{run_id}", timeout=10) return resp.json() ``` **Оцінка:** 30 хвилин. --- ## P1 — Критичні для vNext ### G5: Doc versions ```sql CREATE TABLE IF NOT EXISTS doc_versions ( version_id TEXT PRIMARY KEY, doc_id TEXT NOT NULL REFERENCES documents(doc_id), content TEXT NOT NULL, -- full text author_id TEXT DEFAULT 'system', created_at TEXT NOT NULL ); ``` ```python # New endpoints in docs_router.py: # GET /api/projects/{pid}/documents/{did}/versions # POST /api/projects/{pid}/documents/{did}/restore ``` **Оцінка:** 2 години. --- ### G7: Repo Changesets (CTO Code Flow) Це найскладніша частина. **Рекомендація:** почати з mock endpoints, потім реалізувати реальну логіку. **Mock endpoint (30 хв):** ```python @app.post("/api/repo/changesets") async def create_changeset_mock(body: dict, _auth=Depends(require_auth)): # Mock: store in SQLite, return changeset_id cs_id = str(uuid.uuid4()) # await db.save_changeset(cs_id, body) return {"changeset_id": cs_id, "status": "draft", "mock": True} ``` **Реальна реалізація (2–3 дні):** ```sql CREATE TABLE repo_changesets ( cs_id TEXT PRIMARY KEY, project_id TEXT, repo TEXT NOT NULL, -- e.g., "github.com/IvanTytar/microdao-daarion" base_ref TEXT NOT NULL, -- branch/commit intent TEXT NOT NULL, risk_level TEXT DEFAULT 'low', status TEXT DEFAULT 'draft', created_by TEXT, created_at TEXT NOT NULL ); CREATE TABLE repo_patches ( patch_id TEXT PRIMARY KEY, cs_id TEXT NOT NULL REFERENCES repo_changesets(cs_id), file_path TEXT NOT NULL, patch_text TEXT NOT NULL, -- unified diff created_at TEXT NOT NULL ); CREATE TABLE pull_requests ( pr_id TEXT PRIMARY KEY, cs_id TEXT NOT NULL REFERENCES repo_changesets(cs_id), provider TEXT DEFAULT 'github', -- github/gitlab/gitea pr_url TEXT, pr_number INTEGER, status TEXT DEFAULT 'draft', created_at TEXT NOT NULL ); ``` --- ### G8: Ops Runs (Job System) Поточний `/api/ops/run` — one-shot dispatch. Потрібен job tracking. ```sql CREATE TABLE ops_runs ( run_id TEXT PRIMARY KEY, project_id TEXT, node_id TEXT NOT NULL, -- noda1/noda2 action TEXT NOT NULL, -- з allowlist params TEXT DEFAULT '{}', -- JSON dry_run INTEGER DEFAULT 1, status TEXT DEFAULT 'pending', -- pending/running/success/failed result TEXT DEFAULT '', started_at TEXT, finished_at TEXT, created_by TEXT ); ``` **API:** - `POST /api/ops/runs` (створити job, dry_run=true за замовч.) - `GET /api/ops/runs/{id}` (статус) - `GET /api/ops/runs?project_id=&limit=20` (список) **Оцінка:** 4 години (backend) + 2 год (UI list). --- ### G10: NATS attachment.created Одна зміна в `docs_router.py`: ```python # After successful file save: try: import nats nc = await nats.connect(NATS_URL) await nc.publish(f"attachment.created.{mime_category}", json.dumps({"file_id": file_id, "doc_id": doc_id, ...}).encode()) await nc.close() except Exception: pass # best-effort ``` **Оцінка:** 1 година. --- ### G15: agent_id "l" vs "sofiia" У `services/router/router-config.yml` для NODA2: ```yaml # Check if there's "l:" entry that should be "sofiia:" ``` **Action:** знайти і замінити `"l"` → `"sofiia"` у router-config відповідної ноди. **Оцінка:** 15 хвилин. --- ## P2 — Покращення ### G13: E2EE (confidential mode) **Складність:** Дуже висока. Потребує: 1. Client-side key generation (WebCrypto API) 2. Server-side: store only ciphertext + key_id 3. Router Privacy Gate middleware 4. Dialog Map: тільки user-created edges (не semantic auto-edges) 5. Search: тільки metadata, не plaintext **Рекомендація:** Не реалізовувати до завершення Projects + Dialog Map. Спочатку `mode=public` тільки. --- ### G20: Wiki Markdown Editor Потрібна бібліотека (CodeMirror / Monaco / Tiptap). Для Phase 1 — textarea з preview. ```html
``` --- ## Quick Wins (до 2 годин кожен) | # | Quick Win | Час | Цінність | |---|-----------|-----|---------| | QW1 | `DELETE /api/projects/{id}` | 15 хв | CRUD completeness | | QW2 | `DELETE /api/projects/{id}/documents/{did}` | 15 хв | CRUD completeness | | QW3 | BFF proxy до Supervisor (G4) | 30 хв | CTO workflow access | | QW4 | Mock `/api/repo/changesets` | 30 хв | UI CTO panel development | | QW5 | Mock `/api/ops/runs` | 30 хв | UI CTO panel development | | QW6 | `docs_versions` table + API (G5) | 2 год | Doc history | | QW7 | `USE_EMBEDDINGS=true` + Qdrant ingest | 1 год | Semantic search | | QW8 | `agent_id "l"` → `"sofiia"` fix | 15 хв | Config consistency | | QW9 | NATS `attachment.created` on upload | 1 год | Parser pipeline | | QW10 | WS `dialog_map.updated` basic event | 1 год | Live map refresh | --- ## Повний план відновлення (поетапно) ### Тиждень 1: Stabilize & Quick Wins ``` Day 1–2: - QW1, QW2, QW3, QW8 (CRUD + Supervisor proxy + agent_id fix) - Деплой на NODA2, verify через http://localhost:8002 Day 3–4: - G2: tasks table + basic API + simple list UI - G3: meetings table + basic form UI Day 5: - G5: docs_versions + API - G10: NATS attachment.created - QW4, QW5: mock changeset/ops_run endpoints for UI ``` ### Тиждень 2: Dialog Map + CTO Panel ``` Day 1–2: - G1: dialog_nodes/edges tables + API - WS event: dialog_map.updated Day 3–4: - UI: Dialog Map canvas (D3 tree → force graph) - Entity links UI (drag edge between nodes) Day 5: - G8: ops_runs job system - UI: CTO Ops panel (list + status) ``` ### Тиждень 3: Advanced Features ``` - G7: Repo changesets (real implementation) - G9: USE_EMBEDDINGS=true + semantic search - G12: Full real-time WS events (tasks, docs, meetings) - Kanban drag-drop UI - Doc versions diff viewer ``` ### Тиждень 4+: Scale & Polish ``` - G14: 2-step Plan → Apply - G20: Wiki Markdown editor - G22: Meeting reminders - G24: S3/MinIO for file storage - G13: E2EE (only when everything else is stable) ``` --- ## 5 Найбільш Критичних Прогалин 1. **`dialog_nodes/edges` + project-level Dialog Map API** — без цього vNext граф неможливий 2. **Tasks/Kanban** — Projects без задач = тільки файлосховище 3. **Meetings** — Projects без зустрічей = неповний workflow 4. **Supervisor не проксюється через BFF** — CTO не може запускати LangGraph runs з UI 5. **Repo changesets / CTO code flow** — Sofiia не може "пропонувати PR" як structured artifact --- ## 5 Найбільш Готових Частин для UI 1. **Chat + Voice** — повністю готово, production-grade (Phase 2 streaming, HA, SLO, alerts) 2. **Projects + Documents + File Upload** — CRUD, search, sessions — все є 3. **Dialog Map tree** — `GET /api/sessions/{id}/map` повертає nodes/edges 4. **Ops Actions** — risk/pressure/backlog/notion/release — все є через `/api/ops/run` 5. **Node Health Dashboard** — multi-node, SSH, WebSocket realtime — все є --- ## 3 Рекомендації "Зробити Негайно" ### 1. Зберегти контекст у Dialog Map Найпростіший спосіб не "загубити" поточний дизайн — додати `dialog_nodes/edges` tables у `db.py` прямо зараз (схема вже описана вище). Навіть якщо UI ще не готовий, дані почнуть накопичуватись від поточних повідомлень. ### 2. Proxy Supervisor через BFF 30 хвилин роботи, але це дасть Sofiia доступ до `alert_triage`, `incident_triage`, `postmortem_draft`, `release_check` прямо з UI Console — не тільки через Telegram. ### 3. Нормалізувати `agent_id` Знайти і виправити `"l"` → `"sofiia"` у конфігурації NODA2. Це унеможливить silent routing failures де Router не знаходить агента і тихо fallbacks до дефолту. --- ## Next Actions for UI Team (1–2 days) 1. **Розгорнути і протестувати** поточний стек на NODA2 — `http://localhost:8002/` вже повністю робочий 2. **Реалізувати QW1–QW5** (прості DELETE + Supervisor proxy + mock endpoints) — 2–3 год 3. **Додати `tasks` і `meetings` tables** у `db.py` та відповідні endpoints у `docs_router.py` 4. **Додати `dialog_nodes/edges`** у `db.py` (DDL вище) і endpoint `GET /api/projects/{id}/dialog-map` 5. **Тестувати** через `tests/test_sofiia_docs.py` — всі 28 тестів мають пройти 6. **Оновити** `docker-compose.node2-sofiia.yml` з `SUPERVISOR_URL` env var 7. **Перевірити** що `ops/voice_ha_smoke.sh` проходить після деплою 8. **Прочитати** `docs/architecture_inventory/` (7 файлів) для повного контексту поточного стеку 9. **Використовувати** `ops/fabric_preflight.sh` перед кожним деплоєм (preflight-first policy) 10. **Щотижня**: запускати `ops/fabric_snapshot.py --save` і commit результат — щоб мати baseline для drift detection