Files
microdao-daarion/docs/audit/gaps_and_recovery_plan.md
Apple 67225a39fa docs(platform): add policy configs, runbooks, ops scripts and platform documentation
Config policies (16 files): alert_routing, architecture_pressure, backlog,
cost_weights, data_governance, incident_escalation, incident_intelligence,
network_allowlist, nodes_registry, observability_sources, rbac_tools_matrix,
release_gate, risk_attribution, risk_policy, slo_policy, tool_limits, tools_rollout

Ops (22 files): Caddyfile, calendar compose, grafana voice dashboard,
deployments/incidents logs, runbooks for alerts/audit/backlog/incidents/sofiia/voice,
cron jobs, scripts (alert_triage, audit_cleanup, migrate_*, governance, schedule),
task_registry, voice alerts/ha/latency/policy

Docs (30+ files): HUMANIZED_STEPAN v2.7-v3 changelogs and runbooks,
NODA1/NODA2 status and setup, audit index and traces, backlog, incident,
supervisor, tools, voice, opencode, release, risk, aistalk, spacebot

Made-with: Cursor
2026-03-03 07:14:53 -08:00

478 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
...
```
**Оцінка:** 46 годин роботи.
---
### 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
**Оцінка:** 12 дні (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}
```
**Реальна реалізація (23 дні):**
```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 12:
- QW1, QW2, QW3, QW8 (CRUD + Supervisor proxy + agent_id fix)
- Деплой на NODA2, verify через http://localhost:8002
Day 34:
- 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 12:
- G1: dialog_nodes/edges tables + API
- WS event: dialog_map.updated
Day 34:
- 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 (12 days)
1. **Розгорнути і протестувати** поточний стек на NODA2 — `http://localhost:8002/` вже повністю робочий
2. **Реалізувати QW1QW5** (прості DELETE + Supervisor proxy + mock endpoints) — 23 год
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