feat(rust): advance powershell migration

This commit is contained in:
igor04091968
2026-06-06 08:58:46 +03:00
parent 2ec1836e8c
commit 9c57d6d2ce
42 changed files with 10056 additions and 407 deletions
+616
View File
@@ -0,0 +1,616 @@
# Roadmap: замена PowerShell на Rust EXE
Дата: 2026-06-05
Цель: убрать зависимость AWatch-rus/DetMir от PowerShell-скриптов на рабочих
хостах и заменить их на самодостаточные Rust EXE/службы без потери данных,
без ухудшения пилотной демонстрации и без изменения функционального объема.
Документ описывает порядок миграции. Он не вводит новые функции: каждая Rust
замена сначала должна повторить текущий контракт PowerShell-компонента.
Операторский checkpoint от 2026-06-05 сохранен в private `.ops`-контуре:
Phase 0 live inventory выполнен, Phase 2 validation для уже переведенных
Windows Rust paths выполнен успешно. В tracked документации не публикуются live
hostnames, private IP, токены и runtime evidence paths.
## 1. Целевое состояние
К концу миграции:
- на Windows/RDP-хостах регулярный сбор данных, evidence-sync, 1C upload,
guard/recovery и validation выполняются Rust EXE;
- в Scheduled Tasks и Windows Services нет штатных AWatch-rus задач, которые
запускают `powershell.exe` или `pwsh.exe`;
- install-kit поставляет Rust EXE, конфиги и service/task definitions вместо
runtime `.ps1`;
- Ansible разворачивает Rust binaries и конфиги, а не копирует PowerShell
runtime scripts;
- Telegram/Codex/bot контур вызывает Rust helpers или читает Rust JSON
snapshots, не опираясь на пути к `.ps1`;
- legacy PowerShell хранится только как временный rollback-слой до прохождения
acceptance gates и затем удаляется из install-kit;
- Linux/AW/Proxmox части остаются Rust-first; Ansible может остаться
оркестратором, потому что это инфраструктурный deploy layer, а не runtime
PowerShell.
## 2. Текущее состояние
Уже выполнено или частично выполнено:
- `awatch-agent-rs` заменяет Windows `worktime-session-collector.ps1` для
worktime/RDP path. Legacy PowerShell оставлен как fallback и управляется
`collectors.worktimeSessionEnabled`, `worktimeSessionMode`,
`worktimeLegacyFallbackEnabled`.
- `aw-windows-telemetry.exe` уже используется для:
- `file1c-upload`;
- `dlp-evidence-sync`.
- `aw-1c-ingest-rust` пишет 1C/file analytics в ClickHouse на серверной
стороне.
- `aw-windows-telemetry.exe validate-deployment` добавлен как Rust validation
gate первого уровня. Он проверяет уже мигрированные Windows Rust paths,
свежесть worktime bucket, Rust collector guard service и queue sanity без
запуска PowerShell.
- `AWatchRusCollectorGuard` переключен с `aw-collector-guard.ps1` на
`aw-windows-telemetry.exe collector-guard`. Старый PowerShell guard оставлен
как rollback script, но штатный service runtime его не запускает. Rust guard
получает `sessionId` из native process snapshot, дедуплицирует legacy
collectors по `(kind, sessionId)` и не запускает пользовательские launch
tasks повторно, если legacy collectors уже активны.
- `aw-windows-telemetry.exe` добавил P0 runtime collector subcommands:
`browser-domains-collector`, `dlp-endpoint-collector`,
`file-operations-collector`. На live Windows/RDP host они включены через
`collectors.*Mode=rust_primary`, работают в трех пользовательских сессиях,
а P0 PowerShell collector runtime отсутствует. Это не означает полного
удаления legacy `.ps1`: они оставлены как rollback/reference до расширения
глубокой функциональной parity.
- Основные серверные DetMir/AW/DLP helpers уже Rust-first:
`detmir-status`, `detmir-check`, `detmir-auto`, `detmir-heal-safe`,
`aw-rus-healthd-rust`, `dlp-*`, `worktime-*`, `aw-health-check`,
`check-aw-data`, `aw-prune-local-state` и другие.
Остаток PowerShell в рабочем дереве:
- 26 product `.ps1` в `windows/`;
- operator/MCP helper `scripts/powershell/detmir-powershell-profile.ps1`;
- parse-check helper `.pssa_run.ps1`;
- `.venv/bin/activate.ps1` внутри virtualenv не является продуктовым
компонентом и не входит в миграцию.
## 3. Инвентаризация Windows PowerShell
| Скрипт | Роль | Цель миграции | Приоритет |
|---|---|---|---|
| `worktime-session-collector.ps1` | RDP/worktime сбор | закрепить замену через `awatch-agent-rs`, затем удалить fallback | P0 done/stabilize |
| `browser-domains-native-collector.ps1` | browser/domain и DLP web-сигналы | `aw-windows-telemetry.exe browser-domains-collector`; live Rust primary, legacy fallback/reference | P0 live/stabilize |
| `dlp-endpoint-signals-collector.ps1` | clipboard/USB/print/DLP incident signals | `aw-windows-telemetry.exe dlp-endpoint-collector`; live Rust primary, screenshots только для DLP events | P0 live/stabilize |
| `file-operations-collector.ps1` | file create/delete/rename/archive hints | `aw-windows-telemetry.exe file-operations-collector`; live Rust primary with queue/spool | P0 live/stabilize |
| `email-outbound-collector.ps1` | outbound email metadata/DLP | Rust email metadata collector | P1 |
| `dlp-policy-client.ps1` | получение DLP policy | Rust policy client/cache | P1 |
| `export-upload-file-1c-telemetry.ps1` | 1C telemetry upload | закрепить `aw-windows-telemetry.exe file1c-upload`, затем удалить legacy | P0 done/stabilize |
| `sync-dlp-evidence-artifacts.ps1` | DLP evidence upload | закрепить `aw-windows-telemetry.exe dlp-evidence-sync` | P0 done/stabilize |
| `export-evtx-for-hayabusa.ps1` | bounded EVTX export | Rust EVTX export helper | P1 |
| `export-upload-hayabusa-to-aw-server.ps1` | EVTX/Hayabusa upload | Rust upload helper или режим в `aw-windows-telemetry.exe` | P1 |
| `aw-standalone-service.ps1` | supervisor service wrapper | Rust Windows service wrapper | P0 |
| `aw-collector-guard.ps1` | guard/restart/recovery | Rust guard with allowlist, lock, cooldown | P0 done/stabilize |
| `hardening-recovery.ps1` | recovery/hardening | Rust recovery CLI; destructive actions behind `--apply` | P1 |
| `validate-deployment.ps1` | post-deploy validation | Rust validation CLI `aw-windows-telemetry.exe validate-deployment`; расширять до полной parity перед удалением `.ps1` | P0 started |
| `rebuild-worktime-tasks.ps1` | rebuild scheduled tasks | Rust task reconciliation CLI | P1 |
| `fix-session-watchers.ps1` | repair session watchers | Rust repair subcommand | P2 |
| `cleanup-disc-sessions.ps1` | cleanup stale disconnected sessions | Rust maintenance subcommand | P2 |
| `migrate-awatch-rus-paths.ps1` | path migration | Rust migration CLI with backup and dry-run | P1 |
| `deploy-single-user.ps1` | single-user deploy | Rust/Ansible-backed installer action | P1 |
| `deploy-domain-users.ps1` | domain deploy | Rust/Ansible-backed installer action | P1 |
| `deploy-ensemble.ps1` | orchestrated deploy | Rust deploy coordinator or Ansible playbook wrapper | P1 |
| `install-standalone-service.ps1` | local service install | Rust installer/bootstrap CLI | P0 |
| `install-collector-guard-service.ps1` | guard service install | Rust installer/bootstrap CLI | P0 |
| `install-dlp-client.ps1` | DLP client install | Rust installer/bootstrap CLI | P1 |
| `audit-cryptopro.ps1` | CryptoPro audit | Rust audit CLI | P2 |
| `run-user1-probe.ps1` | manual probe | Rust diagnostic probe | P2 |
## 4. Неприкосновенные ограничения
- Не делать big-bang replacement.
- Не отключать PowerShell fallback до прохождения shadow/parity gates.
- Не переносить функции, которые требуют скрытого сбора, keylogging, screen
recording или content interception. Скриншоты допустимы только для DLP
incident evidence, если это явно включено политикой.
- Не менять бизнес-логику DLP, 1C, worktime или evidence при переносе.
- Не менять thresholds, bucket names, event schema и ClickHouse schema без
отдельного решения.
- Не ломать install-kit и rollback ради удаления `.ps1`.
- Не хранить live hostnames, private IP, tokens, passwords или runtime evidence
paths в публичных tracked docs.
## 5. Общий контракт каждого Rust EXE
Каждый заменяющий EXE должен иметь:
- `--config <path>`;
- `--json` для машинного вывода;
- `--dry-run` для всех действий, меняющих состояние;
- `--timeout-seconds`;
- `--log-path` или structured logging в штатный каталог;
- `--version`;
- стабильные exit codes:
- `0` - OK;
- `1` - usage/config/runtime error;
- `2` - check выполнен, но состояние WARN/FAIL;
- `3` - action запрещен safety policy;
- structured JSON event для audit trail;
- lock/cooldown для guard/recovery/actions;
- spool/queue для сетевых upload path;
- atomic writes для state files;
- no implicit sudo/admin: повышенные права должны быть видны в task/service
definition;
- rollback compatibility с текущими config keys.
## 6. Фазы миграции
### Phase 0. Baseline и реестр вызовов
Цель: зафиксировать, где PowerShell реально используется.
Действия:
1. Сканировать репозиторий:
- `windows/*.ps1`;
- `windows/installkit/innosetup/*.iss`;
- `ansible/*.yml`;
- `ansible/group_vars/*.yml`;
- `scripts/*`;
- docs/runbooks.
2. Снять runtime baseline на тестовом Windows host:
- Scheduled Tasks;
- Windows Services;
- текущие command lines процессов;
- `deployment-config.json`;
- свежесть AW buckets;
- наличие DLP evidence и 1C upload.
3. Для каждого скрипта зафиксировать:
- входные параметры;
- env/config keys;
- side effects;
- event schema;
- log/state paths;
- expected exit codes;
- rollback path.
Выход фазы:
- `docs/POWERSHELL_TO_RUST_ROADMAP_RU.md` как базовая дорожная карта;
- отдельный runtime inventory для live-контура в private/operator notes, без
публикации секретов.
### Phase 1. Windows Rust foundation
Цель: подготовить общий Windows runtime вместо набора разрозненных EXE.
Действия:
1. Расширить существующие `awatch-agent-rs` и `aw-windows-telemetry` только в
рамках parity, без новых функций.
2. Вынести общие Windows helpers:
- config loading;
- ActivityWatch HTTP client;
- evidence upload client;
- Windows task/service inspection;
- Event Log/EVTX access;
- filesystem state/spool;
- structured logs/audit.
3. Зафиксировать единый Windows config contract:
- `C:\ProgramData\AWatch-rus\deployment-config.json`;
- `C:\Program Files\AWatch-rus\windows\*.exe`;
- `C:\ProgramData\AWatch-rus\logs`;
- `C:\ProgramData\AWatch-rus\spool`;
- `C:\ProgramData\AWatch-rus\switch-backups`.
Gate:
```bash
cd adk-rust
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo build --release -p awatch-agent-rs -p aw-windows-telemetry
```
### Phase 2. Закрепить уже выполненные замены
Цель: не переписать повторно то, что уже переведено, а довести до production
definition of done.
Компоненты:
- `worktime-session-collector.ps1` -> `awatch-agent-rs`;
- `export-upload-file-1c-telemetry.ps1` -> `aw-windows-telemetry.exe file1c-upload`;
- `sync-dlp-evidence-artifacts.ps1` -> `aw-windows-telemetry.exe dlp-evidence-sync`;
- `aw-collector-guard.ps1` -> `aw-windows-telemetry.exe collector-guard`
через `AWatchRusCollectorGuard` service wrapper;
- серверный writer -> `aw-1c-ingest-rust`.
Действия:
1. Проверить, что task/service actions запускают EXE, а не PowerShell.
2. Проверить, что PowerShell fallback выключен там, где Rust уже стабилен.
3. Проверить свежесть buckets/ClickHouse/evidence after restart.
4. Обновить install-kit file list: Rust EXE являются primary artifact.
5. Оставить legacy `.ps1` только как rollback на ограниченный период.
Gate:
- `ActivityWatch File1C Upload` запускает `aw-windows-telemetry.exe`;
- `ActivityWatch DLP Evidence Sync` запускает `aw-windows-telemetry.exe`;
- worktime events имеют `source=awatch-agent-rs`;
- `AWatchRusCollectorGuard` запущен и его child process -
`aw-windows-telemetry.exe collector-guard`;
- guard не создает duplicate legacy browser/fileops/DLP endpoint collectors
при stale bucket: при уже активных collectors launch tasks не запускаются
повторно;
- 1C ClickHouse ingest идет по `aw-1c-ingest-rust`;
- PowerShell worktime process count = 0 в штатном режиме.
- PowerShell `aw-collector-guard.ps1` process count = 0 в штатном режиме.
### Phase 3. Runtime collectors
Цель: заменить регулярный сбор данных на Windows.
Порядок:
1. `browser-domains-native-collector.ps1`
- current status: live Rust primary through
`aw-windows-telemetry.exe browser-domains-collector`;
- ActivityWatch window/category health events are written with
`source=aw-windows-telemetry-rust`;
- UIAutomation URL/domain extraction implemented in Rust: browser process
detection, normalized URL, host/rootDomain, default/custom category rules,
`aw-watcher-web-*`, `aw-detmir-web-category_*` and web DLP incident
schema preserve the legacy bucket/event contract;
- live disconnected RDP sessions may legitimately report
`browserDetected=false` and `urlDetected=false` until a browser is
foreground in an interactive user session;
- screenshots только для DLP incident evidence.
2. `dlp-endpoint-signals-collector.ps1`
- current status: live Rust primary through
`aw-windows-telemetry.exe dlp-endpoint-collector`;
- endpoint collector health is written with
`source=aw-windows-telemetry-rust`;
- clipboard metadata/hash/length, USB insert, print job metadata and DLP
incident event semantics implemented in Rust with legacy fields:
`requestedAction`, `enforcementMode`, `nativeChannelAction`,
`enforcementSuppressed`, content pack matches and `enforced`;
- destructive endpoint enforcement for USB write-block and print cancel is
intentionally not enabled during pilot hardening: Rust emits equivalent
audit/incident semantics and suppresses unsafe block actions unless a
separate enforcement decision is made;
- incident screenshot policy remains: screenshots only for DLP events.
3. `file-operations-collector.ps1`
- current status: live Rust primary through
`aw-windows-telemetry.exe file-operations-collector`;
- bounded filesystem watcher, operation schema, queue/spool and
create/rename/delete smoke are verified;
- per-session queue/state/log files are used, so RDP sessions do not share
one state file;
- legacy PowerShell remains installed only as rollback/reference.
4. `email-outbound-collector.ps1`
- metadata-only parity;
- Outlook/SMTP mode behavior preserved;
- no content interception beyond current documented behavior.
5. `dlp-policy-client.ps1`
- Rust policy fetch/cache;
- strict validation;
- safe fallback to last known good policy.
Shadow-mode:
- Rust collector writes `source=<rust-component>` and `mode=shadow`;
- PowerShell remains primary during comparison;
- compare event counts, schema, timestamps, severity, policy hits and evidence
links for at least several collection cycles.
Gate:
- no duplicate management conclusions in portal;
- event schema compatible with current AW/DLP consumers;
- no stale buckets introduced;
- no uncontrolled screenshot capture;
- Rust collector survives AW API outage by spooling and later flushing.
### Phase 4. Guard, service wrapper и recovery
Цель: заменить PowerShell, который управляет процессами и восстановлением.
Компоненты:
- `aw-standalone-service.ps1`;
- `aw-collector-guard.ps1`;
- `hardening-recovery.ps1`;
- `rebuild-worktime-tasks.ps1`;
- `fix-session-watchers.ps1`;
- `cleanup-disc-sessions.ps1`.
Действия:
1. Сделать Rust Windows service wrapper:
- supervises configured collectors;
- records child process state;
- no hidden PowerShell spawn in normal mode.
2. Сделать Rust guard/recovery:
- allowlist actions only;
- lock file;
- cooldown;
- dry-run by default for destructive or repair actions;
- audit entry for every change.
3. Перевести rebuild/fix/cleanup в subcommands одного maintenance EXE.
Gate:
- controlled restart of one collector works;
- stale collector detection matches old guard;
- recovery cannot restart arbitrary process/service;
- rollback restores PowerShell guard/service within one operator action.
### Phase 5. Install/deploy/validate
Цель: install-kit перестает запускать PowerShell как штатный bootstrap.
Компоненты:
- `install-standalone-service.ps1`;
- `install-collector-guard-service.ps1`;
- `install-dlp-client.ps1`;
- `deploy-single-user.ps1`;
- `deploy-domain-users.ps1`;
- `deploy-ensemble.ps1`;
- `validate-deployment.ps1`;
- `migrate-awatch-rus-paths.ps1`;
- InnoSetup `AWatch-rus-InnoSetup.iss`;
- Ansible Windows playbooks/group vars.
Действия:
1. Создать Rust bootstrap/installer EXE:
- install/update service;
- install/update scheduled tasks;
- write config atomically;
- backup previous config/tasks;
- emit JSON validation report.
2. Перевести validation в Rust:
- task actions;
- service status;
- bucket freshness;
- file permissions;
- rust binary version matrix;
- no PowerShell runtime task check.
3. Обновить InnoSetup:
- package `*.exe`;
- run Rust bootstrap;
- keep `.ps1` out of normal install payload after stabilization.
4. Обновить Ansible:
- deploy EXE;
- configure task/service actions to EXE;
- remove default `.ps1` paths from bot/env after migration.
Gate:
- clean install on test Windows host;
- upgrade from PowerShell install to Rust install;
- rollback to previous package;
- `validate-deployment` JSON is consumed by CI/operator without parsing human
text;
- install-kit verification confirms no `powershell.exe` in primary install
action.
### Phase 6. Ops-only хвост
Цель: убрать одноразовые PowerShell helpers.
Компоненты:
- `audit-cryptopro.ps1`;
- `run-user1-probe.ps1`;
- `scripts/powershell/detmir-powershell-profile.ps1`;
- `.pssa_run.ps1`.
Решение:
- `audit-cryptopro.ps1` -> Rust audit CLI;
- `run-user1-probe.ps1` -> Rust diagnostic probe or remove if obsolete;
- operator PowerShell profile is not production runtime and can be retired
after Rust/SSH operator commands exist;
- `.pssa_run.ps1` removed when no product PowerShell remains.
Gate:
- no production task/service depends on these helpers;
- docs no longer instruct operator to run PowerShell for routine checks;
- one emergency manual path remains documented, but not packaged as runtime.
### Phase 7. Decommission
Цель: убрать PowerShell from product surface.
Действия:
1. Remove `.ps1` from install-kit file list.
2. Remove `powershell.exe` installer run action.
3. Remove default `.ps1` paths from Ansible/bot env.
4. Update architecture/docs:
- Windows collectors are Rust EXE;
- PowerShell no longer prerequisite for runtime;
- rollback history documented separately.
5. Run tracked-file hygiene scan for public docs.
Gate:
```bash
rg -n "powershell\\.exe|\\.ps1|PowerShell" windows ansible docs scripts README.md
```
Expected result:
- only historical notes, explicit rollback docs, or non-runtime examples remain;
- no install-kit primary action launches PowerShell;
- no AWatch-rus Scheduled Task action launches PowerShell in live validation.
## 7. Rollout по хостам
Порядок раскатки:
1. Local build host:
- build Windows EXE;
- artifact check;
- installer dry-run.
2. Test Windows/RDP host:
- install Rust EXE side-by-side;
- shadow-mode collectors;
- compare with PowerShell.
3. Canary production Windows/RDP host:
- one host, one business day;
- monitor bucket freshness, DLP incidents, 1C upload, evidence upload.
4. Remaining Windows hosts:
- staged batches;
- no more than one failure domain at a time.
5. AW server / Proxmox / gateway:
- update Ansible/bot references;
- verify server Rust services remain active;
- verify ClickHouse ingest and portal health.
6. Install-kit:
- publish Rust-first package;
- keep previous package as rollback artifact.
## 8. Acceptance gates
Local gates:
```bash
cd adk-rust
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo build --workspace --release
cargo build --release --target x86_64-pc-windows-gnu -p awatch-agent-rs -p aw-windows-telemetry
```
Repository gates:
```bash
scripts/check_detmir_rust_release_artifacts.sh
scripts/verify_innosetup_installer.sh
git diff --check
```
Windows gates:
```powershell
Get-ScheduledTask | Where-Object {
$_.TaskName -like 'ActivityWatch*' -or $_.TaskName -like 'AWatch*'
} | Select-Object TaskName,TaskPath,State
```
Expected:
- task actions point to Rust EXE for migrated components;
- no migrated component runs `powershell.exe`;
- logs are fresh;
- spool is empty or draining;
- AW buckets are fresh;
- DLP evidence sync only uploads DLP incident evidence;
- 1C upload does not copy screenshots.
Server gates:
```bash
detmir-status --json
detmir-check --json
systemctl --failed --no-pager
systemctl list-timers aw-1c-ingest.timer --no-pager
```
Expected:
- DetMir severity is OK or explained WARN;
- no failed Rust services;
- ClickHouse writer timer is active;
- portal data freshness is acceptable for pilot.
## 9. Rollback model
До decommission каждая миграция хранит rollback:
- previous EXE/script backup in `switch-backups`;
- previous task/service definition;
- previous `deployment-config.json`;
- one-command switch back for canary host;
- rollback reason written to audit log.
Rollback триггеры:
- AW bucket stale/dead after migration;
- DLP event loss or uncontrolled duplicate events;
- evidence upload fails repeatedly and spool grows;
- 1C upload stops producing landing files;
- service/guard restarts loop;
- Windows host shows sustained CPU/RAM regression from new EXE;
- user-visible pilot portal data quality degrades.
## 10. Риски
| Риск | Где | Снижение риска |
|---|---|---|
| Windows API differs from PowerShell cmdlets | DLP, tasks, Event Log, print, WMI | parity fixtures, canary, schema comparison |
| UIAutomation/browser URL extraction changes behavior | browser collector | shadow-mode and domain count comparison |
| Outlook/SMTP metadata behavior differs | email collector | metadata-only parity, explicit mode tests |
| Privilege mismatch | services/tasks/recovery | install under same account, explicit elevation, validation |
| AV/EDR blocks unsigned EXE | Windows hosts | code signing plan, allowlist, staged rollout |
| Duplicate events during shadow | all collectors | shadow source tags, portal ignores shadow for KPI |
| Rollback not fast enough | production canary | backup tasks/configs, single switch command |
| Public docs leak live contour details | tracked docs | placeholders and `git grep` hygiene scan |
## 11. Минимальный порядок ближайших работ
1. Зафиксировать live inventory PowerShell usage на тестовом Windows/RDP host.
2. Закрепить уже сделанные Rust paths:
- worktime/RDP;
- 1C file upload;
- DLP evidence sync.
3. Расширить `aw-windows-telemetry.exe validate-deployment` до полной parity с
`validate-deployment.ps1`, потому что он станет главным gate для следующих
замен.
4. Перенести `aw-standalone-service.ps1` и `aw-collector-guard.ps1`, потому
что они управляют runtime collectors.
5. Стабилизировать P0 runtime collectors после live Rust-primary switch:
- browser domains;
- DLP endpoint signals;
- file operations.
URL/domain extraction и clipboard/USB/print incident semantics закрыты на
уровне Rust code/schema/runtime self-test. Следующий шаг перед удалением
legacy `.ps1` - burn-in, canary rollback test и live foreground-browser
proof в интерактивной RDP-сессии.
6. Перенести deploy/install scripts и InnoSetup primary action.
7. Убрать PowerShell paths из Ansible/bot env.
8. Удалить `.ps1` из install-kit и оставить только historical rollback docs.
## 12. Итоговая оценка
Миграция реалистична, потому что серверная часть уже Rust-first, а Windows
контур уже имеет два рабочих Rust основания: `awatch-agent-rs` и
`aw-windows-telemetry.exe`.
Критичный участок не сервер, а Windows runtime и install-kit:
- `dlp-endpoint-signals-collector.ps1`;
- `browser-domains-native-collector.ps1`;
- `aw-collector-guard.ps1`;
- `aw-standalone-service.ps1`;
- `validate-deployment.ps1`;
- InnoSetup action that still launches PowerShell.
До пилота не нужно удалять весь PowerShell хвост. Для пилота достаточно
закрепить уже работающие Rust-primary paths, не допустить копирования
скриншотов в 1C контуре, оставить скриншоты только для DLP events, иметь
понятный rollback и честно формулировать границу parity: code/schema/runtime
path закрыт, а полный live URL/domain incident proof требует активного
foreground browser в интерактивной RDP-сессии.
Полное удаление PowerShell из install-kit лучше делать после canary и shadow
parity по DLP/browser/file collectors.