Files
AWatch-rus/adk-rust/RUNBOOK.md
T

100 KiB
Raw Blame History

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

Целевая структура:

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.

Гейт готовности:

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.

Гейт:

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:

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 работает как:
/usr/local/bin/tsj-guardian-watchdog-rust --apply --loop-forever --interval-seconds 60

Rollback:

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

Сборка:

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:

install -o root -g root -m 0755 \
  target/release/<binary> \
  /usr/local/bin/<binary>

Через Ansible:

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'

Проверка на сервере:

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

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 сохранены:

/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

Пример:

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 провален:

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

Локально:

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:

    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:

    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:

    READINESS_PUBLIC_KEY_SHA256=<READINESS_PUBLIC_KEY_SHA256_FINGERPRINT>
    

    Verification command:

    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:

detmir-readiness --json
detmir-status --json
detmir-check --json
detmir-dlp
detmir-auto --no-heal
systemctl --failed

На AW server:

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:

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 только после периода стабильной эксплуатации.