1773 lines
100 KiB
Markdown
1773 lines
100 KiB
Markdown
# Runbook: перевод DetMir на Rust / ADK-Rust
|
||
|
||
Дата фиксации: `2026-06-01`
|
||
|
||
Цель: постепенно заменить хрупкие Python/shell operational scripts на
|
||
самодостаточные Rust-бинарники, не ломая текущий production-контур DetMir.
|
||
|
||
Этот runbook является рабочим планом миграции. Если фактический runtime
|
||
расходится с этим документом, сначала фиксируется baseline, затем обновляется
|
||
runbook.
|
||
|
||
Текущий resume snapshot проекта хранится в
|
||
`.ai/runtime/detmir-current-session.md`. Перед продолжением миграции после
|
||
перерыва или context reset сначала читать его, затем `RUNBOOK.md` и
|
||
`<OPERATOR_CODEX_HOME>/skills/detmir-rust-migration/references/current-state.md`.
|
||
|
||
## 1. Целевое состояние
|
||
|
||
В результате миграции DetMir должен иметь:
|
||
|
||
- единый Rust workspace в `adk-rust/`;
|
||
- один standalone-бинарник или crate на каждую операционную функцию;
|
||
- стабильные machine-readable JSON-контракты вместо парсинга human text;
|
||
- одинаковые exit codes для старых и новых команд;
|
||
- systemd units/timers, не зависящие от ноутбука, venv, pip и локальных путей;
|
||
- безопасный auto-heal с dry-run, lock, cooldown, allowlist и rollback;
|
||
- ADK-compatible envelopes там, где вывод передается агентам/боту/LLM.
|
||
|
||
Rust не должен использоваться как самоцель. Если компонент надежнее оставить в
|
||
Ansible, PowerShell или Playwright, он остается там до появления практической
|
||
причины для переноса.
|
||
|
||
## 2. Текущая отправная точка
|
||
|
||
Уже создано:
|
||
|
||
- `adk-rust/Cargo.toml` - workspace;
|
||
- `adk-rust/crates/detmir-status` - read-only status module with text, JSON and ADK JSON output;
|
||
- `/usr/local/bin/detmir-status` на Proxmox, проверен против
|
||
`/var/lib/detmir-ai/latest-state.json`;
|
||
- `/usr/local/bin/detmir-adk-status` на Proxmox, проверен против
|
||
`/var/lib/detmir-ai/latest-state.json` как compatibility binary.
|
||
|
||
Ключевые текущие legacy-компоненты:
|
||
|
||
| Компонент | Текущая реализация | Риск переноса |
|
||
|---|---|---|
|
||
| `detmir-status` | Rust binary + thin compatibility wrapper | выполнено |
|
||
| `detmir-check` | Rust binary + thin compatibility wrapper | выполнено |
|
||
| `detmir-dlp` | Rust SSH wrapper + thin compatibility wrapper | выполнено |
|
||
| `detmir-auto` | Rust production via systemd drop-in + legacy script retained | switched |
|
||
| `detmir-heal-safe` | Rust binary deployed + legacy script retained | switched for Rust auto |
|
||
| `tsj_guardian_watchdog.sh` | Rust service via systemd drop-in + legacy shell retained | switched |
|
||
| Telegram `/status`/decision backend | Rust helper + permanent Python bot runtime | backend only |
|
||
| `aw-rus-healthd` | Rust production unit, Python entrypoint removed from repo | done |
|
||
| `dlp-health-check` | Rust production binary, Python entrypoint removed from repo | done |
|
||
| DLP aggregator | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| AW DLP Influx exporter | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| AW worktime Influx exporter | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| AW worktime prewarm | Rust production via systemd drop-in + legacy shell retained | switched |
|
||
| AW worktime API | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| DLP syslog forwarder | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| DLP webhook sender | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| DLP CEF exporter | Rust production via systemd drop-in + legacy Python retained | switched |
|
||
| `tsj_guardian_bot.py` | Permanent Python Telegram runtime | не переносить |
|
||
| AW remaining worktime modules | mostly Rust production; inspect leftovers before next item | средний |
|
||
| deploy/install scripts | shell/Ansible/PowerShell | высокий, переносить последними |
|
||
|
||
## 3. Архитектура workspace
|
||
|
||
Целевая структура:
|
||
|
||
```text
|
||
adk-rust/
|
||
Cargo.toml
|
||
Cargo.lock
|
||
RUNBOOK.md
|
||
crates/
|
||
detmir-core/
|
||
detmir-aw-client/
|
||
detmir-systemd/
|
||
detmir-state/
|
||
detmir-status/
|
||
detmir-check/
|
||
detmir-dlp/
|
||
detmir-auto/
|
||
detmir-heal-safe/
|
||
tsj-guardian-status/
|
||
tsj-guardian-watchdog/
|
||
```
|
||
|
||
Назначение shared crates:
|
||
|
||
| Crate | Назначение |
|
||
|---|---|
|
||
| `detmir-core` | ошибки, CLI output, timeouts, retry, exit codes, config loading |
|
||
| `detmir-aw-client` | ActivityWatch HTTP API, buckets, events, timestamps |
|
||
| `detmir-systemd` | безопасный wrapper вокруг `systemctl`, allowlist, dry-run |
|
||
| `detmir-state` | чтение/запись `/var/lib/detmir-ai`, atomic writes, retention |
|
||
| `detmir-report` | Markdown/JSON reports, ADK envelopes, report bundle assembly |
|
||
|
||
Бинарные модули должны быть маленькими и собираться из shared crates. Логику не
|
||
дублировать между `check`, `status`, `auto` и ботом.
|
||
|
||
## 4. Общие контракты для каждого Rust-модуля
|
||
|
||
Каждый новый модуль обязан иметь:
|
||
|
||
- `--json` для машинного вывода;
|
||
- `--pretty` или обычный text output для человека, если команда операторская;
|
||
- `--config <path>` либо documented env vars;
|
||
- `--dry-run` для любых действий, которые меняют состояние;
|
||
- `--timeout` там, где есть сеть/SSH/HTTP;
|
||
- `--no-color` если вывод может попадать в systemd/Telegram/report;
|
||
- стабильные exit codes:
|
||
- `0` - OK;
|
||
- `1` - usage/config/runtime error;
|
||
- `2` - проверка выполнена, но состояние WARN/FAIL;
|
||
- `3` - action запрещен safety policy;
|
||
- structured logs через `tracing` для daemon/action-команд;
|
||
- unit tests на чистую логику;
|
||
- fixture tests на реальные JSON samples;
|
||
- README с примером локального и серверного запуска.
|
||
|
||
## 5. Safety policy
|
||
|
||
Нельзя сразу заменять управляющие скрипты без shadow-mode.
|
||
|
||
Обязательные правила:
|
||
|
||
- сначала read-only parity, потом mutation;
|
||
- старый и новый модуль должны некоторое время работать параллельно;
|
||
- новый модуль не получает право писать state, рестартить сервисы или удалять
|
||
файлы до прохождения acceptance gates;
|
||
- все state writes только через atomic temp file + rename;
|
||
- heal-команды только по allowlist units;
|
||
- no implicit sudo: если нужен `sudo`, он должен быть явно виден в deploy/unit;
|
||
- lock file обязателен для `auto` и `heal`;
|
||
- cooldown обязателен для restart/start actions;
|
||
- каждый risky action пишет audit entry;
|
||
- rollback должен быть одной командой systemd/service symlink switch.
|
||
|
||
## 6. Фазы миграции
|
||
|
||
### Phase 0. Baseline и фиксация контрактов
|
||
|
||
Цель: перед переносом зафиксировать, что именно считается корректным поведением.
|
||
|
||
Действия:
|
||
|
||
1. Снять текущие outputs legacy-команд:
|
||
- `detmir-status`;
|
||
- `detmir-check --json`;
|
||
- `detmir-dlp`;
|
||
- `detmir-auto` на зеленом контуре;
|
||
- `detmir-heal-safe` в dry-safe сценарии или на mock host.
|
||
2. Сохранить sanitized fixtures в `adk-rust/fixtures/`.
|
||
3. Зафиксировать JSON schemas:
|
||
- status summary;
|
||
- DetMir check result;
|
||
- DLP health result;
|
||
- auto run state;
|
||
- heal action log.
|
||
4. Зафиксировать exit code matrix старых команд.
|
||
5. Зафиксировать runtime paths:
|
||
- `/var/lib/detmir-ai`;
|
||
- `/usr/local/bin`;
|
||
- relevant systemd units/timers.
|
||
|
||
Гейт готовности:
|
||
|
||
```bash
|
||
cd adk-rust
|
||
cargo fmt --all -- --check
|
||
cargo check --workspace
|
||
```
|
||
|
||
Результат фазы: миграция не начинается вслепую; есть baseline для сравнения.
|
||
|
||
### Phase 1. Rust foundation
|
||
|
||
Цель: создать shared crates, чтобы не плодить разные реализации HTTP, времени,
|
||
ошибок, JSON и systemd.
|
||
|
||
Модули:
|
||
|
||
- `detmir-core`;
|
||
- `detmir-state`;
|
||
- `detmir-aw-client`;
|
||
- `detmir-systemd`.
|
||
|
||
Минимальная функциональность:
|
||
|
||
- RFC3339/UTC timestamp parsing;
|
||
- HTTP JSON client with timeout/retry;
|
||
- TCP check;
|
||
- atomic JSON write;
|
||
- retention cleanup;
|
||
- exit-code helper;
|
||
- common `StatusLevel`: `OK`, `WARN`, `FAIL`;
|
||
- bucket mode enum: `fresh`, `inactive_ok`, `event_driven`;
|
||
- systemd read-only checks;
|
||
- systemd action allowlist, но без включения mutation by default.
|
||
|
||
Гейт:
|
||
|
||
```bash
|
||
cd adk-rust
|
||
cargo fmt --all -- --check
|
||
cargo clippy --workspace --all-targets -- -D warnings
|
||
cargo test --workspace
|
||
```
|
||
|
||
### Phase 2. Read-only operator modules
|
||
|
||
Цель: заменить самые безопасные команды, не меняющие состояние.
|
||
|
||
Порядок:
|
||
|
||
1. `detmir-status`
|
||
- объединить с совместимым `detmir-adk-status`;
|
||
- читать `/var/lib/detmir-ai/latest-state.json`;
|
||
- выдавать text, JSON, ADK JSON;
|
||
- заменить shell wrapper после parity.
|
||
2. `detmir-check`
|
||
- перенести Python checks в Rust;
|
||
- сохранить семантику `FRESH`, `INACTIVE`, `EVENT-DRIVEN`, `STALE`, `DEAD`;
|
||
- сохранить `summary.bucket_ok/stale/dead/service_failures/service_warnings`;
|
||
- не менять thresholds без отдельного решения.
|
||
3. `detmir-dlp`
|
||
- сначала переписать SSH wrapper;
|
||
- затем отдельно переносить `dlp-health-check.py` на AW server.
|
||
|
||
Shadow-mode для `detmir-check`:
|
||
|
||
```bash
|
||
detmir-check --json > /tmp/detmir-check.old.json
|
||
adk-rust/target/release/detmir-check --json > /tmp/detmir-check.new.json
|
||
jq -S '.summary' /tmp/detmir-check.old.json > /tmp/old.summary.json
|
||
jq -S '.summary' /tmp/detmir-check.new.json > /tmp/new.summary.json
|
||
diff -u /tmp/old.summary.json /tmp/new.summary.json
|
||
```
|
||
|
||
Гейт:
|
||
|
||
- old/new agree on `ok`;
|
||
- old/new agree on summary counters;
|
||
- new module returns same exit code class;
|
||
- no network side effects;
|
||
- server run succeeds from systemd-like environment.
|
||
|
||
### Phase 3. State/report orchestration
|
||
|
||
Цель: заменить `detmir-auto` без немедленной замены heal.
|
||
|
||
Первый Rust `detmir-auto` должен:
|
||
|
||
- брать lock;
|
||
- создавать run directory;
|
||
- запускать `detmir-check` и `detmir-dlp`;
|
||
- писать `summary-before.json`;
|
||
- писать `state-*.json`;
|
||
- обновлять symlinks:
|
||
- `latest-run`;
|
||
- `latest-state.json`;
|
||
- `latest-report.md`;
|
||
- выполнять retention cleanup;
|
||
- уметь `--no-heal`;
|
||
- по умолчанию на первом этапе не выполнять heal, а только писать
|
||
`would_heal=true`.
|
||
|
||
Pollinations/LLM report:
|
||
|
||
- не должен быть hard dependency для статуса;
|
||
- failure LLM report не должен ломать state update;
|
||
- raw JSON summary всегда должен сохраняться даже при ошибке report generation.
|
||
|
||
Гейт:
|
||
|
||
- Rust `detmir-auto --no-heal` дает такой же `latest-state.json` по смыслу;
|
||
- symlink updates atomic enough for readers;
|
||
- timer можно прогнать вручную без изменения heal behavior;
|
||
- `detmir-status` видит новое state без изменений.
|
||
|
||
Текущее состояние:
|
||
|
||
- `detmir-auto-rust` установлен на Proxmox как shadow binary
|
||
`/usr/local/bin/detmir-auto-rust`;
|
||
- `detmir-auto-rust` умеет вызывать Rust heal через `--enable-heal` или
|
||
`DETMIR_AUTO_HEAL=1`, но shadow unit явно запускается с `--no-heal`;
|
||
- production `detmir-auto.service` переключен на `/usr/local/bin/detmir-auto-rust`
|
||
через drop-in `/etc/systemd/system/detmir-auto.service.d/20-rust-switch.conf`;
|
||
- legacy `/usr/local/bin/detmir-auto` сохранен для rollback;
|
||
- отдельный `detmir-auto-rust-shadow.timer` отключен после успешного
|
||
production timer observation; unit-файлы и shadow state сохранены;
|
||
- shadow пишет только в `/var/lib/detmir-ai/shadow/detmir-auto-rust`;
|
||
- `detmir-auto-rust-shadow.service` использует `SuccessExitStatus=2`, чтобы
|
||
найденный shadow FAIL фиксировался в JSON, но не загрязнял
|
||
`systemctl --failed`;
|
||
- перед стартом shadow service проверяет, что production `detmir-auto.service`
|
||
не активен;
|
||
- последняя systemd shadow-проверка после retry в AW client:
|
||
`severity=OK`, `check_rc=0`, `dlp_rc=0`, buckets `8/0/0`, DLP `22/0/0`.
|
||
- production Rust start проверен: `ExecStart=/usr/local/bin/detmir-auto-rust
|
||
--command-timeout-seconds 180`, latest-state `OK`, `systemctl --failed`
|
||
пустой.
|
||
- scheduled production timer cycle проверен после switch: process status `0`,
|
||
latest-state `OK`, report generated, `systemctl --failed` пустой.
|
||
|
||
### Phase 4. Safe heal
|
||
|
||
Цель: перенести `detmir-heal-safe`, но только после read-only parity.
|
||
|
||
Обязательные guardrails:
|
||
|
||
- `--dry-run` default на первых deploy;
|
||
- allowlist units:
|
||
- `activitywatch-server.service`;
|
||
- `aw-worktime-api.service`;
|
||
- `aw-worktime-ui-bridge.timer`;
|
||
- `activitywatch-dlp-aggregator.timer` только если unit существует;
|
||
- запрет wildcard restart;
|
||
- `systemctl reset-failed` только для allowlist;
|
||
- cooldown между restart попытками;
|
||
- audit log в run directory;
|
||
- hard timeout на SSH/systemctl;
|
||
- never touch Windows/RDP recovery from this module.
|
||
|
||
Гейт:
|
||
|
||
- dry-run показывает exact actions;
|
||
- на зеленом контуре не рестартит ничего;
|
||
- на mock/failing unit рестартит только allowlisted unit;
|
||
- после heal всегда запускается повторный check;
|
||
- exit code не маскирует FAIL.
|
||
|
||
Текущее состояние:
|
||
|
||
- создан crate `detmir-heal-safe`;
|
||
- binary развернут на Proxmox как `/usr/local/bin/detmir-heal-safe-rust`;
|
||
- production `/usr/local/bin/detmir-heal-safe` пока не заменен;
|
||
- production Rust auto использует `/usr/local/bin/detmir-heal-safe-rust` через
|
||
`DETMIR_HEAL_BIN`;
|
||
- default mode: dry-run, mutation только через `--apply`;
|
||
- optional timer start выключен по умолчанию, включается только
|
||
`--start-optional`;
|
||
- green dry-run проверен: restart/start не планируются;
|
||
- green apply проверен: выполнены только `reset-failed` и DLP health snapshot,
|
||
контур остался `OK`, `systemctl --failed` пустой.
|
||
- red/mock heal test проверен на Proxmox в isolated state-dir:
|
||
`summary-before=FAIL`, heal вызван, check/dlp повторены, final state `OK`,
|
||
process rc `0`.
|
||
|
||
### Phase 5. Watchdog и lightweight services
|
||
|
||
Цель: заменить простые shell watchdogs и wrappers.
|
||
|
||
Порядок:
|
||
|
||
1. `tsj-guardian-watchdog.sh` -> `tsj-guardian-watchdog`;
|
||
2. lightweight service wrappers;
|
||
3. bounded cleanup/retention jobs.
|
||
|
||
Особое внимание:
|
||
|
||
- bot heartbeat file parsing;
|
||
- duplicate `gost` instance dedupe;
|
||
- service restart only on stale/missing heartbeat;
|
||
- no killing unrelated processes.
|
||
|
||
Гейт:
|
||
|
||
- dry-run mode показывает, какой PID был бы убит;
|
||
- process matching покрыт тестами;
|
||
- systemd timer/service rollback сохранен.
|
||
|
||
Текущее состояние:
|
||
|
||
- создан crate `tsj-guardian-watchdog`;
|
||
- binary развернут на Proxmox как
|
||
`/usr/local/bin/tsj-guardian-watchdog-rust`;
|
||
- production `tsj-guardian-watchdog.service` переключен на Rust через drop-in
|
||
`/etc/systemd/system/tsj-guardian-watchdog.service.d/20-rust-switch.conf`;
|
||
- legacy `/opt/infra-admin/tsj-bot/tsj_guardian_watchdog.sh` сохранен;
|
||
- dry-run выявил bug legacy-подхода: `pgrep -f` не матчился как literal из-за
|
||
`+` в `gost` pattern;
|
||
- Rust версия использует literal process scan через `ps`;
|
||
- one-shot apply удалил лишний duplicate `gost` PID и оставил systemd MainPID;
|
||
- service mode работает как:
|
||
|
||
```bash
|
||
/usr/local/bin/tsj-guardian-watchdog-rust --apply --loop-forever --interval-seconds 60
|
||
```
|
||
|
||
Rollback:
|
||
|
||
```bash
|
||
sudo rm -f /etc/systemd/system/tsj-guardian-watchdog.service.d/20-rust-switch.conf
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl restart tsj-guardian-watchdog.service
|
||
```
|
||
|
||
### Phase 6. AW server health and DLP modules
|
||
|
||
Цель: перенести серверные Python health checks/exporters.
|
||
|
||
Порядок:
|
||
|
||
1. `aw-server/aw-rus-healthd.py` - выполнено, production через
|
||
`aw-rus-healthd.service.d/20-rust-switch.conf`;
|
||
2. `scripts/dlp-health-check.py` - выполнено, `/usr/local/bin/dlp-health-check`
|
||
заменён Rust-бинарником;
|
||
3. `scripts/aggregate_dlp_events.py` - выполнено, production через
|
||
`activitywatch-dlp-aggregator.service.d/20-rust-switch.conf`;
|
||
4. DLP integrations:
|
||
- syslog forwarder - выполнено, production через
|
||
`aw-dlp-syslog-forwarder.service.d/20-rust-switch.conf`;
|
||
- webhook sender - выполнено, production через
|
||
`aw-dlp-webhook-sender.service.d/20-rust-switch.conf`;
|
||
- CEF exporter - выполнено, production через
|
||
`aw-dlp-cef-exporter.service.d/20-rust-switch.conf`;
|
||
5. `aw-server/aw-dlp-influx-exporter.py` - выполнено, production через
|
||
`aw-dlp-influx-exporter.service.d/20-rust-switch.conf`;
|
||
6. `aw-server/aw-worktime-influx-exporter.py` - выполнено, production через
|
||
`aw-worktime-influx-exporter.service.d/20-rust-switch.conf`;
|
||
7. `aw-server/aw-worktime-prewarm.sh` - выполнено, production через
|
||
`aw-worktime-prewarm.service.d/20-rust-switch.conf`;
|
||
8. `aw-server/aw-worktime-ui-bridge.py` - выполнено, production через
|
||
`aw-worktime-ui-bridge.service.d/20-rust-switch.conf`;
|
||
9. `aw-server/aw-worktime-autoheal.sh` - выполнено, production через
|
||
`aw-worktime-autoheal.service.d/20-rust-switch.conf`;
|
||
10. remaining worktime modules.
|
||
|
||
Правила:
|
||
|
||
- exporters должны сохранять Prometheus/Influx output format;
|
||
- API endpoints не менять без compatibility layer;
|
||
- для каждого exporter сначала golden output fixture;
|
||
- service unit меняется только после side-by-side run.
|
||
|
||
Гейт:
|
||
|
||
- old/new metrics names match;
|
||
- dashboard datasource health не ломается;
|
||
- unit tests старого поведения перенесены или сохранены;
|
||
- `systemctl --failed` clean после deploy.
|
||
|
||
### Phase 7. Telegram bot backend helpers
|
||
|
||
Цель: не переносить Telegram bot runtime на Rust. Python остается постоянным
|
||
production runtime для Telegram polling/sending, proxy, retries и side effects.
|
||
На Rust выносятся только backend helpers, status aggregation, decision/gating и
|
||
безопасные read-only/action contracts, которые бот вызывает как внешние команды.
|
||
|
||
Порядок:
|
||
|
||
1. `[done]` Бот продолжает жить на Python.
|
||
2. `[started]` Команды `/status`, `/detmir`, health summaries начинают читать Rust JSON.
|
||
Первым вынесен `tsj-guardian-status`: read-only helper для строки
|
||
`detmir_auto` из `/var/lib/detmir-ai/latest-state.json`; вторым шагом тот же
|
||
helper начал рендерить `aw_rus_slo` из `AW_RUS_SLO_SUMMARY_CMD`.
|
||
3. Recovery/actions вызывают Rust binaries с dry-run/audit.
|
||
4. `[decision]` Bot runtime на Rust не переносится; новые улучшения Telegram
|
||
делаются в Python runtime или через Rust backend helpers.
|
||
|
||
Почему так:
|
||
|
||
- Telegram runtime, proxy, retries и async edge cases уже стабильно покрыты
|
||
Python-кодом и тестами;
|
||
- перенос runtime даст мало пользы и высокий риск регрессий;
|
||
- максимальная польза от Rust сначала в backend-командах и safety contracts.
|
||
|
||
Гейт:
|
||
|
||
- `/status` показывает те же строки или лучше;
|
||
- smoke message проходит;
|
||
- watchdog активен;
|
||
- restart bot не теряет config/secrets;
|
||
- секреты не попадают в logs/tests.
|
||
|
||
### Phase 8. Deploy/install scripts
|
||
|
||
Цель: переносить последними, только после стабилизации runtime binaries.
|
||
|
||
Что может остаться не на Rust:
|
||
|
||
- Ansible playbooks;
|
||
- Windows PowerShell collector deployment;
|
||
- Playwright browser smoke;
|
||
- packaging scripts, если они надежны и редко исполняются.
|
||
|
||
Что имеет смысл перенести:
|
||
|
||
- validation CLI;
|
||
- install-kit consistency checker;
|
||
- local report generators;
|
||
- deterministic packaging helpers.
|
||
|
||
Гейт:
|
||
|
||
- install-kit output byte/content expected;
|
||
- rollback installer path documented;
|
||
- production deploy не зависит от laptop-only paths.
|
||
|
||
## 7. Deployment workflow
|
||
|
||
Сборка:
|
||
|
||
```bash
|
||
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian/adk-rust
|
||
cargo fmt --all -- --check
|
||
cargo clippy --workspace --all-targets -- -D warnings
|
||
cargo test --workspace
|
||
cargo build --release --workspace
|
||
```
|
||
|
||
Установка одного бинарника на Proxmox:
|
||
|
||
```bash
|
||
install -o root -g root -m 0755 \
|
||
target/release/<binary> \
|
||
/usr/local/bin/<binary>
|
||
```
|
||
|
||
Через Ansible:
|
||
|
||
```bash
|
||
ansible proxmox -i ansible/inventory.ini -m copy -a \
|
||
'src=/mnt/usb_hdd2/Projects/ActivityWatch-Russian/adk-rust/target/release/<binary> dest=/usr/local/bin/<binary> owner=root group=root mode=0755 backup=yes'
|
||
```
|
||
|
||
Проверка на сервере:
|
||
|
||
```bash
|
||
<binary> --version || true
|
||
<binary> --help
|
||
<binary> <safe-read-only-command> --json
|
||
systemctl --failed
|
||
```
|
||
|
||
## 8. Rollback workflow
|
||
|
||
Для каждого заменяемого компонента должен быть rollback:
|
||
|
||
1. Старый script остается на сервере как `<name>.legacy` или в package backup.
|
||
2. systemd unit меняется через drop-in или controlled template.
|
||
3. Перед switch:
|
||
- сохранить `systemctl cat <unit>`;
|
||
- сохранить checksum старого binary/script;
|
||
- сохранить latest known good command.
|
||
4. Rollback command документируется в PR/run note.
|
||
|
||
Текущий rollback для `detmir-auto` после Rust switch:
|
||
|
||
```bash
|
||
sudo rm -f /etc/systemd/system/detmir-auto.service.d/20-rust-switch.conf
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl restart detmir-auto.service
|
||
```
|
||
|
||
Перед switch сохранены:
|
||
|
||
```bash
|
||
/var/lib/detmir-ai/switch-backups/detmir-auto.service.before-rust-20260531-192809.txt
|
||
/var/lib/detmir-ai/switch-backups/detmir-auto.sha256.before-rust-20260531-192809.txt
|
||
```
|
||
|
||
Пример:
|
||
|
||
```bash
|
||
cp -a /usr/local/bin/detmir-check /usr/local/bin/detmir-check.legacy
|
||
install -o root -g root -m 0755 target/release/detmir-check /usr/local/bin/detmir-check
|
||
detmir-check --json
|
||
```
|
||
|
||
Если новый binary провален:
|
||
|
||
```bash
|
||
mv /usr/local/bin/detmir-check.legacy /usr/local/bin/detmir-check
|
||
systemctl restart detmir-auto.timer || true
|
||
```
|
||
|
||
## 9. Acceptance checklist
|
||
|
||
Перед заменой legacy-команды:
|
||
|
||
- [ ] old/new CLI documented;
|
||
- [ ] old/new JSON compared on real fixture;
|
||
- [ ] exit codes match by class;
|
||
- [ ] unit tests pass;
|
||
- [ ] integration smoke pass on target host;
|
||
- [ ] no secrets in logs;
|
||
- [ ] systemd environment tested;
|
||
- [ ] rollback command known;
|
||
- [ ] docs updated;
|
||
- [ ] operator command example added.
|
||
|
||
Перед включением mutation/recovery:
|
||
|
||
- [ ] `--dry-run` verified;
|
||
- [ ] allowlist verified;
|
||
- [ ] lock verified;
|
||
- [ ] cooldown verified;
|
||
- [ ] audit log verified;
|
||
- [ ] green contour causes no action;
|
||
- [ ] failing contour causes only intended action;
|
||
- [ ] post-action check verified;
|
||
- [ ] rollback tested.
|
||
|
||
## 10. Quality gates
|
||
|
||
Локально:
|
||
|
||
```bash
|
||
cd adk-rust
|
||
cargo fmt --all -- --check
|
||
cargo clippy --workspace --all-targets -- -D warnings
|
||
cargo test --workspace
|
||
```
|
||
|
||
Production readiness checklist:
|
||
|
||
- [ ] public defaults are still sanitized: no live hosts, IPs, domains, tokens,
|
||
evidence paths, case IDs, hashes, or operator home paths in tracked files;
|
||
- [ ] live values are supplied only through private inventory/env or server-side
|
||
runtime env;
|
||
- [ ] enabled Influx exporters have live URL, org, bucket, token and hosts;
|
||
- [ ] private production config passes:
|
||
|
||
```bash
|
||
scripts/check_production_inventory_placeholders.sh private-config/runtime.env private-config/ansible-vars.yml
|
||
```
|
||
|
||
Run this guard only on private production override files. Public tracked
|
||
default/example files may intentionally contain `HOST-EXAMPLE` and TEST-NET
|
||
values for release hygiene.
|
||
|
||
- [ ] `ansible-playbook -i ansible/inventory.ini ansible/deploy_aw_server.yml --syntax-check` passes;
|
||
- [ ] `aw-worktime-influx-exporter.service` and `aw-dlp-influx-exporter.service`
|
||
complete once and write points;
|
||
- [ ] `detmir-readiness --json` returns `status=OK`;
|
||
- [ ] readiness bundle is generated with
|
||
`--output-dir /var/lib/activitywatch/health/readiness-bundle`;
|
||
- [ ] readiness bundle integrity passes:
|
||
|
||
```bash
|
||
cd /var/lib/activitywatch/health/readiness-bundle
|
||
sha256sum -c sha256sums.txt
|
||
openssl dgst -sha256 -verify public-key.pem \
|
||
-signature sha256sums.txt.sig sha256sums.txt
|
||
```
|
||
|
||
- [ ] readiness signing public-key fingerprint for the shipped/customer
|
||
contour is fixed and documented:
|
||
|
||
```text
|
||
READINESS_PUBLIC_KEY_SHA256=<READINESS_PUBLIC_KEY_SHA256_FINGERPRINT>
|
||
```
|
||
|
||
Verification command:
|
||
|
||
```bash
|
||
sha256sum /var/lib/activitywatch/health/readiness-bundle/public-key.pem
|
||
jq -r '.signature.public_key_fingerprint_sha256' \
|
||
/var/lib/activitywatch/health/readiness-bundle/detmir-readiness-status.json
|
||
```
|
||
|
||
- [ ] `detmir-readiness-status.json` and `detmir-readiness.prom` expose the
|
||
latest OK/WARN/FAIL state independently from systemd unit result;
|
||
- [ ] `detmir-readiness.timer` is enabled for daily bundle generation;
|
||
- [ ] DetMir portal readiness endpoints return latest/bundle/verify data;
|
||
- [ ] `detmir-check --json`, `detmir-status --json` and Grafana check are green;
|
||
- [ ] rollback path for changed binaries/env files is known.
|
||
|
||
Full readiness procedure is documented in `docs/PRODUCTION_READINESS_RU.md`.
|
||
|
||
На Proxmox:
|
||
|
||
```bash
|
||
detmir-readiness --json
|
||
detmir-status --json
|
||
detmir-check --json
|
||
detmir-dlp
|
||
detmir-auto --no-heal
|
||
systemctl --failed
|
||
```
|
||
|
||
На AW server:
|
||
|
||
```bash
|
||
aw-rus-healthd --json
|
||
dlp-health-check --json
|
||
/usr/local/bin/aw-slo-monitor-rust --state-dir /var/lib/activitywatch/slo-rust-shadow --json
|
||
systemctl --failed
|
||
```
|
||
|
||
Для Telegram:
|
||
|
||
```bash
|
||
systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
|
||
```
|
||
|
||
## 11. Приоритет ближайших работ
|
||
|
||
Немедленный порядок:
|
||
|
||
1. `[done]` Создать `detmir-core`, `detmir-state`, `detmir-aw-client`.
|
||
2. `[done]` Объединить `detmir-status` и `detmir-adk-status` в один Rust binary.
|
||
3. `[done]` Перенести `detmir-check`.
|
||
4. `[done]` Сделать shadow compare `detmir-check` old/new.
|
||
5. `[done]` Перенести `detmir-dlp` wrapper.
|
||
6. `[done]` Начать `detmir-auto --no-heal`.
|
||
7. `[done]` Накопить scheduled shadow/prod parity для `detmir-auto-rust`, затем выполнить controlled switch.
|
||
8. `[done]` Перенести `detmir-heal-safe` с dry-run/apply guardrails и подключить к Rust auto.
|
||
9. `[done]` Наблюдать timer цикл Rust production и отключить redundant shadow timer.
|
||
10. `[done]` Перенести `tsj-guardian-watchdog.sh` на Rust service loop.
|
||
11. `[done]` Перенести AW-RUS SLO monitor/summary path на Rust:
|
||
`aw-slo-monitor-rust` пишет `aw-slo-samples.jsonl`,
|
||
`aw-slo-summary.json` и `aw-slo-summary.txt`; production включен через
|
||
`/etc/systemd/system/aw-slo-monitor.service.d/20-rust-switch.conf`.
|
||
12. `[done]` Выполнить controlled correction старых ложных SLO bad-сэмплов:
|
||
backup создан, 133 записи с единственной причиной
|
||
`worktime_today_html body too small ... < 5000` исправлены, 59
|
||
не-ложных/неоднозначных bad-сэмплов оставлены в истории.
|
||
13. `[done]` Вынести Telegram `/status` aggregation layer в Rust:
|
||
`tsj-guardian-status --status-text` собирает полный текст статуса, Python
|
||
runtime Telegram оставлен и использует старую сборку только как fallback.
|
||
14. `[done]` Вынести incident/escalation decision path в Rust:
|
||
`tsj-guardian-status` принимает JSON через stdin и решает suggestions,
|
||
transient quorum/defer и timeout escalation/fallback. Python runtime
|
||
выполняет только side effects и сохраняет fallback на старую логику.
|
||
15. `[done]` Вынести operator action routing/gating в Rust:
|
||
`tsj-guardian-status --operator-action-decision` нормализует action aliases,
|
||
назначает handler/risk, блокирует неизвестные action и защищает
|
||
update install/rollback confirm без pending-запроса. Python runtime
|
||
продолжает выполнять команды и сохраняет fallback.
|
||
16. `[done]` Вынести DLP policy/mode decision path в Rust:
|
||
`tsj-guardian-status --dlp-policy-decision` определяет monitor/enforce/mixed,
|
||
считает block/total по endpoint groups, строит toggle/target policy plan и
|
||
changed_rules. Python runtime продолжает выполнять API PUT и Windows policy
|
||
sync, fallback на старую Python-логику сохранен.
|
||
17. `[done]` Вынести confirmation state machines в Rust:
|
||
`tsj-guardian-status --confirmation-decision` решает TTL/expire, cancel,
|
||
first_confirm и apply code/stage validation для pfSense, OpenVPN,
|
||
Proxmox selection и Proxmox restore. Python runtime продолжает создавать
|
||
pending-запросы и выполнять side effects после `allowed=true`, fallback на
|
||
старую Python-валидацию сохранен.
|
||
18. `[done]` Вынести autoheal/action plan extraction в Rust:
|
||
`tsj-guardian-status --autoheal-plan-decision` классифицирует AW-Rus
|
||
failures в Windows collector heal, server-side DLP heal, worktime rebuild,
|
||
SLO no-direct-target и задержку перед повторной проверкой. Python runtime
|
||
продолжает выполнять recovery-команды и повторный probe, fallback на старую
|
||
Python-маршрутизацию сохранен. Production deploy проверен на Proxmox:
|
||
sample decisions OK, `tsj-guardian-bot`, `tsj-guardian-watchdog` и
|
||
`gost-tg` active, `detmir-status` OK / `ok_for_operator=true`, свежий
|
||
journal после правильной выкладки helper без повторных unexpected-argument
|
||
warnings.
|
||
19. `[done]` Укрепить deploy artifacts после миграции:
|
||
`deploy_aw_server.yml` переведен на единый `aw_rust_release_dir` для Rust
|
||
binary `stat`/`copy`, чтобы `CARGO_TARGET_DIR` работал одинаково для
|
||
Proxmox/Telegram и AW-server deploy. Добавлен
|
||
`scripts/check_detmir_rust_release_artifacts.sh`; проверено
|
||
`CARGO_TARGET_DIR=<OPERATOR_HOME>/.cache/detmir-adk-rust-target cargo build
|
||
--release --workspace`, artifact check OK, `deploy_aw_server.yml
|
||
--syntax-check` OK.
|
||
20. `[done]` Прогнать production AW-server deploy contract и закрыть найденные
|
||
эксплуатационные дефекты:
|
||
- Influx tokens для deploy берутся из окружения; при восстановлении с
|
||
сервера значения не печатать.
|
||
- Legacy DB merge выключен по умолчанию через
|
||
`aw_legacy_db_merge_enabled: false`, потому что backup SQLite на
|
||
12G root FS уперся в `No space left on device`. Повторять merge только
|
||
после отдельного disk plan.
|
||
- Browser smoke по умолчанию принудительно использует `chromium-cli`;
|
||
`node-playwright` на текущем Node ломается `rimraf: callback function
|
||
required`.
|
||
- `aw-rus-healthd-rust` теперь проверяет AW interactive buckets по
|
||
`metadata.end`, а event buckets выбирают свежайшее событие из окна
|
||
`limit=20` с учетом `timestamp + duration`; это устраняет ложные
|
||
stale/warn на длинных ActivityWatch событиях и guard heartbeat ordering.
|
||
- После reset runtime budget `AWatchRusCollectorGuard` и однократного
|
||
перезапуска AW watcher tasks production verification зеленый:
|
||
`aw-rus-healthd-rust` `ok=14 warn=0 fail=0`, `detmir-status` OK,
|
||
`systemctl --failed` на AW и Proxmox пусто, `deploy_aw_server.yml
|
||
--syntax-check` OK.
|
||
21. `[done]` Выполнить безопасную уборку AW app data и защитить timers от
|
||
лавинного восстановления:
|
||
- удалены только старые browser-smoke run-директории, manual profile,
|
||
неиспользуемый `/opt/activitywatch/releases/v0.13.2` и неиспользуемый
|
||
`/opt/activitywatch/aw-rus-ops/venv`;
|
||
- текущий `aw-server-rust-v0.13.2`, SQLite DB/WAL/SHM и rollback-critical
|
||
backups не удалялись;
|
||
- root FS улучшен примерно с `96%` до `85%`;
|
||
- AW timers переведены на staggered `OnCalendar`, чтобы после ручного
|
||
restart/daemon-reload не запускать все missed persistent jobs одной
|
||
пачкой;
|
||
- legacy `aw_to_influx_exporter.service` удален из systemd, потому что
|
||
production уже использует Rust DLP/worktime Influx exporters;
|
||
- `aw-rus-healthd-rust` получил timeout для wrapper-команд, чтобы
|
||
`aw-health-check`/`dlp-health-check` не могли подвесить healthd.
|
||
22. `[done]` Перенести DLP content analyzer text path в Rust:
|
||
- добавлен `dlp-content-analyzer` crate;
|
||
- CLI совместим с `aw-dlp-content-analyzer --text --dictionary-pack
|
||
--regex-pack`;
|
||
- реализованы dictionary/regex pack matching и checksum validators для
|
||
ИНН, СНИЛС, паспорта РФ;
|
||
- позиции совпадений считаются в Python-compatible character offsets, не
|
||
byte offsets;
|
||
- image/OCR path оставлен через legacy Python fallback, чтобы не тащить
|
||
OCR/Tesseract в Rust на этом шаге;
|
||
- `/usr/local/bin/aw-dlp-content-analyzer` теперь wrapper, который
|
||
предпочитает `/usr/local/bin/aw-dlp-content-analyzer-rust`, но сохраняет
|
||
Python fallback;
|
||
- parity на AW server проверен на `152-fz-pdn`, `contacts`, `secrets`,
|
||
`financial`: match counts, values и offsets совпали;
|
||
- artifact check расширен `dlp-content-analyzer`.
|
||
23. `[done]` Перенести DLP admin CLI в Rust:
|
||
- добавлен `dlp-admin-cli` crate;
|
||
- сохранены команды `policies list/active`, `incidents list`,
|
||
`cases list/create`, `health check`;
|
||
- HTTP client использует no-proxy для локальных AW/DLP сервисов;
|
||
- `/usr/local/bin/dlp-admin-cli` установлен как Rust production CLI,
|
||
legacy `/usr/local/bin/dlp-admin-cli.py` больше не является repo/runtime
|
||
path; рабочий CLI: `/usr/local/bin/dlp-admin-cli`;
|
||
- parity на AW server совпал с Python для `health`, `policies active`,
|
||
`incidents list --since-hours 24 --limit 5`, `cases list --limit 5`;
|
||
- production verification зеленый: AW failed units 0,
|
||
`aw-rus-healthd-rust` 14/0/0, `dlp-health-check` 22/0/0,
|
||
`detmir-status` OK, `detmir-check` OK, Proxmox failed units 0;
|
||
- artifact check расширен `dlp-admin-cli`.
|
||
24. `[done]` Перенести DLP policy engine service в Rust:
|
||
- добавлен `dlp-policy-engine` crate;
|
||
- сохранены существующая SQLite schema/DB и API: policies
|
||
list/create/get/update/delete, active bundle/version,
|
||
submit/approve/draft/activate, rollback, agent heartbeat/desired,
|
||
audit;
|
||
- checksum policy совместим с Python:
|
||
`json.dumps(..., ensure_ascii=False, sort_keys=True,
|
||
separators=(",", ":"))` + SHA-256;
|
||
- env contract сохранен:
|
||
`AW_DLP_POLICY_ENGINE_BIND_HOST`, `AW_DLP_POLICY_ENGINE_PORT`,
|
||
`AW_DLP_POLICY_ENGINE_DB_PATH`;
|
||
- shadow parity на AW server совпал с Python для list/active/version и
|
||
agent heartbeat/desired;
|
||
- Rust исправляет documented `/api/0/dlp/policies/audit?limit=N`: legacy
|
||
FastAPI возвращал 422 из-за route-order конфликта с `{policy_id}`;
|
||
- production `/usr/local/bin/aw-dlp-policy-engine-rust` подключен через
|
||
`/etc/systemd/system/aw-dlp-policy-engine.service.d/20-rust-switch.conf`;
|
||
- rollback: удалить этот drop-in, `systemctl daemon-reload`, restart
|
||
`aw-dlp-policy-engine.service`;
|
||
- production verification зеленый: active policy `default-policy` v6,
|
||
audit endpoint OK, AW failed units 0, `aw-rus-healthd-rust` 14/0/0,
|
||
`dlp-health-check` 22/0/0, `detmir-status` OK, `detmir-check` OK,
|
||
Proxmox failed units 0;
|
||
- artifact check расширен `dlp-policy-engine`.
|
||
25. `[done]` Перенести DLP case management service в Rust:
|
||
- добавлен `dlp-case-management` crate;
|
||
- сохранены существующая SQLite schema/DB и API: `/health`, cases
|
||
list/create/get/update, comments list/create, Hayabusa forensics link;
|
||
- сохранены self-test rejection и evidence SHA-256 contract:
|
||
`json.dumps(..., ensure_ascii=False, sort_keys=True,
|
||
separators=(",", ":"))` + SHA-256;
|
||
- env contract сохранен:
|
||
`AW_DLP_CASE_BIND_HOST`, `AW_DLP_CASE_PORT`, `AW_DLP_CASE_DB_PATH`;
|
||
- local HTTP smoke на temp DB проверил create/list/patch/comment/hayabusa
|
||
link/self-test reject;
|
||
- shadow parity на AW server совпал с Python для health, case list/filter,
|
||
case detail и comments на копии production DB;
|
||
- production `/usr/local/bin/aw-dlp-case-management-rust` подключен через
|
||
`/etc/systemd/system/aw-dlp-case-management.service.d/20-rust-switch.conf`;
|
||
- rollback: удалить этот drop-in, `systemctl daemon-reload`, restart
|
||
`aw-dlp-case-management.service`;
|
||
- production verification зеленый: case list OK/latest id 35,
|
||
AW failed units 0, `aw-rus-healthd-rust` 14/0/0, `dlp-health-check`
|
||
22/0/0, `detmir-status` OK, `detmir-check` OK, Proxmox failed units 0;
|
||
- artifact check расширен `dlp-case-management`.
|
||
26. `[done]` Перенести DLP compliance report scheduler/generator в Rust:
|
||
- добавлен `dlp-compliance` crate;
|
||
- сохранены CLI `--month`, `--profile`, `--profiles`, `--stdout-json`;
|
||
- сохранены env fallback для AW API: `AW_DLP_AW_API_BASE`,
|
||
`AW_SERVER_URL`, default `http://127.0.0.1:5600`;
|
||
- сохранены output/template envs и artifact names:
|
||
`<profile>-<YYYY-MM>.html/json`;
|
||
- shadow parity на AW server в temp output dirs совпал с Python для
|
||
`152-fz,pci-dss`: profile/period/aw_api_base/stats и наличие artifacts;
|
||
- production `/usr/local/bin/aw-dlp-compliance-rust` подключен через
|
||
`/etc/systemd/system/aw-dlp-report-scheduler.service.d/20-rust-switch.conf`;
|
||
- rollback: удалить этот drop-in и `systemctl daemon-reload`;
|
||
- production oneshot run успешен: `152-fz-2026-06` и `pci-dss-2026-06`
|
||
html/json созданы, timer active, AW failed units 0,
|
||
`aw-rus-healthd-rust` 14/0/0, `dlp-health-check` 22/0/0,
|
||
`detmir-status` OK, `detmir-check` OK, Proxmox failed units 0;
|
||
- artifact check расширен `dlp-compliance`.
|
||
27. `[done]` Перенести Hayabusa case alert/link helpers в Rust:
|
||
- добавлен `hayabusa-tools` crate с бинарниками
|
||
`aw-hayabusa-case-alert-rust` и `aw-hayabusa-link-case-rust`;
|
||
- сохранены CLI/env контракты legacy helpers:
|
||
`--case-id`, `--intake-json`, `--case-api-base`, `--mode`,
|
||
`--link-source`, `AW_HAYABUSA_*`;
|
||
- `case-alert` сохраняет scoring/severity, `top_rules`, comment text,
|
||
case create/update/link path и Telegram alert text;
|
||
- Telegram request errors в Rust не печатают bot token/URL;
|
||
- `/usr/local/bin/aw-hayabusa-case-alert` и
|
||
`/usr/local/bin/aw-hayabusa-link-case` теперь wrapper-команды, которые
|
||
предпочитают Rust binaries и fallback на Python из
|
||
`/opt/activitywatch/aw-rus-ops/hayabusa`;
|
||
- shadow compare на реальном `/opt/hayabusa/state/latest-intake.json` с
|
||
`AW_HAYABUSA_AUTO_CASE_ENABLED=false` и
|
||
`AW_HAYABUSA_TELEGRAM_ENABLED=false` дал `summary_equal=true`;
|
||
- production verification зеленый: AW failed units 0,
|
||
`aw-rus-healthd-rust` 14/0/0, `dlp-health-check` 22/0/0,
|
||
`detmir-status` OK, Proxmox failed units 0;
|
||
- artifact check расширен `aw-hayabusa-case-alert-rust` и
|
||
`aw-hayabusa-link-case-rust`.
|
||
28. `[done]` Перенести remaining Hayabusa offline helpers в Rust:
|
||
- `hayabusa-tools` расширен бинарниками
|
||
`aw-hayabusa-from-windows-rust` и `aw-hayabusa-autoprocess-rust`;
|
||
- `from-windows` сохраняет server-side workflow:
|
||
Ansible WinRM export, latest zip discovery, fetch в drop-dir,
|
||
`aw-hayabusa accept`, `process-inbox`, optional link-case и печать
|
||
`LATEST_INTAKE`;
|
||
- `autoprocess` сохраняет drop-dir scanner, lock file,
|
||
`.caseid`/`.meta.json` sidecars, host/mode/link_source inference,
|
||
case-alert capture, sidecar/archive move в report dir;
|
||
- `/usr/local/bin/aw-hayabusa-from-windows` и
|
||
`/usr/local/bin/aw-hayabusa-autoprocess` теперь wrapper-команды,
|
||
которые предпочитают Rust и fallback на Python из
|
||
`/opt/activitywatch/aw-rus-ops/hayabusa`;
|
||
- `aw-hayabusa-drop.service` теперь запускает
|
||
`/usr/local/bin/aw-hayabusa-autoprocess` напрямую, без `python3`, чтобы
|
||
wrapper мог выбрать Rust;
|
||
- no-mutation smoke на AW server: `--help` для всех Hayabusa helpers,
|
||
empty drop-dir для `autoprocess`, `systemctl cat` ExecStart OK;
|
||
- production verification зеленый: AW failed units 0,
|
||
`aw-rus-healthd-rust` 14/0/0, `dlp-health-check` 22/0/0,
|
||
`detmir-status` OK, Proxmox failed units 0;
|
||
- artifact check расширен `aw-hayabusa-from-windows-rust` и
|
||
`aw-hayabusa-autoprocess-rust`.
|
||
29. `[done]` Перенести AW ops helper `aw-health-check` в Rust:
|
||
- добавлен crate `aw-health-check`;
|
||
- сохранены проверки legacy shell: systemd services/timer,
|
||
ActivityWatch `/api/0/info`, Worktime `/health`, DLP transport
|
||
freshness через `dlp-health-check --json`, и drift checks для
|
||
`startOfDay`, `always_active_pattern`, `landingpage`;
|
||
- env contract читается из процесса и `/etc/activitywatch/aw-server.env`;
|
||
- production `/usr/local/bin/aw-health-check` теперь Rust-required wrapper:
|
||
вызывает `/usr/local/bin/aw-health-check-rust` и падает с ошибкой, если
|
||
Rust binary отсутствует;
|
||
- shadow parity на AW server совпал с shell по exit code и ключевым
|
||
health-строкам;
|
||
- production verification зеленый: `aw-health-check` OK,
|
||
`aw-rus-healthd-rust` 14/0/0, `dlp-health-check` 22/0/0,
|
||
`detmir-status` OK, `ok_for_operator=true`, failed units 0;
|
||
- artifact check расширен `aw-health-check`.
|
||
30. `[done]` Перенести AW ops helper `check-aw-data` в Rust:
|
||
- добавлен crate `check-aw-data`;
|
||
- корневой `check-aw-data.sh` стал Rust-first wrapper с fallback на
|
||
`scripts/legacy/check-aw-data.sh`;
|
||
- на AW server production `/usr/local/bin/check-aw-data` теперь wrapper,
|
||
который предпочитает `/usr/local/bin/check-aw-data-rust`, по умолчанию
|
||
использует `http://127.0.0.1:5600` и fallback на
|
||
`/opt/activitywatch/aw-rus-ops/check-aw-data.sh`;
|
||
- сохранены операторские статусы `FRESH`, `STALE`, `DEAD`, `EMPTY`,
|
||
`EVENT-DRIVEN`, `INACTIVE` и CORS probe;
|
||
- быстрый режим по умолчанию использует `metadata.end` из
|
||
`/api/0/buckets`; глубокое чтение event ids включается флагом
|
||
`--with-event-ids`;
|
||
- production verification: `/usr/local/bin/check-aw-data --no-color`
|
||
завершился за 2s, buckets fresh/event-driven/inactive as expected,
|
||
`CORS: OK (HTTP 200)`;
|
||
- final gates зеленые: AW failed units 0, `aw-rus-healthd-rust` 14/0/0,
|
||
`dlp-health-check` 22/0/0, `detmir-auto --no-heal` OK,
|
||
`detmir-status` OK и `ok_for_operator=true`;
|
||
- artifact check расширен `check-aw-data`.
|
||
31. `[done]` Перенести AW maintenance helper `aw-prune-local-state` в Rust:
|
||
- добавлен crate `aw-prune-local-state`;
|
||
- `/usr/local/bin/aw-prune-local-state.sh` теперь Rust-first wrapper,
|
||
который без аргументов запускает Rust binary в legacy-compatible
|
||
`--apply` режиме и fallback на shell из
|
||
`/opt/activitywatch/aw-rus-ops/aw-prune-local-state.sh`;
|
||
- Rust binary по умолчанию работает как dry-run, поддерживает `--json`,
|
||
`--apply`, retention/keep параметры и строгий allowlist путей;
|
||
- safety policy: не удалять SQLite DB вне `backups/db`, не удалять
|
||
`switch-backups`, `before-rust`, `rollback` и корневые state каталоги;
|
||
- cleanup расширен на старые `browser-smoke` run-директории как project
|
||
app data; rollback-critical backups и AW SQLite DB не трогались;
|
||
- production recovery во время проверки: rootfs AW server был 100%
|
||
заполнен, `aw-server-rust` попал в `poisoned lock`; через Rust helper
|
||
удалены старые browser-smoke runs на 347 MB суммарно, затем
|
||
`activitywatch-server.service` восстановлен без удаления DB;
|
||
- final gates зеленые: AW failed units 0, `aw-rus-healthd-rust` 14/0/0,
|
||
`dlp-health-check` 22/0/0, `check-aw-data` OK, `detmir-auto --no-heal`
|
||
OK, `detmir-status` OK и `ok_for_operator=true`;
|
||
- Telegram bot/watchdog временно остановлены по операторской команде и не
|
||
перезапускались в рамках этого шага;
|
||
- artifact check расширен `aw-prune-local-state`.
|
||
32. `[done]` Устранить нехватку места на AW server через Proxmox resize:
|
||
- CT `203` (`aw-server`, `<AW_SERVER_HOST>`) rootfs расширен через Proxmox
|
||
`pct resize 203 rootfs +20G`;
|
||
- перед resize сохранен config backup:
|
||
`/var/lib/detmir-ai/switch-backups/ct203-aw-server.before-rootfs-resize-20260602T054826Z.conf`;
|
||
- rootfs изменился с `12G` на `32G`, свободное место стало около `20G`;
|
||
- Proxmox storage `local-btrfs` после resize имеет около `106G`
|
||
доступного места;
|
||
- AW `/api/0/info` и `/api/0/buckets` проверены с AW server и Proxmox;
|
||
- final gates зеленые: AW health 14/0/0, DLP health 22/0/0,
|
||
`detmir-check` service_warnings=0, `detmir-auto --no-heal` OK,
|
||
`detmir-status` OK и `ok_for_operator=true`, failed units 0;
|
||
- Telegram bot/watchdog оставлены `inactive` по операторской команде.
|
||
33. `[done]` Остановить неконтролируемый рост `aw-session-events`:
|
||
- read-only SQLite audit показал, что `/var/lib/activitywatch/aw-server-rust/sqlite.db`
|
||
занимает около `6.8G`, `freelist_count=0`; VACUUM сам по себе не
|
||
освободит место, потому что размер занят live events;
|
||
- основной источник роста: `aw-session-events_HOST-EXAMPLE` - около
|
||
`6.9M` строк и `~5GB` payload, с пиками `1.2M-2.4M` process-level
|
||
событий в сутки за 2026-05-29..2026-06-01;
|
||
- production live config на RDP был `pollSeconds=5` и
|
||
`sessionEvents.processEventsEnabled=true`;
|
||
- отключена постоянная process-level публикация:
|
||
`aw_windows_process_events_enabled=false` в production/example/default
|
||
Ansible vars и `sessionEvents.processEventsEnabled=false` в live
|
||
`C:\ProgramData\AWatch-rus\deployment-config.json`;
|
||
- live config backup сохранен на RDP:
|
||
`C:\ProgramData\AWatch-rus\switch-backups\deployment-config.before-disable-process-events-20260602T061836Z.json`;
|
||
- старый `worktime-session-collector.ps1` PID `8476` остановлен, collector
|
||
поднят заново штатными `ActivityWatch Launch [...]` tasks/guard;
|
||
- delta-gate: `metadata.end` bucket `aw-session-events_HOST-EXAMPLE`
|
||
остался `2026-06-02T06:27:11.197Z` через 75 секунд, постоянный поток
|
||
остановлен;
|
||
- базовый сбор не сломан: `detmir-check --json` OK,
|
||
`detmir-status` severity OK, AW failed units 0, Proxmox failed units 0,
|
||
AW API и Worktime API отвечают;
|
||
- SQLite row deletion/compact не выполнялись на этом этапе: сначала
|
||
стабилизирован источник роста, retention/trim старых process events -
|
||
отдельный destructive этап с backup.
|
||
34. `[done]` Controlled trim старых process-level `aw-session-events`:
|
||
- перед mutation сохранен полный rollback backup SQLite DB/WAL/SHM:
|
||
`/var/lib/activitywatch/backups/db/aw-sqlite-before-session-events-trim-20260602T064127Z`;
|
||
- на время операции остановлены AW-related timers/services и
|
||
`activitywatch-server.service`, чтобы не было writer'ов к SQLite;
|
||
- scoped delete удалил только события bucket
|
||
`aw-session-events_HOST-EXAMPLE` с `eventType=process_start` или
|
||
`eventType=process_stop`;
|
||
- удалено `6,906,190` шумных process-level событий;
|
||
- сохранены logon events: после trim в `aw-session-events_HOST-EXAMPLE`
|
||
осталось `174` события, recent samples имеют `eventType=logon`;
|
||
- `PRAGMA integrity_check` до и после `VACUUM`: `ok`;
|
||
- DB уменьшилась с `6.8G` до `350M`, rootfs AW server вернулся к
|
||
`38%` использования и около `19G` free;
|
||
- после restart AW был `poisoned lock` из-за timer запросов во время
|
||
старта; выполнен controlled restart при остановленных timers, затем
|
||
`/api/0/info` и `/events?limit=1` вернулись OK;
|
||
- timers включены обратно, failed units на AW и Proxmox: `0`;
|
||
- final gates зеленые: `aw-health-check` OK, `dlp-health-check`
|
||
`22/0/0`, `detmir-check` bucket_ok=8 stale=0 dead=0,
|
||
`detmir-auto --no-heal` OK, `detmir-status` severity OK и
|
||
`ok_for_operator=true`;
|
||
- Telegram bot/watchdog оставлены `inactive` по операторской команде.
|
||
35. `[done]` Добавить Rust guard от повторного роста AW SQLite:
|
||
- добавлен crate `aw-db-health`;
|
||
- helper read-only открывает `/var/lib/activitywatch/aw-server-rust/sqlite.db`
|
||
через SQLite read-only flags и не меняет DB;
|
||
- проверяет:
|
||
- размер `sqlite.db` (`warn=2GiB`, `fail=5GiB` по умолчанию);
|
||
- размер `sqlite.db-wal` (`warn=256MiB`, `fail=1GiB`);
|
||
- число строк `aw-session-events_<host>` (`warn=10000`, `fail=100000`);
|
||
- recent process-level events за окно 600 секунд
|
||
(`warn=1`, `fail=100`);
|
||
- latest `eventType/source` для быстрой диагностики;
|
||
- опционально `--windows-config` и
|
||
`sessionEvents.processEventsEnabled`;
|
||
- поддерживает text output и `--json`;
|
||
- `aw-health-check-rust` теперь вызывает `/usr/local/bin/aw-db-health --json`
|
||
и включает DB growth guard в общий AW health path;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` требует
|
||
`aw-db-health`;
|
||
- `ansible/deploy_aw_server.yml` устанавливает
|
||
`/usr/local/bin/aw-db-health` и обновленный
|
||
`/usr/local/bin/aw-health-check-rust`; установка health helpers
|
||
продублирована до Influx token assert, чтобы DB guard обновлялся даже
|
||
если playbook позже останавливается на пустом внешнем Influx token;
|
||
- production `/usr/local/bin/aw-db-health --json`:
|
||
`ok=true`, `db=352.3MiB`, `wal=4.0MiB`, session rows `174`,
|
||
recent process events `0`, latest `eventType=logon`;
|
||
- production `/usr/local/bin/aw-health-check`: `AW DB growth guard passed`;
|
||
- local gates: `cargo fmt --all -- --check`, `cargo test -p aw-db-health`
|
||
`3 passed`, `cargo test -p aw-health-check` `2 passed`,
|
||
release build OK, artifact check OK;
|
||
- final gates зеленые: `dlp-health-check` `22/0/0`, `detmir-check` OK,
|
||
`detmir-auto --no-heal` OK, `detmir-status` severity OK,
|
||
failed units на AW/Proxmox `0`;
|
||
- Telegram bot/watchdog оставлены `inactive`.
|
||
35.1. `[done]` Добавить автономную guarded чистку AW SQLite:
|
||
- добавлен crate `aw-db-maintenance`;
|
||
- helper dry-run по умолчанию, mutation только с `--apply`;
|
||
- allowlist удаления: только bucket `aw-session-events_<host>` и только
|
||
события `eventType=process_start` или `eventType=process_stop`;
|
||
- logon/session events не удаляются;
|
||
- retention по умолчанию `7` дней;
|
||
- перед удалением создается SQLite backup в
|
||
`/var/lib/activitywatch/backups/db/aw-sqlite-before-db-maintenance-*.db`;
|
||
если строк к удалению нет, backup не создается и DB не пишется;
|
||
- удаление chunked, без `VACUUM`, чтобы не создавать длительный downtime;
|
||
- добавлены `aw-server/aw-db-maintenance.service` и
|
||
`aw-server/aw-db-maintenance.timer`;
|
||
- timer включен на AW server: `OnCalendar=Sun *-*-* 03:30:00`,
|
||
`RandomizedDelaySec=15m`, `Persistent=true`;
|
||
- production dry-run: `planned_delete_rows=0`, `deleted_rows=0`;
|
||
- ручной service smoke: `Result=success`, `ExecMainStatus=0`,
|
||
`planned_delete_rows=0`, `deleted_rows=0`, `backup_created=false`;
|
||
- production timer active; ближайший запуск по remote `systemctl
|
||
list-timers`: `Sun 2026-06-07 03:40:05 UTC`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p aw-db-maintenance`
|
||
`3 passed`, `cargo clippy -p aw-db-maintenance --all-targets -- -D
|
||
warnings`, release build OK, artifact check OK, `quality-gate.sh` OK,
|
||
`ansible-playbook deploy_aw_server.yml --syntax-check` OK;
|
||
- final production gates: AW failed units `0`, `aw-db-health` OK
|
||
(`sqlite.db=359.6MiB`, WAL `4.0MiB`, session rows `174`, recent process
|
||
events `0`, latest `eventType=logon`), DetMir status OK with
|
||
`dlp_counts={ok:22,warn:0,fail:0}`, `ok_for_operator=true`.
|
||
36. `[done]` Устранить blocker полного AW server deploy на Influx token:
|
||
- проблема: `deploy_aw_server.yml` падал на assert
|
||
`aw_worktime_influx_enabled=true`, потому что локальные env
|
||
`AW_WORKTIME_INFLUX_TOKEN` и `AW_DLP_INFLUX_TOKEN` пустые;
|
||
- runtime `/etc/activitywatch/aw-server.env` на AW server уже содержал оба
|
||
token, поэтому правильный путь - сохранить remote secrets, а не
|
||
отключать exporters;
|
||
- playbook теперь через `slurp` с `no_log: true` читает текущий
|
||
`/etc/activitywatch/aw-server.env`, выбирает effective token:
|
||
local env token если задан, иначе existing remote token;
|
||
- asserts проверяют effective tokens и не печатают секреты;
|
||
- запись `/etc/activitywatch/aw-server.env` использует effective tokens,
|
||
чтобы full deploy не затирал рабочие Influx credentials пустыми
|
||
значениями;
|
||
- полный `ansible-playbook -i inventory.ini deploy_aw_server.yml
|
||
-e aw_rust_release_dir=<OPERATOR_HOME>/.cache/detmir-adk-rust-target/release`
|
||
прошел до конца: `failed=0`, `ok=282`;
|
||
- final gates после deploy зеленые: `aw-db-health` OK,
|
||
`aw-health-check` OK, `dlp-health-check` `22/0/0`,
|
||
`detmir-check` OK, `detmir-auto --no-heal` OK,
|
||
`detmir-status` severity OK, failed units на AW/Proxmox `0`;
|
||
- Telegram bot/watchdog оставлены `inactive`.
|
||
37. `[done]` Закрепить safe default для Windows `processEventsEnabled`:
|
||
- исправлены Windows deploy/recovery defaults, чтобы новый deploy,
|
||
ensemble/domain deploy, hardening recovery и общий
|
||
`New-ActivityWatchDeploymentConfig` не включали process-level
|
||
`aw-session-events` без явного параметра;
|
||
- изменены `windows/deploy-single-user.ps1`,
|
||
`windows/deploy-ensemble.ps1`, `windows/deploy-domain-users.ps1`,
|
||
`windows/hardening-recovery.ps1`,
|
||
`windows/ActivityWatch.Windows.Common.psm1` и
|
||
`windows/validate-deployment.ps1`;
|
||
- синхронизированы текстовые копии в
|
||
`install-kit-awindows-20260427-211240/windows/`;
|
||
- invariant: existing live config сохраняется, но missing/new config
|
||
default всегда `sessionEvents.processEventsEnabled=false`; включать
|
||
можно только явно и временно для forensic/debug окна;
|
||
- local gates: PowerShell AST parse для измененных scripts/modules OK,
|
||
`ansible-playbook -i inventory.ini deploy_aw_windows.yml --syntax-check`
|
||
OK;
|
||
- production deploy: обновленные PowerShell файлы скопированы в
|
||
`C:\Program Files\AWatch-rus\windows` с UTF-8 BOM для Windows
|
||
PowerShell 5.1; remote backup перед заменой:
|
||
`C:\ProgramData\AWatch-rus\switch-backups\windows-safe-default-20260602T091213Z`;
|
||
- `hardening-recovery.ps1` выполнен по existing
|
||
`C:\ProgramData\AWatch-rus\deployment-config.json`; после recovery
|
||
config остался `processEventsEnabled=false`, `logonEnabled=true`,
|
||
`pollSeconds=5`, users включают `HOST-EXAMPLE\Администратор`;
|
||
- exact task check: `ActivityWatch Launch [HOST-EXAMPLE_Администратор]`
|
||
существует, ошибочный
|
||
`ActivityWatch Launch [HOST-EXAMPLE_Administrator]` отсутствует,
|
||
`ActivityWatch Recovery` существует;
|
||
- `AWatchRusCollectorGuard` running/automatic; Windows
|
||
`validate-deployment.ps1` вернул `overallOk=True`;
|
||
- production AW DB guard: session rows `174`, recent process events `0`,
|
||
latest `eventType=logon`, DB около `353MiB`;
|
||
- final gates зеленые: `check-aw-data` buckets fresh/event-driven OK,
|
||
`aw-health-check` OK, `dlp-health-check` `22/0/0`,
|
||
`detmir-check` bucket_ok `8`, stale `0`, dead `0`,
|
||
`detmir-auto --no-heal` OK, `detmir-status` severity OK и
|
||
`ok_for_operator=true`, failed units на AW/Proxmox `0`;
|
||
- краткий transient после recovery: один прогон `detmir-auto` увидел
|
||
`file-operations sendFailuresDelta`; повтор через 30 секунд показал
|
||
`sendFailuresDelta=0`, поэтому коррекция collectors не потребовалась;
|
||
- Telegram bot/watchdog оставлены `inactive` по принятому решению.
|
||
38. `[done]` Пересобрать и проверить Windows install kit:
|
||
- выполнен штатный `./scripts/rebuild_install_kit.sh`;
|
||
- пересобраны:
|
||
`install-kit-awindows-20260427-211240/`,
|
||
`install-kit-awindows-20260427-211240.zip`,
|
||
`install-kit-awindows-20260427-211240.tar.gz` и `MANIFEST.txt`;
|
||
- `./scripts/check_install_kit_vs_repo.sh`: compared `62`,
|
||
missing `0`, mismatched `0`, PowerShell mismatches `0`;
|
||
- manifest checksum verification: OK, entries `66`;
|
||
- archives verification: zip и tar.gz распакованы во временные каталоги,
|
||
опасные defaults не найдены; подтверждены
|
||
`[bool]$ProcessEventsEnabled = $false`,
|
||
`effectiveProcessEventsEnabled ... else { $false }`,
|
||
`sessionProcessEventsEnabled ... else { $false }`,
|
||
`aw_windows_process_events_enabled: false`;
|
||
- PowerShell AST parse для kit scripts/modules OK;
|
||
- `ansible-playbook -i inventory.example.ini deploy_aw_windows.yml
|
||
--syntax-check` из install-kit ansible каталога OK.
|
||
39. `[done]` Перенести install-kit consistency checker на Rust:
|
||
- добавлен crate `check-install-kit-vs-repo`;
|
||
- `scripts/check_install_kit_vs_repo.sh` теперь Rust-first wrapper:
|
||
ищет `CHECK_INSTALL_KIT_VS_REPO_RUST`,
|
||
`$CARGO_TARGET_DIR/release/check-install-kit-vs-repo`,
|
||
`adk-rust/target/release/check-install-kit-vs-repo`,
|
||
`/usr/local/bin/check-install-kit-vs-repo`, затем использует Python
|
||
fallback;
|
||
- сохранен текстовый output contract:
|
||
`Compared files`, `Missing in repo`, `Mismatched content`,
|
||
`PowerShell mismatches`; добавлен `--json`;
|
||
- поведение усилено: missing/mismatch теперь дают non-zero exit code,
|
||
чтобы рассинхрон install-kit не проходил молча;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` теперь требует
|
||
`check-install-kit-vs-repo`;
|
||
- после обновления wrapper выполнен `./scripts/rebuild_install_kit.sh`,
|
||
чтобы install kit нес актуальный Rust-first wrapper;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
check-install-kit-vs-repo` (`3 passed`), `cargo clippy -p
|
||
check-install-kit-vs-repo --all-targets -- -D warnings`, release build
|
||
OK, `./scripts/check_install_kit_vs_repo.sh` OK, negative mismatch smoke
|
||
rc `1`, `./scripts/validate_install_kit.sh` OK, artifact check OK.
|
||
40. `[done]` Перенести install-kit validator на Rust:
|
||
- добавлен crate `validate-install-kit`;
|
||
- `scripts/validate_install_kit.sh` теперь Rust-first wrapper:
|
||
ищет `VALIDATE_INSTALL_KIT_RUST`,
|
||
`$CARGO_TARGET_DIR/release/validate-install-kit`,
|
||
`adk-rust/target/release/validate-install-kit`,
|
||
`/usr/local/bin/validate-install-kit`, затем использует legacy
|
||
Bash/Python fallback;
|
||
- сохранен stage output:
|
||
`[1/4] Required files presence`,
|
||
`[2/4] Manifest checksum verification`,
|
||
`[3/4] Manifest completeness`,
|
||
`[4/4] Archive composition check`,
|
||
`validate_install_kit: OK`; добавлен `--json`;
|
||
- Rust validator проверяет required files, SHA256 из `MANIFEST.txt`,
|
||
полноту manifest относительно install-kit directory, совпадение состава
|
||
zip/tar.gz и корректный archive prefix;
|
||
- `scripts/rebuild_install_kit.sh` теперь включает
|
||
`scripts/validate_install_kit.sh` в install-kit `scripts/`;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` теперь требует
|
||
`validate-install-kit`;
|
||
- после wrapper change выполнен `./scripts/rebuild_install_kit.sh`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
validate-install-kit` (`4 passed`), `cargo clippy -p
|
||
validate-install-kit --all-targets -- -D warnings`, release build OK,
|
||
Rust-first `./scripts/validate_install_kit.sh` OK
|
||
(`MANIFEST complete: 67 files tracked`, `Archives match: 68 files`),
|
||
`./scripts/check_install_kit_vs_repo.sh` OK (`compared 62`,
|
||
mismatches `0`), negative manifest checksum smoke rc `1`, negative
|
||
archive mismatch smoke rc `1`, artifact check OK.
|
||
41. `[done]` Перенести InnoSetup installer verifier на Rust:
|
||
- добавлен crate `verify-innosetup-installer`;
|
||
- `scripts/verify_innosetup_installer.sh` теперь Rust-first wrapper:
|
||
ищет `VERIFY_INNOSETUP_INSTALLER_RUST`,
|
||
`$CARGO_TARGET_DIR/release/verify-innosetup-installer`,
|
||
`adk-rust/target/release/verify-innosetup-installer`,
|
||
`/usr/local/bin/verify-innosetup-installer`, затем использует legacy
|
||
Wine/Bash fallback;
|
||
- Rust verifier создает чистый Wine prefix, silent-install'ит
|
||
`windows/installkit/innosetup/AWatch-rus-InstallKit.exe` в
|
||
`C:\AWatchRusExtract`, сверяет payload guard/policy файлов с
|
||
репозиторием и проверяет self-test marker
|
||
`collector guard self-test OK`;
|
||
- первый прогон корректно поймал устаревший installer payload:
|
||
`aw-collector-guard.ps1` и `install-collector-guard-service.ps1`
|
||
отличались от repo после safe-default hardening;
|
||
- installer пересобран штатным `windows/installkit/innosetup/build_with_wine.sh`,
|
||
после чего Rust-first verifier завершился `verify_innosetup_installer:
|
||
OK`;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` теперь требует
|
||
`verify-innosetup-installer`.
|
||
42. `[done]` Перенести install-kit rebuild path на Rust:
|
||
- добавлен crate `rebuild-install-kit`;
|
||
- `scripts/rebuild_install_kit.sh` теперь Rust-first wrapper:
|
||
ищет `REBUILD_INSTALL_KIT_RUST`,
|
||
`$CARGO_TARGET_DIR/release/rebuild-install-kit`,
|
||
`adk-rust/target/release/rebuild-install-kit`,
|
||
`/usr/local/bin/rebuild-install-kit`, затем использует legacy Bash
|
||
fallback;
|
||
- Rust rebuild сохраняет `*.deployment-config.json`, пересобирает
|
||
directory tree, `MANIFEST.txt`, `.zip` и `.tar.gz`;
|
||
- копирование реализовано через read + replace-write, а не `fs::copy`,
|
||
потому что текущий mounted-диск возвращал `Operation not permitted` на
|
||
truncate/copy existing files;
|
||
- install-kit теперь включает сам `scripts/rebuild_install_kit.sh`, чтобы
|
||
комплект был самодостаточным для дальнейшей пересборки;
|
||
- Rust-first rebuild verified через `validate_install_kit`,
|
||
`check_install_kit_vs_repo`, archive script `cmp`, and release artifact
|
||
check.
|
||
43. `[done]` Перенести `quality-gate.sh` на Rust-first orchestrator:
|
||
- добавлен crate `quality-gate`;
|
||
- `scripts/quality-gate.sh` теперь Rust-first wrapper:
|
||
ищет `QUALITY_GATE_RUST`,
|
||
`$CARGO_TARGET_DIR/release/quality-gate`,
|
||
`adk-rust/target/release/quality-gate`, `/usr/local/bin/quality-gate`,
|
||
затем использует legacy Bash fallback;
|
||
- Rust orchestrator сохраняет stage contract: Bash syntax,
|
||
ShellCheck when available, Node syntax when available, PowerShell parse
|
||
and collector guard self-test when `pwsh` available, Ansible syntax when
|
||
`ansible-playbook` available;
|
||
- final gates: `cargo fmt --all -- --check`, targeted tests for
|
||
`verify-innosetup-installer`, `rebuild-install-kit`, `quality-gate`,
|
||
`validate-install-kit`, clippy `-D warnings`, release builds,
|
||
Rust-first `quality-gate: OK`, install-kit validate/check OK,
|
||
InnoSetup verifier OK, and DetMir read-only status OK with
|
||
`ok_for_operator=true`.
|
||
44. `[done]` Перенести offline Sigma/Hayabusa IOC extraction path на Rust:
|
||
- добавлен crate `extract-ioc-from-sigma`;
|
||
- `scripts/build_dlp_ioc_from_hayabusa.sh` теперь Rust-first wrapper:
|
||
ищет `EXTRACT_IOC_FROM_SIGMA_RUST`,
|
||
`$CARGO_TARGET_DIR/release/extract-ioc-from-sigma`,
|
||
`adk-rust/target/release/extract-ioc-from-sigma`,
|
||
`/usr/local/bin/extract-ioc-from-sigma`, затем использует legacy Python
|
||
`scripts/extract_ioc_from_sigma.py`;
|
||
- Rust binary сохраняет CLI-контракт `--rules-root`, `--out-dir`,
|
||
`--table-name` и output files `ioc_blacklist.json`,
|
||
`ioc_blacklist.csv`, `ioc_blacklist.sql`;
|
||
- extraction contract сохранен: `Image|endswith`,
|
||
`CommandLine|contains`, `OriginalFileName`, `Hashes|SHA256` и SHA256
|
||
values embedded in `Hashes` strings;
|
||
- parity fix: Rust использует Unicode lowercase и PyYAML-compatible
|
||
`True`/`False` rendering для YAML 1.1 boolean-like literals, чтобы не
|
||
расширять IOC set относительно legacy Python;
|
||
- real Hayabusa rules parity:
|
||
`rules_scanned=4963`, `iocs_extracted=13510`,
|
||
`commandline_contains=10192`, `original_filename=457`,
|
||
`process_image_endswith=1171`, `sha256=1690`,
|
||
`missing_in_rust=0`, `extra_in_rust=0`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
extract-ioc-from-sigma` (`3 passed`), `cargo clippy -p
|
||
extract-ioc-from-sigma --all-targets -- -D warnings`, release build OK,
|
||
`scripts/check_detmir_rust_release_artifacts.sh` OK,
|
||
Rust-first `scripts/quality-gate.sh` OK.
|
||
45. `[done]` Перенести `scripts/rdp-worktime-report.sh` на Rust-first helper:
|
||
- добавлен crate `rdp-worktime-report`;
|
||
- `scripts/rdp-worktime-report.sh` теперь Rust-first wrapper:
|
||
ищет `RDP_WORKTIME_REPORT_RUST`,
|
||
`$CARGO_TARGET_DIR/release/rdp-worktime-report`,
|
||
`adk-rust/target/release/rdp-worktime-report`,
|
||
`/usr/local/bin/rdp-worktime-report`, затем использует legacy embedded
|
||
Python fallback;
|
||
- Rust binary сохраняет CLI/env contract: `--day today|yesterday`,
|
||
`--from YYYY-MM-DD --to YYYY-MM-DD`, `AW_BASE_URL`,
|
||
`AW_WORKTIME_HOST`, `AW_WORKTIME_DEFAULT_SAMPLE_SECONDS`,
|
||
`AW_WORKTIME_MAX_SAMPLE_SECONDS`, `OUT_DIR`;
|
||
- output contract сохранен: `rdp-worktime-<FROM>_<TO>.csv`,
|
||
`rdp-worktime-<FROM>_<TO>.json`, stdout paths plus `CSV:`/`JSON:`;
|
||
- calculation contract сохранен: bucket
|
||
`aw-worktime-sessions_<host>`, active samples by `active=true` or
|
||
Russian/English active state, interval merge, sampleSeconds/pollSeconds,
|
||
duration and next-timestamp fallback, canonical `HOST\user` userId;
|
||
- live parity on yesterday against Python fallback:
|
||
`rust_csv_rows=3`, `python_csv_rows=3`, `csv_equal=True`,
|
||
JSON `host/bucket_id/from/to/rows` all equal;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
rdp-worktime-report` (`3 passed`), `cargo clippy -p
|
||
rdp-worktime-report --all-targets -- -D warnings`, release build OK,
|
||
`scripts/check_detmir_rust_release_artifacts.sh` OK,
|
||
`scripts/quality-gate.sh` OK, DetMir read-only status OK with
|
||
`dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`.
|
||
46. `[done]` Перенести Proxmox DetMir contour smoke на Rust-first helper:
|
||
- добавлен crate `aw-contour-smoke`;
|
||
- `scripts/aw-contour-smoke-gateway.sh` теперь Rust-first wrapper:
|
||
ищет `AW_CONTOUR_SMOKE_RUST`,
|
||
`$CARGO_TARGET_DIR/release/aw-contour-smoke`,
|
||
`adk-rust/target/release/aw-contour-smoke`,
|
||
`/usr/local/sbin/aw-contour-smoke`,
|
||
`/usr/local/bin/aw-contour-smoke`, затем использует legacy Bash
|
||
fallback;
|
||
- `scripts/aw-contour-smoke-local.sh` умеет временно доставлять release
|
||
binary на Proxmox для shadow smoke без постоянного platform change;
|
||
- remote parity: Rust и legacy оба вернули `OK=30 WARN=0 FAIL=3 SKIP=0`,
|
||
`rc=2`; все 3 fail совпали и относятся к существующему gateway-auth
|
||
`HTTP 401` на `/go/proxmox-gui`, `/go/file1c-brief`,
|
||
`/go/file1c-actions`, не к Rust regression;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
aw-contour-smoke` (`2 passed`), `cargo clippy -p aw-contour-smoke
|
||
--all-targets -- -D warnings`, release build OK,
|
||
`bash -n` wrappers OK, artifact check OK, `quality-gate.sh` OK.
|
||
47. `[done]` Перенести `scripts/diag_and_manual_restart.sh` на Rust-first
|
||
diagnostic helper:
|
||
- добавлен crate `diag-and-manual-restart`;
|
||
- wrapper ищет `DIAG_AND_MANUAL_RESTART_RUST`,
|
||
`$CARGO_TARGET_DIR/release/diag-and-manual-restart`,
|
||
`adk-rust/target/release/diag-and-manual-restart`,
|
||
`/usr/local/bin/diag-and-manual-restart`, затем использует legacy Bash
|
||
fallback;
|
||
- здоровый контур не перезапускается: helper запускает `aw-health-check`
|
||
и `dlp-health-check`, печатает `Diagnostics: healthy. Restart not
|
||
needed.` и выходит `0`;
|
||
- mutation path требует явного подтверждения или `--yes`; live restart не
|
||
запускался, потому что контур был зеленый;
|
||
- gates: fmt/test/clippy/release OK, `bash -n` OK, Rust-first healthy path
|
||
rc `0`, legacy fallback healthy path rc `0`, artifact check OK,
|
||
`quality-gate.sh` OK, DetMir read-only OK.
|
||
48. `[done]` Перенести browser smoke launcher на Rust-first helper:
|
||
- добавлен crate `aw-browser-smoke`;
|
||
- `scripts/aw-webui-browser-smoke.sh` теперь Rust-first wrapper:
|
||
ищет `AW_BROWSER_SMOKE_RUST`,
|
||
`$CARGO_TARGET_DIR/release/aw-browser-smoke`,
|
||
`adk-rust/target/release/aw-browser-smoke`,
|
||
`/usr/local/bin/aw-browser-smoke`, затем использует legacy Node
|
||
fallback;
|
||
- Playwright/Chromium логика остается в
|
||
`scripts/aw-webui-browser-smoke.mjs`; Rust отвечает за устойчивый запуск,
|
||
`NODE_PATH`, passthrough args и сохранение child exit code;
|
||
- fake-node проверки подтвердили passthrough `.mjs` path, user args,
|
||
`NODE_PATH` и exit-code propagation для Rust launcher и legacy fallback;
|
||
- live browser smoke через Rust-first wrapper: `ok=true`, страницы
|
||
`aw_webui_home`, `worktime_today_html`,
|
||
`worktime_management_html` все OK;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p aw-browser-smoke`
|
||
(`3 passed`), `cargo clippy -p aw-browser-smoke --all-targets -- -D
|
||
warnings`, release build OK, `bash -n` OK, artifact check OK,
|
||
`quality-gate.sh` OK, DetMir read-only OK with
|
||
`dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`.
|
||
49. `[done]` Перенести `check-aw-full.sh` на Rust-first read-only helper:
|
||
- добавлен crate `check-aw-full`;
|
||
- root wrapper `check-aw-full.sh` ищет `CHECK_AW_FULL_RUST`,
|
||
`$CARGO_TARGET_DIR/release/check-aw-full`,
|
||
`adk-rust/target/release/check-aw-full`, `/usr/local/bin/check-aw-full`,
|
||
затем выполняет embedded legacy Bash fallback;
|
||
- для прямого legacy compare добавлен `CHECK_AW_FULL_FORCE_LEGACY=1`;
|
||
- Rust helper сохраняет операторский контракт: AW server connectivity,
|
||
CORS probe, 8 bucket freshness rows, RDP WinRM/SSH TCP checks, summary
|
||
`FRESH/STALE/DEAD`, recovery hint when stale/dead collectors exist;
|
||
- live parity: Rust-first и legacy оба показали connectivity OK, CORS OK,
|
||
WinRM OK, SSH OK, `FRESH=8 STALE=0 DEAD=0`; bucket statuses совпали,
|
||
один live event id у `Window watcher` ожидаемо сдвинулся между
|
||
последовательными запусками;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p check-aw-full`
|
||
(`4 passed`), `cargo clippy -p check-aw-full --all-targets -- -D
|
||
warnings`, release build OK, `bash -n` OK, artifact check OK,
|
||
`quality-gate.sh` OK, DetMir read-only OK with
|
||
`dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`.
|
||
50. `[done]` Перенести merge engine на Rust:
|
||
- добавлен crate `merge-aw-server-dbs`;
|
||
- рабочий runtime path: `adk-rust/target/release/merge-aw-server-dbs` и
|
||
`/usr/local/bin/merge-aw-server-dbs`;
|
||
- legacy `scripts/merge_aw_server_dbs.py` удален из repo/runtime path;
|
||
- `ansible/deploy_aw_server.yml` устанавливает
|
||
`/usr/local/bin/merge-aw-server-dbs` и использует его без Python;
|
||
- Rust сохраняет контракт `--base`, `--overlay`, `--output`, SQLite
|
||
backup-copy base DB, bucket matching by id/key/name, duplicate event
|
||
suppression by `(starttime,endtime,data)`, JSON summary;
|
||
- offline parity fixture: Rust replacement дал ожидаемый JSON
|
||
`inserted_buckets=1`, `inserted_events=3` и одинаковые SQLite table rows
|
||
(`buckets=3`, `events=5`) на кейсе duplicate event + name-conflict
|
||
bucket;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
merge-aw-server-dbs` (`2 passed`), `cargo clippy -p
|
||
merge-aw-server-dbs --all-targets -- -D warnings`, release build OK,
|
||
artifact check OK, `quality-gate.sh` OK, DetMir read-only OK after DLP baseline retry
|
||
with `dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`.
|
||
51. `[done]` Перенести `scripts/prod_backup_restore.sh` в safe-by-default
|
||
Rust planner/checker:
|
||
- добавлен crate `prod-backup-restore`;
|
||
- `scripts/prod_backup_restore.sh` теперь Rust-first wrapper:
|
||
ищет `PROD_BACKUP_RESTORE_RUST`,
|
||
`$CARGO_TARGET_DIR/release/prod-backup-restore`,
|
||
`adk-rust/target/release/prod-backup-restore`,
|
||
`/usr/local/bin/prod-backup-restore`;
|
||
- обычный запуск больше не выполняет restore при наличии Rust artifact:
|
||
он печатает plan-only flow и помечает destructive steps;
|
||
- если Rust artifact отсутствует, wrapper отказывается запускать
|
||
destructive legacy случайно и просит собрать Rust planner;
|
||
- старый Python destructive flow удален; `--apply-legacy` больше не
|
||
поддерживается;
|
||
- Rust `--apply` намеренно запрещен на этом этапе;
|
||
- `--check-inputs --json` валидирует env/files/commands без вывода
|
||
секретов, проверяет `AW_SSH_PASSWORD`, `AW_WINRM_PASSWORD`, `sshpass`,
|
||
`ansible-playbook`, inventory и `merge-aw-server-dbs`;
|
||
- план сохраняет порядок restore flow: scp Rust merge binary, remote backup dir,
|
||
DB existence checks, DB backups, stop `activitywatch-server`, merge DB,
|
||
install merged DB, 3 ansible playbooks, final AW validation;
|
||
- safe smoke: plan text OK, JSON valid (`steps=12`, `missing_count=0`),
|
||
destructive steps present and marked, password names/values не попадают в
|
||
planned commands, Rust `--apply` exits rc `1`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
prod-backup-restore` (`3 passed`), `cargo clippy -p
|
||
prod-backup-restore --all-targets -- -D warnings`, release build OK,
|
||
`bash -n scripts/prod_backup_restore.sh`, artifact check OK,
|
||
`quality-gate.sh` OK, DetMir read-only OK with
|
||
`dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`;
|
||
transient `service_warnings=1` disappeared on immediate
|
||
`detmir-check --json` repeat (`service_warnings=0`).
|
||
52. `[done]` Перенести `scripts/prod_rollout.sh` в Rust-first safe
|
||
planner/orchestrator:
|
||
- добавлен crate `prod-rollout`;
|
||
- `scripts/prod_rollout.sh` теперь Rust-first wrapper:
|
||
ищет `PROD_ROLLOUT_RUST`, `$CARGO_TARGET_DIR/release/prod-rollout`,
|
||
`adk-rust/target/release/prod-rollout`, `/usr/local/bin/prod-rollout`;
|
||
- обычный запуск больше не стартует production deploy автоматически:
|
||
он показывает plan-only rollout flow;
|
||
- реальный rollout через Rust доступен только явно:
|
||
`scripts/prod_rollout.sh --apply`;
|
||
- старый Bash rollout сохранен только явно:
|
||
`scripts/prod_rollout.sh --apply-legacy`;
|
||
- `--check-inputs --json` валидирует `AW_SSH_PASSWORD`,
|
||
`AW_WINRM_PASSWORD`, `git`, `ansible`, `ansible-playbook`, inventory,
|
||
`quality-gate.sh`, `deploy_aw_server.yml`, `deploy_aw_windows.yml` и
|
||
`post_validate_aw_windows.yml` без вывода секретов;
|
||
- план сохраняет legacy порядок: `quality-gate`, Ansible ping AW server,
|
||
Ansible win_ping AW Windows, `deploy_aw_server --check --diff`, real
|
||
`deploy_aw_server`, `deploy_aw_windows --check --diff`, real
|
||
`deploy_aw_windows`, `post_validate_aw_windows`;
|
||
- mutation в плане помечена только для `deploy-aw-server` и
|
||
`deploy-aw-windows`;
|
||
- apply-runner пишет stdout/stderr каждого шага в
|
||
`.rollout-logs/<timestamp>/`, общий ход - в `rollout.log`;
|
||
- safe smoke: `scripts/prod_rollout.sh --check-inputs --json` вернул
|
||
`mode=plan-only`, `missing_count=0`, `steps=8`, mutations
|
||
`deploy-aw-server` и `deploy-aw-windows`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p prod-rollout`
|
||
(`3 passed`), `cargo clippy -p prod-rollout --all-targets -- -D
|
||
warnings`, release build OK, `bash -n scripts/prod_rollout.sh`,
|
||
artifact check OK.
|
||
53. `[done]` Перенести `aw-server/ensure-reliability.sh` в Rust-first
|
||
dry-run/apply helper:
|
||
- добавлен crate `aw-ensure-reliability`;
|
||
- `aw-server/ensure-reliability.sh` теперь Rust-first wrapper:
|
||
ищет `AW_ENSURE_RELIABILITY_RUST`,
|
||
`$CARGO_TARGET_DIR/release/aw-ensure-reliability`,
|
||
`adk-rust/target/release/aw-ensure-reliability`,
|
||
`/usr/local/bin/aw-ensure-reliability`;
|
||
- обычный запуск больше не делает `chown`, `systemctl stop/start`,
|
||
logrotate write или health timer write; он показывает dry-run plan;
|
||
- реальный Rust repair требует explicit `--apply`;
|
||
- старый Bash repair доступен только explicit `--apply-legacy`;
|
||
- Rust helper сохраняет legacy action set: env check,
|
||
ownership/mode repair for `/var/lib/activitywatch`,
|
||
`/var/log/activitywatch`, `/opt/activitywatch`, logrotate install,
|
||
health-check script/timer/service install, ordered AW service restart and
|
||
enable;
|
||
- `deploy_aw_server.yml` optional устанавливает
|
||
`/usr/local/bin/aw-ensure-reliability` до Influx token checks, если
|
||
release artifact доступен;
|
||
- production binary доставлен на AW server, но `--apply` не запускался;
|
||
- production dry-run: `apply=false`, `ok=true`, `missing=0`,
|
||
`executed=0`, `steps=25`;
|
||
- final production gates: AW failed units `0`, DetMir status OK with
|
||
`service_warnings=0`, `dlp_counts={ok:22,warn:0,fail:0}`,
|
||
`ok_for_operator=true`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p
|
||
aw-ensure-reliability` (`2 passed`), `cargo clippy -p
|
||
aw-ensure-reliability --all-targets -- -D warnings`, release build OK,
|
||
`bash -n aw-server/ensure-reliability.sh`, artifact check OK,
|
||
`ansible-playbook deploy_aw_server.yml --syntax-check` OK.
|
||
54. `[done]` Перевести Linux install scripts в Rust-first safe planner слой:
|
||
- добавлен crate `aw-linux-install`;
|
||
- wrappers теперь Rust-first и dry-run по умолчанию:
|
||
`scripts/install_aw_linux_client.sh`,
|
||
`scripts/install_aw_linux_remote_worker.sh`,
|
||
`scripts/install_aw_linux_web_category_logger.sh`,
|
||
`scripts/install_aw_console_ssh_logger.sh`,
|
||
`scripts/install_aw_pve_webadmin_logger.sh`;
|
||
- real install требует explicit `--apply`;
|
||
- old shell install требует explicit `--apply-legacy`;
|
||
- `aw-linux-install --apply` запускает соответствующий legacy script с
|
||
`--apply-legacy`, сохраняя embedded Python collectors и shell install
|
||
contract;
|
||
- `remote_worker` legacy path исправлен: вложенные client/console/web
|
||
installers вызываются с `--apply-legacy`, чтобы real legacy install не
|
||
превратился в dry-run после оборачивания;
|
||
- PVE webadmin logger не запускался и Proxmox platform не менялся; это
|
||
только safe planner wrapper;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p aw-linux-install`
|
||
(`2 passed`), `cargo clippy -p aw-linux-install --all-targets -- -D
|
||
warnings`, release build OK, `sh -n` для всех пяти wrappers OK,
|
||
dry-run JSON для всех пяти wrappers OK, artifact check OK.
|
||
55. `[done]` Закрепить проверку актуализации и правильности Grafana данных:
|
||
- добавлен crate `detmir-grafana-check`;
|
||
- check читает Grafana dashboard API, валидирует форму dashboard,
|
||
отсутствие старых `aw_window_event`/`aw_afk_event`, наличие текущих
|
||
worktime measurements и выполняет все panel queries через Grafana
|
||
datasource API;
|
||
- freshness panel считается обязательным: значение свежести должно быть не
|
||
старше `DETMIR_GRAFANA_MAX_FRESHNESS_MINUTES` (production default 360);
|
||
- результат пишется в Grafana CT 201:
|
||
`/var/lib/detmir-grafana-check/latest.json` и `latest.txt`;
|
||
- установлен `detmir-grafana-check.service` и timer каждые 15 минут;
|
||
- `detmir-check` теперь читает последний Grafana-check artifact через
|
||
`sudo -n /usr/sbin/pct exec 201` и считает `grafana-data` required
|
||
service check, так что общий `detmir-status` краснеет при сломанной или
|
||
устаревшей Grafana;
|
||
- добавлен playbook `ansible/deploy_grafana_check.yml`;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` теперь требует
|
||
`detmir-grafana-check`;
|
||
- production verification: `detmir-grafana-check` OK
|
||
(`ok=13,warn=0,fail=0`), 7/7 panels have rows, freshness около 120 min,
|
||
timer active, Grafana CT failed units 0, Proxmox failed units 0,
|
||
`detmir-check` `service_failures=0`, `detmir-auto --no-heal` rc 0,
|
||
`detmir-status` OK / `ok_for_operator=true`.
|
||
56. `[done]` Реализовать read-only MVP `detmir-portal`:
|
||
- добавлен crate `detmir-portal`;
|
||
- portal работает как Rust web service на Proxmox:
|
||
`/usr/local/bin/detmir-portal`, `detmir-portal.service`,
|
||
bind `127.0.0.1:8720`;
|
||
- внешний route добавлен в существующий nginx gateway:
|
||
`https://<PUBLIC_GATEWAY_FQDN>/portal/`;
|
||
- UI содержит вкладки `Оператор`, `Руководитель`, `Владелец`,
|
||
`Инциденты ИБ`;
|
||
- API реализованы: `/api/health`, `/api/summary`, `/api/operator`,
|
||
`/api/manager`, `/api/owner`, `/api/incidents`, `/api/links`;
|
||
- источники read-only: `detmir-status --json`, `detmir-check --json`,
|
||
`systemctl --failed`, AW Worktime API, 1C analytics health;
|
||
- portal не делает write/heal/restart actions и не трогает pfSense,
|
||
Telegram runtime, NAT/DNS/VPN;
|
||
- добавлен deployment playbook `ansible/deploy_detmir_portal.yml`;
|
||
- gateway playbook теперь проверяет `/portal/api/health` с auth;
|
||
- `scripts/check_detmir_rust_release_artifacts.sh` теперь требует
|
||
`detmir-portal`;
|
||
- gates: `cargo fmt --all -- --check`, `cargo test -p detmir-portal`
|
||
(`3 passed`), `cargo clippy -p detmir-portal --all-targets -- -D
|
||
warnings`, release build OK, `ansible-playbook
|
||
deploy_detmir_portal.yml --syntax-check`, `ansible-playbook
|
||
deploy_proxmox_web_gateway.yml --syntax-check`, artifact check OK;
|
||
- production verification: `detmir-portal` active, nginx active,
|
||
`/portal/api/health` OK through gateway auth, all sources true,
|
||
external `/portal/` returns protected `401` without auth, gateway
|
||
`/healthz` returns `ok`, `detmir-status` OK / `ok_for_operator=true`,
|
||
Proxmox failed units 0;
|
||
- browser verification through production tunnel: desktop and mobile
|
||
nonblank, tabs work, API requests 200, console errors 0. Screenshots:
|
||
`.playwright-cli/page-2026-06-02T17-31-49-049Z.png`,
|
||
`.playwright-cli/page-2026-06-02T17-33-50-318Z.png`,
|
||
`.playwright-cli/page-2026-06-02T17-34-04-725Z.png`.
|
||
- portal link repair after Grafana/gateway smoke: quick links now open in a
|
||
separate tab, `worktime_report` points to explicit HTML
|
||
`/reports/worktime/management?format=html&host=HOST-EXAMPLE`,
|
||
`1С действия` is shown in the portal, and gateway `/r/aw-worktime` is
|
||
pinned to the same HTML report. Production smoke: AW UI, Worktime, 1C
|
||
brief, and 1C actions returned `200 text/html`; Grafana links correctly
|
||
redirect to `/login` when no Grafana session exists.
|
||
- incident operator workflow added to portal: incidents now have stable
|
||
IDs, operator state is persisted in
|
||
`/var/lib/detmir-portal/incidents-state.json`, audit events append to
|
||
`/var/lib/detmir-portal/audit.jsonl`, nginx passes `$remote_user` as
|
||
`X-Remote-User`, and the UI exposes `В работу` / `Назначить` actions.
|
||
This layer only records acknowledgement/assignment metadata; it does not
|
||
heal, restart, mutate AW/DLP/1C, or touch pfSense/Telegram. Gates:
|
||
`cargo fmt --all -- --check`, `cargo test -p detmir-portal` (`5 passed`),
|
||
`cargo clippy -p detmir-portal --all-targets -- -D warnings`, release
|
||
build OK. Production smoke: `POST /portal/api/incidents/action` returned
|
||
`200 application/json`, actor resolved to `detmir`, state/audit files
|
||
were written, smoke state was removed after validation, portal health
|
||
stayed `200`, `detmir-status` stayed `OK / ok_for_operator=true`, and
|
||
failed units stayed `0`. Local Playwright smoke against a temporary
|
||
portal instance verified `Инциденты ИБ`, `В работу`, persisted UI state,
|
||
disabled ack button, and zero JS errors.
|
||
- `Инциденты ИБ` tightened to a DLP-focused card: the tab now filters the
|
||
card to DLP/incident/case items, adds a synthetic DLP-count incident when
|
||
`dlp_counts.warn/fail` is non-zero, and shows only DLP/Grafana dashboard
|
||
links (`detmir-dlp-security`, `detmir-dlp-management`,
|
||
`awatch-dlp-overview`, Grafana catalog). Worktime, 1C, AW UI, and other
|
||
non-incident operational links are intentionally not shown in this card.
|
||
Local Playwright smoke verified DLP incident rendering, dashboard links,
|
||
no Worktime/1C text in the card, and zero JS errors.
|
||
- DLP evidence viewing layer added to the portal:
|
||
`detmir-portal` now exposes `/api/dlp/evidence` and safe screenshot
|
||
routes by opaque evidence id. The service reads DLP warehouse SQLite
|
||
read-only, extracts `screenshotSha256`/dimensions from raw event JSON,
|
||
and serves image files only from an allowlisted evidence root after
|
||
canonical path, extension, size, and SHA-256 validation. Evidence views
|
||
and downloads append to `evidence-audit.jsonl`. Production uses an
|
||
AW-server evidence-only service, `/usr/local/bin/detmir-portal-evidence`
|
||
with `detmir-portal-evidence.service`, because the DLP warehouse lives on
|
||
the AW server. Proxmox nginx gateway routes
|
||
`/portal/api/dlp/evidence*` to `<AW_SERVER_HOST>:8721`. Current production
|
||
verification: AW evidence API `ok=true`, gateway evidence route
|
||
`ok=true`, `db_available=true`, 11 DLP evidence rows returned,
|
||
`screenshot_available=0` because current stored rows do not yet contain
|
||
screenshot metadata, both portal/evidence services active, failed units
|
||
0, `detmir-status` `OK / ok_for_operator=true`. Local HTTP smoke
|
||
verified byte-identical screenshot serving and audit logging; local
|
||
Playwright smoke verified the `Доказательства` block, `СКРИН`, `Открыть`,
|
||
`Скачать`, and zero JS errors.
|
||
- DLP evidence screenshot delivery is now automated in production:
|
||
the AW evidence API accepts authenticated uploads at
|
||
`POST /api/dlp/evidence/upload` in evidence-only mode. Uploads require a
|
||
server-generated Bearer token stored outside git, validate base64 body,
|
||
PNG/JPEG magic, max size, and exact SHA-256 before atomic write to
|
||
`/var/lib/activitywatch/dlp-evidence/screenshots/<sha256>.(png|jpg)`.
|
||
Windows RDP host runs `sync-dlp-evidence-artifacts.ps1` through scheduled
|
||
task `ActivityWatch DLP Evidence Sync` every 5 minutes as SYSTEM. The
|
||
sync scans `C:\ProgramData\AWatch-rus\incident-artifacts` plus configured
|
||
artifact roots, uploads new PNG screenshots, and keeps local upload state
|
||
in `C:\ProgramData\AWatch-rus\dlp-evidence-sync-state.json`. Production
|
||
controlled test: a visible PNG was created on Windows, sync uploaded it
|
||
(`uploaded=1`, `failed=0`), a temporary warehouse row made it visible in
|
||
the portal with `screenshot_available=true`, gateway preview returned
|
||
`image/png` with matching SHA, external Playwright verified the portal UI
|
||
and preview, audit recorded upload/view, unauthenticated upload returned
|
||
403, and the synthetic row/files/state were removed. Final state:
|
||
evidence count returned to 11, synthetic hit false, AW/Proxmox failed
|
||
units 0, `detmir-status` OK, sync task Ready with `lastTaskResult=0`.
|
||
- during this deploy, `detmir-grafana-check` was corrected so empty
|
||
detail-only panels for employees/applications are WARN, not FAIL. The
|
||
mandatory freshness/summary panels still fail the check when stale or
|
||
empty. This removed a false red state shortly after midnight when
|
||
`today` detail rows were legitimately empty. Production verification:
|
||
Grafana check `ok=true` with `fail=0`, `detmir-auto --no-heal` rc `0`,
|
||
portal health `true`, `detmir-status` `OK / ok_for_operator=true`, and
|
||
failed units `0`.
|
||
- `docs/DETMIR_THREAT_MODEL_RU.md` added as the current working threat
|
||
model for DetMir. It records the product as an operational
|
||
control and technical audit platform, not a certified DLP/SIEM/EDR/XDR
|
||
or FSTEC SZI. It also records Igor as the declared product owner, lists
|
||
assets, trust zones, attacker/operator-failure classes, implemented
|
||
evidence controls, residual risks, and the hardening roadmap.
|
||
- `docs/DETMIR_RUSSIAN_SOFTWARE_REGISTRY_POSITIONING_RU.md` added as the
|
||
registry/product positioning note. Current decision: lead with DetMir as
|
||
an operational control and IT infrastructure management platform, use
|
||
`09.10` as the primary Russian software registry class target, keep
|
||
DLP/security/evidence/Hayabusa as applied modules, and prepare website,
|
||
operator/admin docs, ownership package, screenshots, and dependency
|
||
inventory before any registry filing.
|
||
- registry proof package skeleton added:
|
||
`docs/ADMIN_GUIDE_RU.md`, `docs/OPERATOR_GUIDE_RU.md`,
|
||
`docs/INSTALL_RU.md`, `docs/ARCHITECTURE_RU.md`,
|
||
`docs/OWNERSHIP_RU.md`, `docs/THIRD_PARTY_LICENSES_RU.md`, and
|
||
`docs/REGISTRY_CHECKLIST_RU.md`. Naming decision fixed across the docs:
|
||
`DetMir` is the product, `AWatch-rus` is the repository/technical base,
|
||
and the external formula is `DetMir, программный комплекс на базе
|
||
AWatch-rus`.
|
||
|
||
Отложить:
|
||
|
||
- post-MVP развитие `detmir-portal`: role-aware views, safe check-now action,
|
||
daily owner report, historical trends, AI summary with strict source
|
||
citations, action buttons with allowlist and audit log. Детальный план:
|
||
`docs/DETMIR_PORTAL_GUI_PLAN_RU.md`;
|
||
- перенос Telegram bot runtime снят с плана: Python остается постоянным
|
||
runtime, Rust используется только для backend helpers;
|
||
- перенос оставшихся install/runtime scripts на Rust;
|
||
- переписывание PowerShell Windows collector path;
|
||
- любые network/firewall/VPN изменения;
|
||
- pfSense полностью frozen/no-touch: текущего потенциала достаточно, NAT/gateway
|
||
tooling не переносить и не дергать без отдельной явной команды.
|
||
|
||
## 12. Stop conditions
|
||
|
||
Миграцию остановить и откатить конкретный модуль, если:
|
||
|
||
- новый binary дает иной `severity` без объяснимой причины;
|
||
- меняет exit code class на зеленом или красном контуре;
|
||
- пишет неполный/corrupt state;
|
||
- вызывает restart на зеленом контуре;
|
||
- ломает Telegram `/status`;
|
||
- ломает Grafana/Influx/Prometheus metric names;
|
||
- требует laptop-only path или интерактивный shell;
|
||
- выводит секреты в stdout/journald/report.
|
||
|
||
## 13. Рабочий принцип
|
||
|
||
Правильный перенос на Rust - это не переписывание строк один-в-один.
|
||
|
||
Для каждого legacy script надо сделать:
|
||
|
||
1. понять контракт;
|
||
2. зафиксировать fixture;
|
||
3. написать typed model;
|
||
4. реализовать read-only parity;
|
||
5. включить shadow-mode;
|
||
6. заменить production command;
|
||
7. только потом добавлять action/mutation;
|
||
8. удалить legacy только после периода стабильной эксплуатации.
|