# 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
<!-- Simple Phase 1 wiki editor -->
<div id="wikiEditor">
  <textarea id="wikiContent" placeholder="# Сторінка wiki..."></textarea>
  <div id="wikiPreview" class="markdown-preview"></div>
</div>
```

---

## 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
