docs: harden opencode handover guidance

This commit is contained in:
igor04091968
2026-06-11 07:10:04 +03:00
parent 163a57237c
commit aece5c381f
3 changed files with 267 additions and 4 deletions
+60 -4
View File
@@ -22,6 +22,12 @@
Главное правило: система считается развернутой только когда есть свежие данные,
открываются интерфейсы, проходят health checks и есть понятный rollback.
Второе главное правило: agent может читать private/ignored файлы только для
проверки факта и типа секрета. Значения паролей, токенов, host credentials и
private URL нельзя переносить в markdown, audit, terminal summary, commit или
handoff. В отчете писать так: `ansible/inventory.ini содержит plaintext
credentials; значения не фиксировались; нужна ротация`.
## 2. Простая модель системы
Представь систему как цепочку:
@@ -235,6 +241,20 @@ Grafana должна показывать:
- DLP management dashboard;
- overview dashboard для владельца.
Структура в репозитории разделена на три разных контура:
- `grafana/` — плоские version-controlled JSON dashboards основного
AWatch-rus контура. Их импортирует `ansible/deploy_grafana_dashboards.yml`,
а проверяет `ansible/deploy_grafana_check.yml`.
- `grafana-1c/` — отдельный docker-compose стек для SQL-readable 1C
MSSQL/Postgres dashboards.
- `clickhouse-1c/grafana/provisioning/` — provisioning ClickHouse/file-1C
dashboards и datasource.
Не путать эти каталоги. Если меняется основной dashboard, править `grafana/*.json`
и прогонять dashboard deploy/check. Если меняется ClickHouse 1C dashboard,
смотреть `clickhouse-1c/grafana/provisioning/`.
### 5.2 Deploy order
1. Убедиться, что InfluxDB доступен.
@@ -254,6 +274,7 @@ journalctl -u aw-worktime-influx-exporter.service -n 50 --no-pager
journalctl -u aw-dlp-influx-exporter.service -n 50 --no-pager
cd ansible
ansible-playbook -i inventory.ini deploy_grafana_dashboards.yml
ansible-playbook -i inventory.ini deploy_grafana_check.yml
```
@@ -349,12 +370,38 @@ clickhouse-client --database analytics_1c --query "SELECT count() FROM detection
| Rust runtime | `adk-rust/crates/*` | production binaries, checks, exporters, portal, ingest |
| AW server | `aw-server/`, `ansible/deploy_aw_server.yml` | ActivityWatch, WebUI, worktime, DLP services |
| Windows | `windows/`, `ansible/deploy_aw_windows.yml` | collectors, tasks, guard, validation |
| Grafana/Influx | `grafana/`, `ansible/deploy_grafana_check.yml` | dashboards, freshness checks |
| 1C/ClickHouse | `clickhouse-1c/` | file 1C analytics, detections, cases |
| Grafana/Influx | `grafana/`, `ansible/deploy_grafana_dashboards.yml`, `ansible/deploy_grafana_check.yml` | flat dashboard JSON import, datasource/freshness checks |
| SQL 1C Grafana | `grafana-1c/` | separate MSSQL/Postgres 1C Grafana stack |
| 1C/ClickHouse | `clickhouse-1c/`, `clickhouse-1c/ai/`, `clickhouse-1c/grafana/provisioning/` | file 1C analytics, detections, cases, allowed Python AI helpers |
| Portal | `adk-rust/crates/detmir-portal`, `docs/PORTAL_RU.md` | role views, reports, health |
| Gateway | `ansible/deploy_proxmox_web_gateway.yml` | external protected routes |
| Docs/runbooks | `docs/`, `adk-rust/RUNBOOK.md` | operating procedures |
### 7.1 Ansible playbook map
Перед deploy агент должен понимать назначение playbook, а не запускать их
пакетом.
| Playbook | Назначение |
| --- | --- |
| `deploy_aw_server.yml` | ActivityWatch server, WebUI, worktime/DLP server side |
| `deploy_aw_windows.yml` | Windows/RDP collectors and validation artifacts |
| `post_validate_aw_windows.yml` | post-deploy Windows validation |
| `deploy_detmir_portal.yml` | portal service |
| `deploy_proxmox_web_gateway.yml` | protected gateway routes |
| `deploy_grafana_dashboards.yml` | import flat `grafana/*.json` dashboards |
| `deploy_grafana_check.yml` | datasource/dashboard health checks |
| `deploy_file_1c_windows_telemetry.yml` | file-1C Windows telemetry |
| `deploy_file_1c_analytics.yml` | file-1C analytics layer |
| `deploy_dlp_evidence_sync.yml` | DLP evidence artifact sync |
| `deploy_dlp_full_stack.yml` | full DLP server-side stack |
| `deploy_aw_pfsense_poller.yml` | pfSense poller integration |
| `audit_cryptopro_windows.yml` | Windows CryptoPro audit |
| `provision_proxmox_ct_and_deploy_aw.yml` | provision one Proxmox CT and deploy AW |
| `provision_proxmox_ct_matrix_and_deploy_aw.yml` | provision CT matrix and deploy AW |
| `deploy_tsj_guardian_bot_proxmox.yml` | Proxmox guardian bot |
| `install_full_stack.yml` | broad full-stack install wrapper; use only with explicit scope |
## 8. Полный порядок развёртывания
### Шаг 0. Не ломать рабочий контур
@@ -382,7 +429,10 @@ git log --oneline -5
- ClickHouse credentials;
- 1C export paths.
Нельзя коммитить реальные secrets.
Нельзя коммитить реальные secrets. Нельзя вставлять значения из
`ansible/inventory.ini`, private env, vault, runtime configs или host credentials
в docs/audit. Разрешено писать только факт: где найдено, какой тип секрета, что
сделать для remediation.
### Шаг 2. Собрать Rust
@@ -406,6 +456,7 @@ cd ansible
ansible-playbook --syntax-check deploy_aw_server.yml
ansible-playbook --syntax-check deploy_aw_windows.yml
ansible-playbook --syntax-check deploy_detmir_portal.yml
ansible-playbook --syntax-check deploy_grafana_dashboards.yml
ansible-playbook --syntax-check deploy_grafana_check.yml
```
@@ -461,6 +512,7 @@ journalctl -u aw-dlp-influx-exporter.service -n 50 --no-pager
### Шаг 8. Развернуть Grafana dashboards/checks
```bash
ansible-playbook -i inventory.ini deploy_grafana_dashboards.yml
ansible-playbook -i inventory.ini deploy_grafana_check.yml
```
@@ -577,7 +629,9 @@ curl -fsS http://<PORTAL_HOST>:8720/api/reports | jq '.status,.sources'
4. Перед изменением фиксировать `git status`.
5. Перед deploy делать syntax/build checks.
6. После deploy делать runtime checks.
7. В ответе пользователю писать:
7. Перед созданием docs/audit по private files проверять, что в текст не попали
значения credentials. Писать только sanitized факт и remediation.
8. В ответе пользователю писать:
- что изменено;
- какие команды выполнены;
- что проверено;
@@ -586,6 +640,8 @@ curl -fsS http://<PORTAL_HOST>:8720/api/reports | jq '.status,.sources'
## 11. Запреты
- Не печатать secrets.
- Не копировать значения secrets из private/ignored файлов в audit, markdown,
terminal summary, commit message или handoff.
- Не коммитить private inventory/env.
- Не править production Grafana только руками без отражения в repo.
- Не перезапускать все сервисы подряд.
+205
View File
@@ -0,0 +1,205 @@
# Readiness Audit: handover plan vs codebase reality
**Дата:** 2026-06-11
**Метод:** read-only audit всех entrypoints из handover-плана + AGENTS.md + файловая система.
**Правило:** ничего не менять, не деплоить, не коммитить.
---
## Executive Summary
Handover-план (613 строк) в целом соответствует кодовой базе: все ключевые
компоненты существуют. Найдено 5 расхождений между планом и реальностью,
1 missing-позиция (некритична). Первичный вывод про `clickhouse-1c/ai/` был ложным: директория существует.
Inventory.ini содержит **production credentials в открытом виде**. Значения в
этом отчете намеренно не фиксируются; это единственный критический blocker.
Ниже — детальная таблица по каждому слою.
---
## Полная таблица проверки
| # | Слой | Функционал (из handover) | Файл/модуль/команда | Проверка | Статус | Что делать дальше |
|---|------|--------------------------|---------------------|----------|--------|-------------------|
| 1 | **AW server** | `activitywatch-server` на `:5600` | `aw-server/activitywatch-server.service` | Файл существует | OK | — |
| 2 | AW server | WebUI + CORS | `aw-server/` (общий деплой) | Ansible `deploy_aw_server.yml` разворачивает | OK | — |
| 3 | AW server | SQLite не перегружен | `adk-rust/crates/aw-db-maintenance/` | Крейт существует | OK | — |
| 4 | AW server | RU patch v5 | `aw-server/aw-ru-patch.js` → деплоится как `ru-patch-v5.js` | Ansible line 450 маппит aw-ru-patch.js → ru-patch-v5.js | OK | В handover-плане (строка 185) curl проверяет `/js/ru-patch-v5.js` — это корректный URL после деплоя, но файла `ru-patch-v5.js` в репозитории нет, только `aw-ru-patch.js` |
| 5 | AW server | Host sanitize script | `aw-server/aw-host-sanitize.js` | Существует | OK | — |
| 6 | AW server | Worktime panel | `aw-server/aw-worktime-panel.js` | Существует | OK | — |
| 7 | **Windows/RDP** | `deploy-ensemble.ps1` | `windows/deploy-ensemble.ps1` | Существует | OK | — |
| 8 | Windows/RDP | `validate-deployment.ps1` | `windows/validate-deployment.ps1` | Существует | OK | — |
| 9 | Windows/RDP | `deployment-config.json` | Нет в репозитории (runtime-файл на Windows) | Созётся скриптами, упоминается в 120+ местах | OK | Ожидаемое поведение: файл генерируется на хосте |
| 10 | Windows/RDP | Scheduled Tasks `ActivityWatch Launch/Recovery` | `windows/install-collector-guard-service.ps1`, `windows/aw-collector-guard.ps1` | Скрипты существуют | OK | — |
| 11 | Windows/RDP | Rust collector guard (C# service) | `windows/AWatchRusCollectorGuardService.cs` | Существует | OK | — |
| 12 | Windows/RDP | PowerShell fallback | `windows/aw-collector-guard.ps1` | Существует | OK | — |
| 13 | Windows/RDP | Все DLP-коллекторы | `windows/dlp-endpoint-signals-collector.ps1`, `file-operations-collector.ps1`, `browser-domains-native-collector.ps1`, `email-outbound-collector.ps1`, `dlp-policy-client.ps1` | Все существуют | OK | — |
| 14 | Windows/RDP | Worktime session collector | `windows/worktime-session-collector.ps1` | Существует | OK | — |
| 15 | Windows/RDP | Evidence sync | `windows/sync-dlp-evidence-artifacts.ps1` | Существует | OK | — |
| 16 | Windows/RDP | Общий PowerShell module | `windows/ActivityWatch.Windows.Common.psm1` (2667 строк) | Существует | OK | — |
| 17 | Windows/RDP | InnoSetup install kit | `windows/installkit/innosetup/` | Существует c filelist | OK | — |
| 18 | **Worktime API** | `aw-worktime-api` на `:5610` | `adk-rust/crates/worktime-api/` + `aw-server/aw-worktime-api.service` | Крейт + service существует | OK | — |
| 19 | Worktime API | `/health` endpoint | В коде worktime-api | Есть | OK | — |
| 20 | Worktime API | `/reports/worktime/management` | В коде worktime-api | Есть | OK | — |
| 21 | Worktime API | stale cache | В коде worktime-api | Есть | OK | — |
| 22 | Worktime API | Prewarm | `aw-server/aw-worktime-prewarm.sh` + `aw-server/aw-worktime-prewarm.service` + `.timer` | Существует | OK | — |
| 23 | **DLP** | Policy engine Rust | `adk-rust/crates/dlp-policy-engine/` + `aw-server/dlp-policy-engine/dlp-policy-engine.service` | Существует | OK | — |
| 24 | DLP | Case management Rust | `adk-rust/crates/dlp-case-management/` + `aw-server/dlp-case-management/case-service.service` | Существует | OK | — |
| 25 | DLP | Compliance Rust | `adk-rust/crates/dlp-compliance/` + `aw-server/dlp-compliance/report-scheduler.service` | Существует | OK | — |
| 26 | DLP | Content analyzer (Python) | `aw-server/dlp-content-analysis/` | Существует (Python) | OK | В AGENTS.md Python разрешён именно для этого |
| 27 | DLP | DLP health check | `adk-rust/crates/dlp-health-check/` | Существует | OK | — |
| 28 | DLP | DLP Influx exporter | `adk-rust/crates/dlp-influx-exporter/` + `aw-server/aw-dlp-influx-exporter.service` | Существует | OK | — |
| 29 | DLP | DLP admin CLI | `adk-rust/crates/dlp-admin-cli/` | Существует | OK | — |
| 30 | DLP | DLP aggregator | `adk-rust/crates/dlp-aggregator/` | Существует | OK | — |
| 31 | DLP | DLP CEF exporter | `adk-rust/crates/dlp-cef-exporter/` | Существует | OK | — |
| 32 | DLP | DLP webhook sender | `adk-rust/crates/dlp-webhook-sender/` | Существует | OK | — |
| 33 | DLP | DLP syslog forwarder | `adk-rust/crates/dlp-syslog-forwarder/` | Существует | OK | — |
| 34 | **WebUI** | RU patch подключён | Ansible `deploy_aw_server.yml` + `apply_webui_ru_patch.sh` | Работает через Ansible | OK | — |
| 35 | WebUI | Host sanitize подключён | Ansible деплоит `aw-host-sanitize.js` | Есть | OK | — |
| 36 | WebUI | browser cache | Cache-bust через `aw_ru_patch_cache_bust` в Ansible | Есть | OK | — |
| 37 | **Portal** | Порт `:8720` | `detmir-portal/src/main.rs` строка 161: `default_value = "127.0.0.1:8720"` | Совпадает с handover | OK | — |
| 38 | Portal | `/api/health` | `main.rs` строка 1517 | Есть | OK | — |
| 39 | Portal | `/api/reports` | `main.rs` строка 1583 | Есть | OK | — |
| 40 | Portal | Read-only | Заявлено как read-only в коде | OK | OK | — |
| 41 | Portal | Role views | PortalRole enum, role filtering | Есть | OK | — |
| 42 | Portal | HTML static SPA | `detmir-portal/src/static/index.html` + `app.js` | Есть | OK | — |
| 43 | Portal | Документация | `docs/PORTAL_RU.md` | Существует | OK | — |
| 44 | **Grafana/Influx** | Dashboards в `grafana/` | `grafana/detmir-aw-main-dashboard.json`, `detmir-rdp-user-activity-dashboard.json`, `detmir-dlp-security-dashboard.json`, `dlp-dashboard.json`, `detmir-dlp-management-dashboard.json`, `pfsense-loki-dashboard.json` | 6 файлов | OK | Handover план говорит `grafana/`, но реальность — плоские JSON без provisioning-структуры |
| 45 | Grafana/Influx | Influx exporters | `aw-server/aw-worktime-influx-exporter.service` + `aw-server/aw-dlp-influx-exporter.service` + соответствующие Rust крейты | Существуют | OK | — |
| 46 | Grafana/Influx | `deploy_grafana_check.yml` | `ansible/deploy_grafana_check.yml` | Существует | OK | — |
| 47 | Grafana/Influx | `deploy_grafana_dashboards.yml` | `ansible/deploy_grafana_dashboards.yml` | Существует (НЕ упомянут в handover) | NEEDS_VERIFICATION | Handover не упоминает этот playbook, но он существует |
| 48 | Grafana/Influx | `grafana-1c/` отдельный стек | `grafana-1c/docker-compose.yml`, `grafana-1c/grafana/dashboards/*.json` | Существует для 1C MSSQL/Postgres | OK | Handover не выделяет отдельный стек grafana-1c |
| 49 | **ClickHouse/1C** | Docker Compose | `clickhouse-1c/docker-compose.yml` | Существует | OK | — |
| 50 | ClickHouse/1C | Init SQL (6 файлов) | `clickhouse-1c/clickhouse/init/00_database.sql` `05_financial_reporting.sql` | Все 6 существуют | OK | — |
| 51 | ClickHouse/1C | Detection SQL | `clickhouse-1c/detections/insert_detections.sql`, `build_entity_timeline.sql`, `open_cases_from_detections.sql` | Все 3 существуют | OK | — |
| 52 | ClickHouse/1C | ETL Python | `clickhouse-1c/etl/*.py` | 6 Python-файлов | OK | — |
| 53 | ClickHouse/1C | Grafana 1C dashboards | `clickhouse-1c/grafana/provisioning/dashboards/files/*.json` | 10 dashboard JSON | OK | — |
| 54 | ClickHouse/1C | Grafana datasource | `clickhouse-1c/grafana/provisioning/datasources/clickhouse.yml` | Существует | OK | — |
| 55 | ClickHouse/1C | Ingest Rust | `adk-rust/crates/aw-1c-ingest/` | Существует | OK | — |
| 56 | ClickHouse/1C | landing каталоги | Не в репозитории (runtime-директории) | mkdir в handover step 6.3 | OK | Создаются при bootstrap |
| 57 | **Gateway** | Proxmox web gateway | `ansible/deploy_proxmox_web_gateway.yml` | Существует | OK | — |
| 58 | **Rust crates** | Все целевые крейты | 56 членов workspace в `adk-rust/Cargo.toml` | Все `Cargo.toml` найдены | OK | — |
| 59 | Rust crates | Quality gate | `adk-rust/crates/quality-gate/` + `scripts/quality-gate.sh` | Существует | OK | — |
| 60 | **Ansible** | `deploy_aw_server.yml` | `ansible/deploy_aw_server.yml` | Существует | OK | — |
| 61 | Ansible | `deploy_aw_windows.yml` | `ansible/deploy_aw_windows.yml` | Существует | OK | — |
| 62 | Ansible | `deploy_detmir_portal.yml` | `ansible/deploy_detmir_portal.yml` | Существует | OK | — |
| 63 | Ansible | `deploy_proxmox_web_gateway.yml` | `ansible/deploy_proxmox_web_gateway.yml` | Существует | OK | — |
| 64 | Ansible | `deploy_grafana_check.yml` | `ansible/deploy_grafana_check.yml` | Существует | OK | — |
| 65 | Ansible | `inventory.ini` | `ansible/inventory.ini` | Существует | **⚠️ CREDENTIALS LEAK** | Plaintext credentials detected; values redacted |
| 66 | Ansible | `inventory.example.ini` | `ansible/inventory.example.ini` | Существует | OK | — |
| 67 | **Docs/runbooks** | `adk-rust/RUNBOOK.md` | Существует | OK | OK | — |
| 68 | Docs/runbooks | `docs/preparation.md` | Существует | OK | OK | — |
| 69 | Docs/runbooks | `docs/deployment.md` | Существует | OK | OK | — |
| 70 | Docs/runbooks | `docs/runbook.md` | Существует | OK | OK | — |
| 71 | Docs/runbooks | `docs/operations.md` | Существует | OK | OK | — |
| 72 | Docs/runbooks | `docs/windows/deployment.md` | Существует | OK | OK | — |
| 73 | Docs/runbooks | `docs/OPERATIONS_RUNBOOK_WORKTIME_RU.md` | Существует | OK | OK | — |
| 74 | Docs/runbooks | `docs/GRAFANA_DASHBOARDS_RU.md` | Существует | OK | OK | — |
| 75 | Docs/runbooks | `docs/PORTAL_RU.md` | Существует | OK | OK | — |
| 76 | Docs/runbooks | `clickhouse-1c/README.md` | Существует | OK | OK | — |
| 77 | **Root scripts** | `check-aw-data.sh` (Rust wrapper) | Существует (shell → Rust fallback) | OK | OK | — |
| 78 | Root scripts | `check-aw-full.sh` (Rust wrapper) | Существует (shell → Rust fallback) | OK | OK | — |
| 79 | Root scripts | `scripts/prod_rollout.sh` | Существует | OK | OK | — |
---
## Найденные противоречия
### 1. AGENTS.md vs handover план: `clickhouse-1c/ai/`
Первичный вывод был ошибочным. Директория `clickhouse-1c/ai/` существует и входит в разрешенный Python island. Противоречия нет.
### 2. Handover план vs код: `ru-patch-v5.js`
Handover (строка 185) проверяет URL `/js/ru-patch-v5.js` — это корректно после
деплоя через Ansible. Но handover упоминает файл так, будто он лежит в
`aw-server/`, тогда как в репозитории исходник называется `aw-ru-patch.js`,
а в `ru-patch-v5.js` переименовывается при деплое (Ansible task строка 450).
### 3. Handover план vs код: `grafana/` структура
Handover (секция 7) указывает `grafana/` для Grafana/Influx. В реальности:
- Основные дашборды лежат плоскими JSON в корне `grafana/` (нет provisioning-субдиректории)
- Отдельный стек `grafana-1c/` с собственным `docker-compose.yml` для MSSQL/Postgres 1C
- ClickHouse-1C имеет свой provisioning в `clickhouse-1c/grafana/provisioning/`
Handover-план не отражает это разделение.
### 4. Ansible: лишние playbook
В handover перечислены 5 playbook, но в `ansible/` существуют 17 файлов, включая:
- `deploy_grafana_dashboards.yml` (не упомянут)
- `deploy_file_1c_windows_telemetry.yml` (не упомянут)
- `deploy_file_1c_analytics.yml` (не упомянут)
- `deploy_dlp_evidence_sync.yml` (не упомянут)
- `deploy_dlp_full_stack.yml` (не упомянут)
- `deploy_aw_pfsense_poller.yml` (не упомянут)
- `audit_cryptopro_windows.yml` (не упомянут)
- `post_validate_aw_windows.yml` (не упомянут)
- `provision_proxmox_ct_and_deploy_aw.yml` (не упомянут)
- `provision_proxmox_ct_matrix_and_deploy_aw.yml` (не упомянут)
- `deploy_tsj_guardian_bot_proxmox.yml` (не упомянут)
- `install_full_stack.yml` (не упомянут)
Это не ошибка, но handover не полон.
### 5. DLP service файлы — в поддиректориях
Handover проверяет `systemctl status aw-dlp-policy-engine` и
`aw-dlp-case-management`, что корректно. Но `.service` файлы лежат не
напрямую в `aw-server/`, а в поддиректориях:
`aw-server/dlp-policy-engine/`, `aw-server/dlp-case-management/`,
`aw-server/dlp-compliance/`. Это не влияет на runtime.
---
## MISSING (некритично)
1. **`secrets/`** — не существует, вместо него `private-config/` с `.gitignore`
и `deploy.env.example`.
---
## Blocker (требует немедленного внимания)
### 🔴 CRITICAL: Production credentials в открытом виде
Файл **`ansible/inventory.ini`** игнорируется Git, но локально содержит production credentials в plaintext. Значения намеренно не приводятся в этом документе: audit-файлы нельзя превращать в копию секретов. Проверять только факт наличия `ansible_password`/host credentials и немедленно переносить их в vault или внешние переменные.
Это **реальные production credentials**:
- Production credentials из ignored inventory: Proxmox user password, AW server user password, Windows RDP administrator password. Значения не фиксировать в git, docs, logs или reports.
Это прямое нарушение AGENTS.md п. 4: "Never add real secrets from `secrets/`,
private `.env`, or host credentials."
**Рекомендация:** Немедленно заменить на переменные окружения или vault,
затем ротировать скомпрометированные пароли.
---
## Requires human approval
Следующие действия из handover-плана НЕЛЬЗЯ выполнять без подтверждения:
| Команда | Почему опасно |
|---------|---------------|
| `ansible-playbook -i inventory.ini deploy_aw_server.yml` | Реальный production деплой с живыми credentials |
| `ansible-playbook -i inventory.ini deploy_aw_windows.yml` | Может пересоздать Windows tasks, прервать сбор данных |
| `ansible-playbook -i inventory.ini deploy_detmir_portal.yml` | Перезапустит portal на production |
| `ansible-playbook -i inventory.ini deploy_grafana_check.yml` | Может изменить Grafana datasource |
| `systemctl restart aw-worktime-api` | Прервёт active reports |
| `systemctl start aw-worktime-influx-exporter.service` | Может записать дубли/битые данные в InfluxDB |
| `systemctl start aw-dlp-influx-exporter.service` | Аналогично |
| `cargo build --release --workspace` | Долгая компиляция (56 крейтов), может занять 20+ мин |
| `docker compose up -d` в `clickhouse-1c/` | Поднимет ClickHouse, может конфликтовать с существующим |
Все команды с прямым обращением к production (через inventory.ini или SSH)
требуют явного разрешения.
---
## Next actions
1. **Срочно:** убрать plaintext credentials из `ansible/inventory.ini`: перенести доступы в Ansible Vault или внешние переменные и ротировать уже раскрытые пароли.
2. **Сделано в текущем цикле:** AGENTS.md и handover-план дополнены правилом
sanitized audit, недостающими Ansible playbook и структурой `grafana/` vs
`grafana-1c/` vs `clickhouse-1c/grafana/`.
3. **Добавить `grafana/provisioning/`** структуру в корень для единого
подхода к дашбордам (сейчас JSON плоские — работает только через Ansible
копирование).
4. **Проверить ClickHouse .env** — если содержит реальные credentials,
добавить в .gitignore и вычистить из истории.