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
254 lines
8.5 KiB
Markdown
254 lines
8.5 KiB
Markdown
# drift_analyzer_tool
|
||
|
||
**Drift Analyzer — 6-й gate у release_check**
|
||
Знаходить розбіжності між "джерелами правди" (docs/inventory/config) та фактичним станом repo.
|
||
|
||
---
|
||
|
||
## Огляд
|
||
|
||
`drift_analyzer_tool` — детерміністичний (без LLM), read-only аналізатор drift у 4 категоріях.
|
||
|
||
| Категорія | Джерело правди | Факт | Приклад drift |
|
||
|-----------|---------------|------|---------------|
|
||
| **services** | `inventory_services.csv` / `01_SERVICE_CATALOG.md` | `docker-compose*.yml` | DEPLOYED сервіс відсутній у compose |
|
||
| **openapi** | `docs/contracts/*.openapi.yaml` | FastAPI route decorators у коді | Endpoint у spec але нема в коді |
|
||
| **nats** | `inventory_nats_topics.csv` | `nc.publish/subscribe` у коді | Subject у коді не задокументований |
|
||
| **tools** | `config/tools_rollout.yml` + `rbac_tools_matrix.yml` | Handlers у `tool_manager.py` | Tool у rollout але нема handler |
|
||
|
||
---
|
||
|
||
## Використання
|
||
|
||
### Через агента (OpenCode / Telegram)
|
||
|
||
```
|
||
"Запусти drift аналіз"
|
||
"Перевір drift для категорій tools та openapi"
|
||
"Drift check перед релізом"
|
||
```
|
||
|
||
### Через execute_tool
|
||
|
||
```json
|
||
{
|
||
"action": "analyze",
|
||
"categories": ["services", "openapi", "nats", "tools"],
|
||
"timeout_sec": 25
|
||
}
|
||
```
|
||
|
||
### Через release_check (Gate 6, optional)
|
||
|
||
```json
|
||
{
|
||
"action": "start_task",
|
||
"params": {
|
||
"task_id": "release_check",
|
||
"inputs": {
|
||
"service_name": "router",
|
||
"run_drift": true,
|
||
"drift_categories": ["openapi", "tools"],
|
||
"drift_timeout_sec": 20
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Параметри
|
||
|
||
| Параметр | Тип | Обов'язковий | Опис |
|
||
|----------|-----|:---:|------|
|
||
| `action` | `"analyze"` | ✅ | Єдина дія |
|
||
| `categories` | array | — | Підмножина `["services","openapi","nats","tools"]` (default: всі) |
|
||
| `timeout_sec` | number | — | Таймаут в секундах (default: 25, max: 30) |
|
||
|
||
---
|
||
|
||
## Формат відповіді
|
||
|
||
```json
|
||
{
|
||
"pass": false,
|
||
"summary": "❌ Drift analysis FAILED. 2 error(s), 1 warning(s).",
|
||
"stats": {
|
||
"errors": 2,
|
||
"warnings": 1,
|
||
"infos": 0,
|
||
"skipped": [],
|
||
"items_checked": {
|
||
"services": 42,
|
||
"openapi": 18,
|
||
"tools": 65
|
||
},
|
||
"elapsed_ms": 1234.5,
|
||
"by_category": { "...": "..." }
|
||
},
|
||
"findings": [
|
||
{
|
||
"category": "tools",
|
||
"severity": "error",
|
||
"id": "DRIFT-TOOLS-001",
|
||
"title": "Tool 'fake_tool_x' in tools_rollout.yml but no handler in tool_manager.py",
|
||
"evidence": {
|
||
"path": "config/tools_rollout.yml",
|
||
"details": "'fake_tool_x' referenced in rollout groups but missing from KNOWN_TOOL_HANDLERS"
|
||
},
|
||
"recommended_fix": "Add handler for 'fake_tool_x' in tool_manager.py execute_tool dispatch, or remove from rollout."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Pass/Fail правило
|
||
|
||
| Умова | `pass` |
|
||
|-------|--------|
|
||
| Будь-який `severity: error` | `false` |
|
||
| Тільки `warning` / `info` | `true` |
|
||
| Категорія відсутня (skipped) | не впливає |
|
||
|
||
---
|
||
|
||
## Категорії деталі
|
||
|
||
### 1. services — Service Catalog vs docker-compose
|
||
|
||
**Джерела:**
|
||
- A: `docs/architecture_inventory/inventory_services.csv` → поле `type` (DEPLOYED/DEFINED/...)
|
||
- B: всі `docker-compose*.yml` у repo root + `infra/compose/docker-compose.yml`
|
||
|
||
**Findings:**
|
||
|
||
| ID | Severity | Умова |
|
||
|----|----------|-------|
|
||
| `DRIFT-SVC-001` | error | Сервіс `DEPLOYED` у catalog, але відсутній в compose |
|
||
| `DRIFT-SVC-002` | warning | Сервіс є в compose, але не в catalog |
|
||
|
||
**Normalization:** `my-svc` ↔ `my_svc` (dash/underscore equivalence).
|
||
|
||
---
|
||
|
||
### 2. openapi — API Spec vs Code Routes
|
||
|
||
**Джерела:**
|
||
- A: `docs/contracts/*.openapi.yaml` та будь-які `openapi*.yaml/yml/json` у repo
|
||
- B: Python файли — `@app.get(...)`, `@router.post(...)`, `.add_api_route(...)`
|
||
|
||
**Findings:**
|
||
|
||
| ID | Severity | Умова |
|
||
|----|----------|-------|
|
||
| `DRIFT-OAS-001` | error | Path у OpenAPI spec але не знайдено в коді |
|
||
| `DRIFT-OAS-002` | error | Path `/v1/*` є в коді але не описаний у spec |
|
||
| `DRIFT-OAS-003` | warning | Method mismatch для тієї самої path |
|
||
|
||
**Normalization:** trailing slash, lowercase path comparison.
|
||
**Скоп коду:** тільки `/v1/` routes перевіряються для OAS-002.
|
||
|
||
---
|
||
|
||
### 3. nats — Subject Inventory vs Code Usage
|
||
|
||
**Джерела:**
|
||
- A: `docs/architecture_inventory/inventory_nats_topics.csv` (поле `subject`)
|
||
- B: regex пошук `nc.publish(...)`, `nc.subscribe(...)`, `subject=...` у `.py` файлах
|
||
|
||
**Findings:**
|
||
|
||
| ID | Severity | Умова |
|
||
|----|----------|-------|
|
||
| `DRIFT-NATS-001` | warning | Subject використовується в коді але відсутній у inventory |
|
||
| `DRIFT-NATS-002` | info | Subject у inventory але не знайдено в коді (можливо legacy) |
|
||
|
||
**Wildcard matching:** `agent.run.{agent_id}` → `agent.run.*` → `agent.run.>`.
|
||
**Skipped:** якщо `inventory_nats_topics.csv` відсутній — категорія `skipped`, gate не падає.
|
||
|
||
---
|
||
|
||
### 4. tools — Rollout/Matrix vs Handlers
|
||
|
||
**Джерела:**
|
||
- A: `config/tools_rollout.yml` (всі tool-назви у groups, з @group expand)
|
||
- B: `config/rbac_tools_matrix.yml` (секція `tools:`)
|
||
- C: `KNOWN_TOOL_HANDLERS` у `drift_analyzer.py` (compile-time список)
|
||
- D: `agent_tools_config.effective_tools` для ролей `agent_default` і `agent_cto`
|
||
|
||
**Findings:**
|
||
|
||
| ID | Severity | Умова |
|
||
|----|----------|-------|
|
||
| `DRIFT-TOOLS-001` | error | Tool у rollout але нема handler |
|
||
| `DRIFT-TOOLS-002` | warning | Handler є але tool відсутній у RBAC matrix |
|
||
| `DRIFT-TOOLS-003` | warning | Tool у matrix але ніколи не потрапляє в effective_tools |
|
||
|
||
**Maintenance:** при додаванні нового tool handler — оновіть `KNOWN_TOOL_HANDLERS` у `drift_analyzer.py`.
|
||
|
||
---
|
||
|
||
## Безпека
|
||
|
||
- **Read-only:** не записує нічого у repo
|
||
- **Path traversal:** сканує тільки всередині `REPO_ROOT`
|
||
- **Excluded dirs:** `node_modules`, `.git`, `venv*`, `__pycache__`, `dist`, `build`, `rollback_backups`
|
||
- **File size limit:** max 256KB per file
|
||
- **File count limit:** max 300 files per category scan
|
||
- **Secret redaction:** evidence маскується `_redact_evidence()` перед поверненням
|
||
- **Governance:** проходить через `ToolGovernance.pre_call/post_call` (RBAC, limits, audit)
|
||
|
||
---
|
||
|
||
## RBAC Entitlements
|
||
|
||
| Entitlement | Хто | Що дозволяє |
|
||
|-------------|-----|-------------|
|
||
| `tools.drift.read` | `agent_cto`, `agent_oncall` | Запускати drift analyze |
|
||
| `tools.drift.gate` | `agent_cto` | Запускати drift у release gate |
|
||
|
||
---
|
||
|
||
## Limits (`config/tool_limits.yml`)
|
||
|
||
| Параметр | Значення |
|
||
|----------|----------|
|
||
| `timeout_ms` | 30 000 (30s) |
|
||
| `max_chars_in` | 5 000 |
|
||
| `max_bytes_out` | 524 288 (512KB) |
|
||
| `rate_limit_rpm` | 5 |
|
||
| `concurrency` | 1 |
|
||
|
||
---
|
||
|
||
## Оновлення `KNOWN_TOOL_HANDLERS`
|
||
|
||
Коли додається новий tool handler у `tool_manager.py`:
|
||
|
||
1. Додай tool name до `KNOWN_TOOL_HANDLERS` у `drift_analyzer.py`
|
||
2. Додай tool до `config/tools_rollout.yml` (потрібна роль)
|
||
3. Додай tool до `config/rbac_tools_matrix.yml` (actions + entitlements)
|
||
4. Запусти `pytest tests/test_drift_analyzer.py::TestToolsDrift` щоб перевірити
|
||
|
||
```python
|
||
# drift_analyzer.py
|
||
KNOWN_TOOL_HANDLERS: FrozenSet[str] = frozenset({
|
||
...,
|
||
"my_new_tool", # add here
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## Файли
|
||
|
||
| Файл | Призначення |
|
||
|------|-------------|
|
||
| `services/router/drift_analyzer.py` | Вся логіка аналізу (4 категорії) |
|
||
| `services/router/tool_manager.py` | Handler `_drift_analyzer_tool` + TOOL_DEFINITIONS |
|
||
| `services/router/release_check_runner.py` | Gate 6 `_run_drift()` |
|
||
| `config/tools_rollout.yml` | `cto_tools` включає `drift_analyzer_tool` |
|
||
| `config/rbac_tools_matrix.yml` | `drift_analyzer_tool` actions + `tools.drift.*` entitlements |
|
||
| `config/tool_limits.yml` | `drift_analyzer_tool` limits |
|
||
| `tests/test_drift_analyzer.py` | 29 тестів + fixtures |
|