# Execution Tracker Дата обновления: 2026-03-15 Владелец: Engineering ## Статусы фаз - [x] A. Аудит API и HTTP-маршрутов - [~] B. Проверка ядра и внешних зависимостей - [~] C. Подготовка к веб-совместимости - [~] D. Документирование и итоговые проверки - [~] E. Дизайн multi-client PBR и anti-conflict guardrails - [~] F. Рефакторинг и модульность (декомпозиция крупных файлов GUI/API/Go) ## Порядок реализации (фикс) - 1. Сначала полностью закрываем backend-интеграцию transport-клиентов (`singbox`, `dnstt-client`, `phoenix`) в Go-ядре. - 2. Затем переиспользуем backend-контракт в desktop GUI (без дублирования бизнес-логики). - 3. Веб-прототип (`Vite + React + TS`) начинаем только после завершения backend+GUI этапа. ## Фокус текущего этапа (freeze) - Desktop-first: сейчас работаем только по `SingBox` вкладке и `SingBox` Go API. - `DNSTT` и `Phoenix`: backend foundation/e2e уже зафиксированы, UI-вкладки и протокольные экраны для них в этом этапе не развиваем. - Приоритет реализации: - временно сужаем активный backend scope до `E3.3/E3.6`: - единый `interface orchestrator` path для всех mutating transport-операций, - `validate -> plan -> confirm -> apply -> health-check -> commit` без частично подтверждённого runtime; - `E5.4` GUI-протоколы внутри отдельной вкладки `SingBox` не расширяем, пока `E3.3/E3.6` не доведены до стабильного backend-state. ## Текущие шаги - Step A1: [x] каталогизация маршрутов и сопоставление с Go-обработчиками - Step A2: [x] валидация GUI как клиента (api_client.py, dashboard_controller) - Step B1: [~] оценка nftables/systemd/SmartDNS зависимостей (`routes_*`, `traffic_*`, `dns_*`, `smartdns_runtime`) - Step B2: [~] запись требований root/long-running/состояний для веба - Step B3: [~] resolver hardening plan: зафиксировать разницу `system resolver` vs `singbox DNS` и закрыть backlog улучшений - Step C1: [~] ревью binding и SSE (`server.go`, `events_bus.go`, `trace_handlers.go`) - Step C2: [x] запись потребностей по CORS/auth/token для веб-интерфейса - Step C3: [x] зафиксирован стек web prototype: `Vite + React + TypeScript` (SPA, без Next.js на MVP-этапе) - Step C4: [x] создан web foundation-модуль `selective-vpn-web/` (routing + query + SSE connectivity, без mutating controls) - Step D1: [x] матрица endpoint → handler → dependencies + web-ready - Step D2: [x] чеклист проверок (curl, SSE, VPN login) - Step D3: [~] последовательность запуска multi-client (web + iOS + Android) зафиксирована - Step D4: [~] зафиксирован транспортный интеграционный бэклог (`sing-box client`, `dnstt-client`, `phoenix->slipstream`) - Step D4.1: [x] внедрён минимальный общий transport backend-контракт в Go (`lifecycle + health + metrics + unified errors + capabilities contract hints`) - Step D4.2: [~] внедрены backend-адаптеры foundation (`mock/systemd`) + template `exec_start` + restart/watchdog tuning + unit hardening + `runtime_mode` foundation + packaging updater/rollback foundation для `singbox|dnstt|phoenix` - Step D4.2.8: [x] e2e backend-проверки по клиентам (`singbox -> dnstt(+ssh) -> phoenix`) через API lifecycle/runbook - Step D4.2.9: [x] добавлен операционный runbook helper (`scripts/transport_runbook.py`) + smoke (`tests/transport_runbook_cli_smoke.sh`) - Step D4.2.10: [x] добавлены real-systemd e2e и backend-cleanup unit artifacts при delete client - Step D4.2.11: [x] добавлены production-like e2e (template commands + packaging profiles `bundled|system`) - Step D4.2.12: [x] добавлен recovery runbook (health->restart->recheck->provision/start fallback + diagnostics) - Step D4.2.13: [x] внедрён `singbox` bootstrap-bypass в Go backend (`transport`): endpoint `/32` host-routes в `table agvpn` через `main` default-route перед `start/restart`, cleanup на `stop` - Step D4.2.14: [x] добавлен `netns` test-contour для transport backend (`setup/cleanup + NAT + optional strict mode`) - Step D4.2.15: [x] миграция legacy DNS-конфига `sing-box` (`address -> type/server`) в `Provision()` + backup + strict-mode - Step D4.3: [x] зафиксирована матрица совместимости `web + iOS + Android` (единый transport control-plane контракт + platform constraints) - Step V1: [x] стабилизация UX списка VPN локаций (async + cache + auto-apply + лёгкий manual refresh trigger) - Step V2: [x] добавить "умный поиск" в списке локаций (по набору символов без отдельной строки поиска) - Step E1: [x] подготовлен дизайн multi-client PBR (`docs/phase-e/E1_MULTI_CLIENT_PBR_DESIGN.md`) - Step E2: [x] спроектирован API `transport/*` (clients/policies/validate/apply/conflicts) - Step E2.2: [x] реализован dry-run validator конфликтов (`ownership`, `cidr_overlap`, `unknown_client`) - Step E2.3: [x] реализован apply flow (`base_revision`, `confirm_token`, snapshot rollback, SSE events) - Step E3.1: [x] усилен allocator (`mark/pref` резервы, auto re-balance, deterministic restore) - Step E3.2: [x] добавлен endpoint-level rollback (`/transport/policies/rollback`) - Step E3.3: [x] реализовать единый interface orchestrator в Go (`create/bind/start/stop/cleanup` для engine-инстансов, netns/table sync без конфликтов) - Step E3.3.0: [x] зафиксирован execution roadmap мультиинтерфейса (`docs/phase-e/E3_MULTI_INTERFACE_EXECUTION_PLAN.md`) - Step E3.3.1: [x] foundation-этап: `iface_id` в `transport client` + state `transport-interfaces.json` + `GET /api/v1/transport/interfaces` (без изменения data-plane) - Step E3.3.2: [x] внедрить interface orchestrator lock+mapping (`iface_id -> runtime iface/netns/table`) с единым lifecycle path - Step E3.3.2.1: [x] добавлен per-`iface_id` lock manager для mutating lifecycle/provision операций (`start/stop/restart/provision`) в transport-контуре; операции одного интерфейса сериализуются - Step E3.3.2.2: [x] runtime mapping-layer завершён: `iface_id -> routing_table` (dedicated iface default + interface hints + sync в create/patch/lifecycle/netns-toggle/provision) + owner-scoped nft compile naming (`iface+client+selector`) без shared-set mixing - Step E3.3.2.3: [x] оставшиеся mutating transport-paths переведены на iface-aware orchestration lock: - `create`, `PATCH /transport/clients/{id}`, `DELETE /transport/clients/{id}`, `POST /transport/netns/toggle`; - добавлен multi-`iface_id` lock helper с детерминированным порядком захвата для patch/multi-target операций; - `lifecycle start/restart` теперь расширяют lock-set до всех `same-netns` `SingBox` peer-ов, включая binding через `iface -> netns`, поэтому cross-`iface_id` netns-конфликт не обходит orchestrator; - `netns toggle` больше не делает прямой `backend restart`: restart/provision теперь идут через общий backend execution path (`provision` + lifecycle preflight/orchestrator), поэтому peer-stop guard, egress refresh и runtime commit не дублируются отдельной веткой; - lock-resolver для lifecycle/netns toggle теперь использует `same-netns` peer matching без фильтра по runtime-status (lock захватывает всех netns-peer клиентов), чтобы избежать гонок при stale/lag status и не пропускать cross-iface сериализацию. - lifecycle/provision/create/patch/delete/netns теперь используют общий `iface` serialization pattern, без отдельной GUI/local orchestration-ветки. - Step E3.3.3: [x] внедрён M3 per-interface policy compiler + atomic apply executor foundation: - compile-plan `iface_id -> table/mark/pref/nft-set` подключён в validate/apply/rollback/get-policy; - apply/rollback теперь проходят через atomic runtime executor с snapshot/restore (`transport-policies.runtime.json`, `transport-policies.runtime.prev.json`) до commit policy revision; - подключён kernel stage в executor: - nft stage: per-interface CIDR set apply/cleanup (`inet agvpn`, managed sets `agvpn_pi_*`); - optional ip rule stage (`fwmark/pref -> lookup table`) под отдельным флагом; - kernel stage включается через env-флаги: `SVPN_TRANSPORT_POLICY_KERNEL_APPLY=1`, `SVPN_TRANSPORT_POLICY_KERNEL_IPRULES=1`. - M3 owner scope закрыт: compile-plan теперь генерирует owner-scoped `nft_set` (`owner_scope=iface+client`), что исключает shared-set mixing для нескольких клиентов на одном `iface_id`. - Step E3.4: [x] внедрён strict ownership registry для policy-intent (single-owner на domain/ip/cidr/app intent, явные conflict reason до apply) - Step E3.4.1: [x] добавлен persisted ownership registry (`transport-ownership.json`) + endpoint `GET /api/v1/transport/owners` (auto-rebuild из compile-plan при рассинхроне revision) - Step E3.4.2: [x] apply-guardrails усилены: `force_override` разрешён только для `owner_switch`; non-overridable block (`ownership`, `cidr_overlap`, `unknown_client`, allocator conflicts) не обходятся override-флагом - Step E3.4.3: [x] read-side ownership rebuild усилен `plan_digest` guard: `GET /api/v1/transport/owners` пересобирает ownership не только по `policy_revision`, но и при drift compile-plan digest; в response добавлен `plan_digest`, ownership records возвращают `owner_scope` - Step E3.5: [x] добавлен anti-mixing guard (`conntrack` stickiness + destination owner lock), чтобы один destination не ходил через два engine одновременно - Step E3.5.1: [x] добавлен runtime owner-lock guard в validate/apply: owner switch блокируется, если previous owner клиента активен (`up|starting|degraded`); `force_override` это не обходит - Step E3.5.2: [x] расширен observability ownership: `GET /api/v1/transport/owners` теперь возвращает `owner_status` + `lock_active` по каждой записи и агрегат `lock_count` - Step E3.5.3: [x] добавлен conntrack stickiness foundation: kernel stage умеет собирать destination-lock state (`transport-owner-locks.json`) по `mark->owner` mapping; endpoint `GET /api/v1/transport/owner-locks`; validate/apply блокируют owner-switch по `cidr` при активных `destination_lock` - Step E3.5.4: [x] добавлен безопасный recovery clear-flow для owner-locks: `POST /api/v1/transport/owner-locks/clear` (filter: `client_id`, `destination_ip(s)`), двухшаговый confirm-token (`clr-*`), без unscoped clear-all - Step E3.5.5: [x] расширен destination-lock guard на `domain` selector: owner-switch по `domain`/`*.domain` теперь учитывает resolver `domain-cache` (`direct+wildcard`) и блокируется при sticky-совпадении `destination_ip` у previous owner - Step E3.6: [x] расширен apply pipeline до `validate -> plan -> confirm -> apply -> health-check -> commit` с auto-rollback при fail health-check - Step E3.6.1: [x] добавлен post-apply transport health-check до policy commit: - apply/rollback после runtime apply выполняют backend health probe по активным transport-owner клиентам из compile-plan; - для `enabled/up|starting|degraded` клиентов failure/down считается commit-blocking, inactive draft clients (`enabled=false`, `status=down`) пропускаются; - при health-check fail выполняется runtime auto-rollback на `transport-policies.runtime.prev.json`, policy revision не коммитится; - `TransportPolicyResponse` дополнен `health_check` summary для GUI/Web наблюдаемости. - Step E3.6.2: [x] добить transaction pipeline: - уменьшить удержание глобального `transportMu` на long-running runtime steps после стабилизации orchestrator path; - расширить health gate на более полный per-interface observability/runtime coverage. - Step E3.6.2.1: [x] добавлен persisted `Idempotency-Key` storage/replay для mutating policy endpoints: - `POST /api/v1/transport/policies/apply` и `POST /api/v1/transport/policies/rollback` сохраняют response snapshot по `(scope, idempotency_key, request_hash)`; - повтор того же ключа с тем же payload возвращает сохранённый response без повторного runtime apply/rollback и без инкремента `policy_revision`; - reuse того же ключа с другим payload блокируется кодом `IDEMPOTENCY_KEY_REUSED`; - state вынесен в отдельный persisted backend store (`transport-policy-idempotency.json`) с pruning по TTL/size. - Step E3.6.2.2: [x] уменьшить удержание глобального `transportMu` на long-running runtime steps после стабилизации orchestrator path - `transport client provision` переведён на двухфазный flow: - под `transportMu` остаются только snapshot/bind и final commit, - сам `backend.Provision()` выполняется под `iface`-lock, но вне глобального mutex; - `transport client lifecycle` переведён на двухфазный flow: - `same-netns` peer-stop и target `backend.Action()` выполняются под `iface`-lock set, но вне глобального mutex; - под `transportMu` остаются только snapshot/bind/reload/final commit, включая commit peer-stop результатов; - partial peer-stop failure теперь сохраняет уже выполненные peer changes в state до возврата ошибки target lifecycle, без silent drift между runtime и persisted state; - `transport netns toggle` переведён на такой же двухфазный flow: - config patch/save остаётся под `transportMu`, - долгие `provision/restart` шаги выполняются уже через общий provision/lifecycle path под `iface`-lock set, но вне глобального mutex; - lock resolver для toggle теперь заранее включает `same-netns` peer `iface_id`, если restart затрагивает binding после toggle. - Step E3.6.2.3: [x] расширен health gate на более полный per-interface observability/runtime coverage - `TransportPolicyResponse.health_check` возвращает per-`iface_id` summary c runtime-полями: - `iface_id/mode/runtime_iface/netns_name/routing_table`, - `client_count/checked_count/failed_count/skipped_count`, - `status`, `latency_ms`, `last_error`, `active_client_id`, - interface-level `ok/message` для GUI/Web без ручной агрегации по client items; - summary строится из того же post-probe runtime snapshot (включая netns-клиентов), что и client-level health items, без отдельной UI-склейки. - Step E3.6.2.4: [x] сокращён hold-time `transportMu` в read-side snapshot handlers (`/transport/interfaces`, `/transport/policies`, `/transport/ownership`, `/transport/runtime/observability`): - под `transportMu` выполняется только короткий state snapshot (`clients/interfaces/policy/plan/ownership`) + token capture; - нормализация/compile-plan выполняются вне глобального mutex; - добавлен snapshot-aware conditional commit helper (`compileTransportPolicyPlanForSnapshot`, `saveTransportInterfacesIfSnapshotCurrent`, `saveTransportPlanIfSnapshotCurrent`, `saveTransportOwnershipIfSnapshotCurrent`) для safe save без stale overwrite; - API/DTO контракты endpoint’ов сохранены без изменений. - Step E3.6.2.5: [x] доведён аналогичный snapshot/conditional-save подход для `netns` toggle-пути (`POST /api/v1/transport/netns/toggle`): - lock-id resolver теперь использует короткий snapshot (`clients/interfaces`) под `transportMu`, нормализация interfaces и conditional-save выполняются вне глобального mutex; - в execute-path убран persist interfaces из-под `transportMu`: используется in-memory normalize для binding, а persist вынесен в post-unlock `conditional-save helper`; - добавлен pure helper `resolveTransportLifecycleLockIDsForSnapshot` (in-memory), lock-resolver теперь использует snapshot-state без повторного I/O под mutex; flaky unit-case стабилизирован. - устранён stale-guard gap для mutating netns-path: post-commit persist interfaces больше не зависит от `clients.updated_at` (используется interfaces-only snapshot), поэтому нормализованные iface-записи не теряются после `saveTransportClientsState`. - mutating контракт netns-toggle сохранён (clients state commit остаётся atomic под `transportMu`, provision/restart по-прежнему вне глобального mutex). - Step E3: [x] схема allocator'ов (`mark/table/pref`) и стратегия atomic apply/rollback - Step E4.1: [x] зафиксирован UX-flow `validate -> confirm -> apply` для web/mobile - Step E4.2: [x] внедрён foundation state-machine в GUI controller (`draft/validated/risky/confirm/applied`) - Step E4.3.1: [x] добавлен GUI foundation-блок `Transport engine` (select + prepare/connect/disconnect/restart через `/api/v1/transport/clients/{id}/*`) - Step E4.3.2: [x] `Connect/Switch` переведён на pipeline `validate -> confirm -> apply` + добавлен `Rollback policy` button - Step E4.3: [~] детализирован UX-подпоток `Engine Switch / Connect` (desired/active engine, switch states, rollback action) - Step E4.4: [x] зафиксирован desktop-first дизайн общего multi-interface GUI блока (`Ownership + Destination locks + safe clear-flow`) в `docs/phase-e/E4_2_MULTI_INTERFACE_GUI_DESIGN.md` - Step E4.5: [x] desktop GUI подключён к ownership/lock API: - read-only панель `Ownership & destination locks` во вкладке `SingBox` (таблицы ownership + destination locks, summary/revision); - refresh-хук встроен в `refresh_singbox_tab` и transport refresh-поток; - подключён безопасный clear-flow через `POST /api/v1/transport/owner-locks/clear` (2-step confirm token, без optimistic update, только re-fetch). - Step E4.5.1: [x] desktop GUI подключён к `GET /api/v1/transport/interfaces`: - в панели `Ownership & destination locks` добавлена таблица интерфейсов (`iface_id/mode/runtime_iface/netns/routing_table/client up/total`); - summary расширен данными `interfaces count + policy revision + intents count`. - Step E4.5.2: [x] добавлен desktop GUI policy-intents editor (в `SingBox` блоке `Ownership & destination locks`): - draft-таблица intents (`selector_type/value`, `client_id`, `mode`, `priority`); - actions: `Reload policy`, `Add intent`, `Remove selected`, `Validate policy`, `Apply policy`, `Rollback policy`; - apply использует backend flow `validate -> (optional confirm for risky) -> apply`, rollback через `/transport/policies/rollback`; - черновик не перетирается авто-refresh'ем при dirty state. - Step E4.5.3: [x] UX-polish policy-intents в MultiIF: - добавлены quick templates (`domain/wildcard/cidr/ip/app_key/uid`) с prefill без auto-add; - добавлен `Load selected` + double-click по строке draft для обратной подгрузки intent в форму редактирования; - добавлен Enter-to-add для selector value и duplicate guard (не даёт добавить полностью идентичный intent); - в draft-таблице клиент показывается как `name (client_id)` для лучшей читаемости. - Step E4.5.4: [x] добавлено inline-редактирование draft intent в MultiIF: - добавлена кнопка `Update selected` (обновляет выбранную строку без remove/add); - общий validator формы переиспользуется для add/update (domain/cidr/uid guardrails едины); - duplicate guard поддерживает `skip_index` при update (строку можно обновлять без ложного self-duplicate). - Step E4.5.5: [x] MultiIF policy/ownership отвязаны от `transport-only` и включают `AdGuard VPN` как virtual policy target: - в backend compile/validate/apply/rollback path добавлен virtual client `adguardvpn` (без записи в `transport-clients.json`); - ownership/locks/read-side используют единый `policyTargets` snapshot (`transport clients + adguardvpn`); - в GUI включён scope-filter `All/Transport/AdGuard VPN`, client selector для intents поддерживает `AdGuard VPN (adguardvpn)`; - сняты artificial guard-блоки `adguard-only`, чтобы low-level MultiIF слой работал единообразно для всех engine. - Step E4.5.6: [x] интерфейсный слой MultiIF переведён на backend-source-of-truth для AdGuard: - `GET /api/v1/transport/interfaces` теперь добавляет virtual interface-row `adguardvpn` (status/iface/table/up-count) из backend observer; - GUI больше не строит adguard-строку локально через `vpn_status_model`/egress-хелперы; - filter `All/Transport/AdGuard VPN` применяется единообразно к interfaces/ownership/locks таблицам. - Step E4.5.7: [x] добавлен быстрый UX-flow для первичной проверки MultiIF policy editor: - кнопка `Add demo intent` создаёт тестовый `domain` intent (`demo.invalid`, авто-уникализация `demo-N.invalid`) на выбранный client; - quick-help обновлён до пошагового сценария `Add demo/fill -> Validate -> Apply`; - flow использует тот же draft/validate/apply pipeline без отдельной ветки логики. - Step E4.5.8: [x] в MultiIF добавлено визуальное разделение `Draft` и `Applied` policy intents: - добавлена отдельная read-only таблица `Applied intents` (текущая backend policy); - status/state строка теперь показывает оба счётчика (`draft` и `applied`) для быстрого сравнения; - API-unavailable/error paths очищают обе таблицы синхронно, чтобы не было stale-данных. - Step E4.5.9: [x] добавлена визуализация конфликтов валидации policy (MultiIF): - отдельная read-only таблица `Validation conflicts` (`type/severity/owners/reason/suggested resolution`); - таблица синхронизируется из `validate/apply` flow и сохраняет последний результат проверки; - при API-unavailable/endpoint-error таблица очищается вместе с draft/applied, чтобы не показывать stale-conflicts. - Step E4: [~] UX предупреждения и conflict flow (`validate -> confirm -> apply`, включая engine switch/connect) - Step E5: [~] зафиксировать требования для протоколов во вкладке `SingBox` и target Go API `singbox profiles` - Step E5.1: [x] requirements freeze для `SingBox Protocols` (UI + Go API + storage/events) - Step E5.1.2: [x] собран шаблон и матрица полей по протоколам `singbox` (`vless/trojan/shadowsocks/wireguard/hysteria2/tuic`) + JSON manifest для генерации форм - Step E5.1.3: [x] зафиксирована client-side UI матрица (по блокам формы, VLESS baseline + guardrails, без server-only полей) - Step E5.2: [x] реализовать Go model/state для `singbox profiles` (CRUD + versioning + secrets store) - Step E5.3: [x] реализовать Go flow `validate/render/apply/rollback/history/features` для `singbox profiles` - Step E5.4: [~] реализовать GUI-блок протоколов в `SingBox` вкладке (list/editor/validate/preview/apply/rollback) - Step E5.4.1: [x] внедрён desktop foundation-дизайн `SingBox` вкладки: `connection card` + `profile settings` + `global defaults` - Step E5.4.2: [x] подключены GUI-кнопки `Validate profile`/`Apply profile` к Go API `/api/v1/transport/singbox/profiles/{id}/validate|apply` (с activity-log и runtime refresh) - Step E5.4.3: [x] реализованы `Preview render` + `Rollback profile` + `History` и auto-link `engine -> profile` (auto-create/patch по `client_id`) + лёгкий рефакторинг handlers вкладки `SingBox` - Step E5.4.4: [x] добавлен VLESS client-editor flow в GUI: auto-load по выбранному engine/profile, `Save draft` в Go API (`raw_config`), auto-sync draft перед `Preview/Validate/Apply` - Step E5.4.5: [x] `Connection profiles` переведены в dashboard-плитки с context-menu `Run/Edit/Delete`; активный профиль подсвечивается зелёным, protocol editor вынесен в отдельный modal edit-dialog - Step E5.4.6: [x] улучшен editor UX: non-destructive переключение `security/transport` (без reset полей), `Flow` сделан editable (preset + custom), добавлен `Create connection` (`Clipboard|Link|Manual`) - Step E5.4.7: [x] унифицирован link-import pipeline: общий dispatcher + shared raw-config helpers для `vless/trojan/ss/hysteria2(hy2)/tuic`, без привязки к одному протоколу - Step E5.4.8: [x] расширен form-editor на `trojan/shadowsocks/hysteria2/tuic` (единая форма + protocol switch + guardrails + raw save по выбранному protocol) - Step E5.4.9: [x] добавлен `wireguard` в единый editor/import pipeline (поля WG + raw save + wireguard:// link parser) - Step E6: [x] реализовать единый Go-сервис `egress identity` (IP + geo/country) для всех движков (`AdGuardVPN`, `transport:*`) - Step E6.1: [x] зафиксировать backend-контракт `/api/v1/egress/identity` + `/api/v1/egress/identity/refresh` (scope-aware: `adguardvpn|transport:|system`) - Step E6.2: [x] реализовать provider-адаптеры в Go (общий интерфейс источника egress IP; netns-aware для transport-клиентов) - Step E6.3: [x] добавить общий SWR/cache/backoff для egress identity (без блокировки UI, с `stale/updated_at/last_error`) - Step E6.4: [x] добавить GeoIP-слой (country_code/country_name) и нормализованный ответ для GUI/Web/Mobile - Step E6.5: [x] интегрировать в desktop-карточки `AdGuardVPN` и `SingBox` (показывать `IP + country`, флаг рендерится в UI из `country_code`) - Step E6.6: [x] добавить unified runtime observability API для multi-interface (`active_iface`, `egress`, `latency`, `last_error`, counters per engine/policy) - Step E6.6.1: [x] добавить backend DTO snapshot для карточек multi-interface (`client_id`, `iface_id`, `active_iface`, `egress`, `latency`, `last_error`, counters) - новый backend aggregator собирает per-`iface_id` runtime snapshot из `transport interfaces + clients + policy compile-plan + egress identity`; - DTO включает binding (`runtime_iface/active_iface/netns/routing_table`), active client, aggregate `status/latency/last_error`, status counters и `engine_counts/rule_count` без ручной склейки на UI. - Step E6.6.2: [x] endpoint `GET /api/v1/transport/runtime/observability` + SSE `transport_runtime_snapshot_changed` - handler и SSE publisher переиспользуют один и тот же backend snapshot builder без отдельной event-specific логики; - `transport_runtime_snapshot_changed` публикуется после create/patch/delete, lifecycle, health refresh, policy apply/rollback, netns toggle и transport egress updates; - payload события содержит unified snapshot (`items`) и метаданные `reason/client_ids/iface_ids/generated_at`, так что UI может либо re-fetch endpoint, либо обновиться напрямую тем же DTO. - Step E6.7: [~] (обязательный post-transport этап) подключить `AdGuardVPN` через adapter к unified engine control-plane (без ломки autoloop/login/location flow), чтобы multi-interface observability/control был единым для всех движков - Step E6.7.1: [x] backend adapter-путь `adguardvpn` подключён к unified transport control-plane: - `GET /api/v1/transport/clients?include_virtual=true` возвращает virtual client `adguardvpn` в том же DTO-контракте, что и transport clients; - `GET /api/v1/transport/clients/adguardvpn` + `health|metrics|start|stop|restart|provision` работают через virtual adapter без записи в `transport-clients.json`; - legacy `POST /api/v1/vpn/autoconnect` переведён на тот же adapter execution path, чтобы исключить рассинхрон между old VPN flow и unified control-plane; - runtime observability (`/transport/runtime/observability`) теперь включает virtual snapshot `adguardvpn` и использует scope-aware egress lookup (`adguardvpn` вместо `transport:adguardvpn`). - Step E6.7.2: [x] GUI policy-editor переведён на unified policy targets из backend (`include_virtual=true`) без локального adguard-костыля: - добавлен отдельный UI-cache `transport_policy_clients` (source of truth для `policy client selector` и label-резолва); - client selector и draft/applied таблицы теперь используют общий backend-список targets (`transport + virtual`) и не подмешивают `adguardvpn` вручную; - refresh policy locks делает явный prefetch policy-targets, а при ошибке использует безопасный fallback на текущий transport-list, без остановки UI. - Step E6.7.3: [x] добавлена визуальная индикация типа policy target в MultiIF (`[transport]`/`[virtual]`): - policy client selector, draft/applied intents и ownership/locks таблицы показывают тип target в label; - interfaces таблица маркирует строки по типу target и отдельно помечает virtual-mode (`... | VIRTUAL`); - summary дополнен счётчиками targets (`transport=N`, `virtual=M`) и hint с legend по меткам. - Step E7: [ ] (deferred) разделить policy-слои `System selective PBR` и `SingBox L7 routing` (реализация позже, после текущего контура) - Step E7.1: [ ] зафиксировать единый L7 rule-contract для SingBox (`domain/suffix/keyword/regex`, `ip_cidr`, `port`, `network`, `protocol`, `process/user/package`, `rule_set`, `priority`, `action`) - Step E7.2: [ ] реализовать Go renderer `policy -> singbox.json` (`dns`, `route.rules`, `rule_set`, `outbounds`, `final`) без ручного JSON в UI - Step E7.3: [ ] внедрить pipeline `validate -> dry-run -> apply -> rollback` для L7 policy (`sing-box check` обязателен) - Step E7.4: [ ] реализовать DNS-стратегию для L7 (`SingBox DNS policy` + `bootstrap bypass`) без дублирования intent в системном resolver-контуре - Step E7.5: [ ] добавить conflict guardrails между PBR и L7 (single-owner intent: правило живет только в одном policy-слое) - Step E7.6: [ ] добавить наблюдаемость L7 (`effective policy`, `active outbound`, `last match reason`, counters/hits per rule, egress per-engine) - Step F1: [~] выполнить поэтапный рефакторинг и модульность без изменения поведения - Step F1.1: [x] зафиксирован план декомпозиции крупных файлов (`docs/phase-f/F1_REFACTOR_MODULARITY_PLAN.md`) - Step F1.2: [x] netns-логика вынесена в отдельные модули GUI/API (`netns_debug.py`, `transport_netns_exec.go`) + оформлен runtime-case документ - Step F1.2.1: [x] orchestration netns-toggle перенесён в Go API endpoint `/api/v1/transport/netns/toggle`, GUI переключатель переведён на единый backend вызов - Step F1.2.2: [x] добавлен общий backend `refresh coordinator` (SWR + single-flight + backoff), `vpn locations` переведены на общую схему без изменения API-контракта - Step F1.2.3: [x] та же SWR-схема применена для `transport health` (`/transport/clients` background refresh + `POST /transport/health/refresh` + SSE `transport_client_health_changed`) - Step F1.6: [x] перевести точки входа Go API на `cmd/*` (отдельные бинари `api`, `routes-update`, `routes-clear`, `autoloop`) с сохранением legacy entrypoint - Step F1.7: [x] разрезать `selective-vpn-api/app/server.go` на `entrypoints/api_bootstrap/api_routes` без изменения API-контракта и CLI-поведения - Step F1.8: [x] вынести доменные route-helper'ы в отдельные `app/api_routes_*.go` файлы (без изменения endpoint-path/handler-контракта) - Step F1.9: [x] вынести CLI/bootstrap раннеры в подпакеты `app/cli` и `app/bootstrap` с сохранением фасадов `Run*` в `app` - Step F1.10: [x] разложить `app/transport_handlers.go` на модульные файлы (`handlers_clients`, `handlers_policy`, `policy_validate`, `client_runtime`, `tokens_state`, `shared`) без изменения endpoint-path - Step F1.11: [~] вынести переиспользуемую transport-логику в отдельные подпакеты (`app/transport/*`) только для частей без циклических зависимостей (через facade+deps) - Step F1.11.1: [x] вынесен runtime/config helper-блок transport backend в подпакет `app/transportcfg` с сохранением app-facade (`transport_backends_runtime_helpers.go`) - Step F1.11.2: [x] вынесен systemd helper-блок transport backend в `app/transportcfg` с сохранением app-facade (`transport_backends_systemd_helpers.go`) - Step F1.11.3: [x] вынесен exec/binary/template helper-блок transport backend в `app/transportcfg` с сохранением app-facade (`transport_backends_exec_helpers.go`) - Step F1.11.4: [x] вынесен probe/endpoint helper-блок transport backend в `app/transportcfg` с сохранением app-facade (`transport_backends_probe_helpers.go`) - Step F1.11.5: [x] вынесен in-memory SSE event-bus в подпакет `app/eventsbus` с сохранением app-facade (`events_bus.go`) - Step F1.11.6: [x] вынесен API route-registry в подпакет `app/apiroutes` (удалены `api_routes_{core,dns,trace,traffic,transport,vpn}.go`, сохранён единый `registerAPIRoutes` facade) - Step F1.11.7: [x] объединены misc transport adapters в `transport_backends.go` (удалён `transport_backends_adapters_misc.go`) - Step F1.11.8: [x] вынесены низкоуровневые command helpers в `app/syscmd` (`RunCommand`, `RunCommandTimeout`, `CheckPolicyRoute`) с сохранением фасада `app/shell.go` - Step F1.11.9: [x] вынесены общие HTTP helper'ы в `app/httpx` (`LogRequests`, `WriteJSON`, `HandleHealthz`) с сохранением фасада `app/http_helpers.go` - Step F1.11.10: [x] вынесен SSE stream-loop в `app/eventstream` (`ParseSinceID`, `Serve`) с сохранением фасада `app/events_handlers.go` - Step F1.11.11: [x] вынесен SWR/backoff coordinator в `app/refreshcoord` с сохранением фасада `app/refresh_coordinator.go` и без изменения поведения egress/vpn-locations/transport-health контуров - Step F1.11.12: [x] вынесен NFT update engine в `app/nftupdate` (atomic + chunked fallback) с сохранением фасада `app/nft_update.go` - Step F1.11.13: [x] вынесен traffic candidates collector в `app/trafficcandidates` с сохранением API-контракта endpoint `GET /api/v1/traffic/candidates` - Step F1.11.14: [x] устранена «россыпь bridge-файлов» resolver-контура: `resolver_*_bridge.go` схлопнуты в единый `app/resolver_bridge.go` (без изменения API/логики) - Step F1.11.15: [x] вынесен traffic app profiles store в `app/trafficprofiles` с сохранением API-контракта `GET/POST/DELETE /api/v1/traffic/app-profiles` - Step F1.11.16: [x] вынесена canonical `app_key` нормализация в `app/trafficprofiles` и удалён root-файл `traffic_appkey.go` (совместимость через app-facade функции сохранена) - Step F1.11.17: [x] вынесены state/dedupe/persistence helper'ы `traffic app marks` в подпакет `app/trafficappmarks` с сохранением runtime/API-контракта - Step F1.11.18: [x] вынесены cgroup path/inode helper'ы `traffic app marks` в `app/trafficappmarks` с сохранением фасадов в `app/traffic_appmarks.go` - Step F1.11.19: [x] вынесены NFT helper'ы `traffic app marks` в `app/trafficappmarks/nft.go` (insert/delete/has/local-bypass/handle parse/ipv4-compact) с сохранением фасадов в `app/traffic_appmarks.go` - Step F1.11.20: [x] вынесены cleanup helper'ы `traffic app marks` в `app/trafficappmarks/nft.go` (`CleanupLegacyRules`, `ClearManagedRules`) с сохранением фасадов в `app/traffic_appmarks.go` - Step F1.11.21: [x] вынесена TTL prune-логика `traffic app marks` в `app/trafficappmarks/store.go` (`PruneExpired`) с сохранением удаления runtime nft-правил через app-facade callback - Step F1.11.22: [x] вынесен базовый nft ensure bootstrap `traffic app marks` в `app/trafficappmarks/nft.go` (`EnsureBase`) с сохранением фасада `ensureAppMarksNft` в `app/traffic_appmarks.go` - Step F1.11.23: [x] вынесены общие `singbox profile flow` helper'ы в `app/transportcfg/singbox_helpers.go` (`typed protocol support`, `parse port`, `digest/diff config`, `json config file io`, `optional file restore`, `history stamp`, `issue message join`) с сохранением app-facade вызовов в `transport_singbox_profiles_flow.go` - Step F1.11.24: [x] вынесены history helper'ы `singbox profile flow` в `app/transportcfg/history_helpers.go` (`WriteFileAtomic`, `ReadJSONFiles`, `SelectRecordCandidate`, `DecodeBase64Optional`) и переведены вызовы `append/load/select/decode` в `transport_singbox_profiles_flow.go` - Step F1.11.25: [x] удалены локальные helper'ы `findSingBoxBinary`/`sanitizeHistoryStamp` в `transport_singbox_profiles_flow.go`; вызовы переведены на `transportcfg.FirstExistingBinaryCandidate` и `transportcfg.SanitizeHistoryStamp` - Step F1.11.26: [x] вынесены secrets-map helper'ы в `app/transportcfg/secrets_helpers.go` (`NormalizeSecretUpdates`, `Clone/Equal/MaskStringMap`, `Read/WriteStringMapJSON`), `transport_singbox_profiles.go` переведён на пакетные вызовы - Step F1.11.27: [x] очищен `transport_singbox_profiles_flow.go` от остаточных локальных wrapper'ов (config io/file optional/diff/normalize), вызовы переведены на `transportcfg` напрямую - Step F1.11.28: [x] вынесен state/normalization-блок `singbox profiles` в отдельный файл `app/transport_singbox_profiles_state.go` (`load/save/normalize/derive/index/active/select + cloneMapDeep`) без изменения API-контрактов - Step F1.11.29: [x] вынесены secrets/error helper'ы `singbox profiles` в `app/transport_singbox_profiles_secrets.go` и `app/transport_singbox_profiles_errors.go`; `transport_singbox_profiles.go` оставлен как handlers/mutate фасад - Step F1.11.30: [x] вынесен mutate-блок `singbox profiles` (`create/patch`) в `app/transport_singbox_profiles_mutate.go`; `transport_singbox_profiles.go` сфокусирован на HTTP handlers/route dispatch - Step F1.11.31: [x] вынесены runtime helper'ы `singbox profile flow` в `app/transport_singbox_profiles_runtime.go` (`find profile by client`, `prepare profile`, `resolve apply client`, `apply runtime`) - Step F1.11.32: [x] вынесены history helper'ы `singbox profile flow` в `app/transport_singbox_profiles_history.go` (`append/load/select/decode history`, `join issue messages`) - Step F1.11.33: [x] вынесен eval/render helper-блок `singbox profile flow` в `app/transport_singbox_profiles_eval.go` (`evaluate`, `binary validate`, `rendered path/config writer`, issue mapping) - Step F1.11.34: [x] вынесен state/normalization-блок `transport client runtime` в `app/transport_client_runtime_state.go` (`create builder`, `kind/capabilities normalize`, `runtime snapshot normalize`, `uptime/index/clone helpers`) без изменения API-контрактов - Step F1.11.35: [x] вынесен `transport client runtime` health/netns-блок в отдельные файлы `app/transport_client_runtime_health.go` и `app/transport_client_runtime_netns.go` (response snapshot + netns peer-stop guard) без изменения lifecycle/API-контрактов - Step F1.11.36: [x] выполнен разнос transport client handlers: netns toggle вынесен в `app/transport_handlers_netns.go`, lifecycle action-dispatch вынесен в `app/transport_handlers_actions.go`, `transport_handlers_clients.go` оставлен list/card routing-фасадом - Step F1.11.37: [x] вынесен helper-блок `routes update` в `app/routes_update_helpers.go` (table/list/io/counter/fs/readiness helpers) без изменения orchestration-пайплайна `routesUpdate` - Step F1.11.38: [x] вынесен state/storage/normalize блок `dns settings` в `app/dns_settings_state.go` (`upstream pool`, `mode state`, `smartdns helpers`, `dnscfg adapters`) без изменения API handlers - Step F1.11.39: [x] начата декомпозиция `types.go`: DNS/SmartDNS DTO вынесены в `app/types_dns.go` (без изменения JSON-контрактов/имён типов) - Step F1.11.40: [x] продолжена декомпозиция `types.go`: вынесены доменные DTO в `app/types_traffic.go`, `app/types_egress.go`, `app/types_transport.go`, `app/types_singbox.go`; `types.go` оставлен для shared/system/event/resolver моделей - Step F1.11.41: [x] выполнен разнос `transport policy handlers`: `validate/apply/rollback` вынесены в `app/transport_handlers_policy_mutations.go`; `transport_handlers_policy.go` оставлен для `list/conflicts/capabilities` - Step F1.11.42: [x] вынесен endpoint `POST /transport/health/refresh` в `app/transport_handlers_health_refresh.go`; `transport_health_refresh.go` оставлен как runtime S-W-R/probe logic - Step F1.11.43: [x] вынесен low-level probe/helper блок `egress identity` в `app/egress_identity_probe.go` (`probe via system/iface/netns/proxy`, endpoint helpers, singbox socks parse, iface bind address) без изменения API/SWR-контракта - Step F1.11.44: [x] выполнен разнос `routes handlers` service/timer блока в `app/routes_handlers_service.go`; `routes_handlers.go` оставлен для status/clear/fix/update endpoints - Step F1.11.45: [x] выполнен разнос `routes handlers` clear/precheck/op-lock блока в `app/routes_handlers_ops.go`; `routes_handlers.go` оставлен для status/policy-fix/update endpoints - Step F1.11.46: [x] вынесены `egress identity` HTTP handlers в `app/egress_identity_handlers.go`; `egress_identity.go` оставлен для service/SWR/state orchestration - Step F1.11.47: [x] выполнен разнос монолита `app/traffic_appmarks.go` на `traffic_appmarks_handlers.go`, `traffic_appmarks_ops.go`, `traffic_appmarks_runtime.go`; `traffic_appmarks.go` оставлен для core-констант/типов (без изменения API/runtime поведения) - Step F1.11.48: [x] вынесен helper-блок `routes cache` в `app/routes_cache_helpers.go` (table lines/io/restore-route/nft-snapshot/meta/file helpers); `routes_cache.go` оставлен orchestration-фасадом restore/save - Step F1.11.49: [x] выполнен разнос `egress identity` runtime-контура: provider-probe вынесен в `app/egress_identity_providers.go`, refresh/snapshot orchestration в `app/egress_identity_refresh.go`, geo cache/lookup в `app/egress_identity_geo.go`, scope/identity helpers в `app/egress_identity_scope.go`; `app/egress_identity.go` оставлен с константами/типами/инициализацией - Step F1.11.50: [x] выполнен разнос `vpn handlers` монолита на `app/vpn_handlers_auth.go` (login/logout/systemd), `app/vpn_handlers_status.go` (autoloop/status parse), `app/vpn_handlers_locations.go` (autoconnect/locations/set-location resolver); `app/vpn_handlers.go` оставлен как модульный фасад-комментарий - Step F1.11.51: [x] вынесены helper'ы `autoloop` в `app/autoloop_helpers.go` (`login-state write/parse`, `connected check`, `location resolve`, `ISO/cache helpers`), `app/autoloop.go` оставлен как loop orchestration без изменения runtime-поведения - Step F1.11.52: [x] выполнен разнос `transport systemd backend` на роль-файлы: `transport_backends_adapter_systemd_action.go`, `..._health.go`, `..._provision.go`, `..._cleanup.go`; базовый `transport_backends_adapter_systemd.go` оставлен для shared type aliases/render helpers - Step F1.11.53: [x] выполнен stage-разнос `routes_update.go`: preflight вынесен в `routes_update_stage_preflight.go`, policy routing в `routes_update_stage_policy.go`, nft setup/progress update в `routes_update_stage_nft.go`, domains+resolver merge в `routes_update_stage_resolve.go`, artifacts/status publish в `routes_update_stage_artifacts.go`; `routes_update.go` оставлен orchestration-фасадом - Step F1.11.54: [x] выполнен разнос `vpn_login_session.go`: HTTP endpoints и `loginStateAlreadyLogged` вынесены в `vpn_login_session_handlers.go`, базовый `vpn_login_session.go` оставлен для manager/PTY state-machine (без изменения API login session `/api/v1/vpn/login/session/*`) - Step F1.11.55: [x] выполнен разнос `vpn_locations_cache.go`: refresh/SWR orchestration вынесен в `vpn_locations_cache_refresh.go`, parser/location-target блок в `vpn_locations_cache_parse.go`, cache io/normalize/store в `vpn_locations_cache_store.go`; базовый файл оставлен с типами/константами/инициализацией - Step F1.11.56: [x] выполнен разнос `transport_health_refresh.go`: SWR/state mutex блок вынесен в `transport_health_refresh_state.go`, queue/candidate scheduling в `transport_health_refresh_queue.go`, probe runtime в `transport_health_refresh_probe.go`, persist/change-bucket helpers в `transport_health_refresh_compare.go`; базовый файл оставлен с константами/типами/конструктором - Step F1.11.57: [x] выполнен разнос `transport_client_runtime_alloc.go`: normalize/reconcile блок вынесен в `transport_client_runtime_alloc_normalize.go`, table/mark/pref slot helper'ы в `transport_client_runtime_alloc_slots.go`; базовый файл оставлен для `allocateTransportSlots` фасада - Step F1.11.58: [x] выполнен разнос `transport_netns.go`: spec/name/prefix/hash/existence вынесены в `transport_netns_spec.go`, nft/policy-route helpers в `transport_netns_rules.go`, command soft/must wrappers в `transport_netns_run.go`; базовый файл оставлен для `ensure/cleanup` orchestration - Step F1.11.59: [x] выполнен разнос `transport_bootstrap_bypass.go`: candidate/source extractors вынесены в `transport_bootstrap_bypass_candidates.go`, resolve/target/main-route в `transport_bootstrap_bypass_resolve.go`, route mutation в `transport_bootstrap_bypass_routes.go`, state io/normalize в `transport_bootstrap_bypass_state.go`; базовый файл оставлен для control-flow и shared error helpers - Step F1.11.60: [x] выполнен разнос `transport_singbox_profiles_flow.go`: HTTP handlers вынесены в role-файлы `transport_singbox_profiles_flow_{validate,render,apply,rollback,history}.go`; базовый `transport_singbox_profiles_flow.go` оставлен для shared моделей/dispatcher - Step F1.11.61: [x] выполнен разнос `transport_singbox_profiles.go`: list handlers вынесены в `transport_singbox_profiles_list.go`, by-id/card handlers в `transport_singbox_profiles_card.go`, features endpoint в `transport_singbox_profiles_features.go`; базовый файл оставлен для state/constants/shared mutex - Step F1.11.62: [x] выполнен разнос benchmark-контура `dns_settings.go`: heavy benchmark handlers/helper'ы вынесены в `dns_settings_benchmark.go`; `dns_settings.go` оставлен для DNS mode/upstreams/smartdns control handlers - Step F1.11.63: [x] выполнен разнос `resolver_bridge.go`: базовые alias/type/const вынесены в `resolver_bridge.go`, util wrappers в `resolver_bridge_utils.go`, domain-cache adapters в `resolver_bridge_cache.go`, dns-mode/lookup adapters в `resolver_bridge_dns.go`, planning/pipeline adapters в `resolver_bridge_pipeline.go` - Step F1.11.64: [x] выполнен разнос `transport_backends.go`: probe adapters (`dial/probe/endpoints/deps`) вынесены в `transport_backends_probe.go`, runtime-mode backend structs (`unsupported/mock`) в `transport_backends_runtime_modes.go`; базовый файл оставлен для backend selection/config facade - Step F1.11.65: [x] выполнен разнос `vpn_login_session.go`: session state/mutex methods вынесены в `vpn_login_session_state.go`, PTY lifecycle/parser в `vpn_login_session_pty.go`, базовый файл оставлен для DTO/models/manager struct; HTTP handlers остаются в `vpn_login_session_handlers.go` - Step F1.11.66: [x] выполнен разнос `types_transport.go`: модели разделены на `types_transport_core.go` (client/runtime/capabilities), `types_transport_policy.go` (policy/conflicts), `types_transport_runtime.go` (health/lifecycle/refresh/netns responses); поведение API/JSON-контракты не изменены - Step F1.11.67: [x] выполнен разнос `transport_singbox_dns_migration.go`: rule helper'ы вынесены в `transport_singbox_dns_migration_rules.go`, convert/parse блок в `transport_singbox_dns_migration_convert.go`, общий helper `asString/parsePort` вынесен в `helpers_values.go`; миграционный runtime/API-поток сохранён без изменений - Step F1.11.68: [x] выполнен разнос `vpn_handlers_locations.go`: selection/validation helper'ы вынесены в `vpn_locations_selection.go`, основной handler-файл оставлен для `autoconnect/list/set-location` endpoint-потока и egress refresh trigger - Step F1.11.69: [x] выполнен разнос `egress_identity_refresh.go`: runtime refresh/publish блок вынесен в `egress_identity_refresh_runner.go`, entry snapshot/state/sem helper'ы в `egress_identity_refresh_state.go`; базовый файл оставлен для queue/scope orchestration - Step F1.11.70: [x] выполнен разнос `dns_settings_state.go`: upstream/pool persistence вынесены в `dns_settings_state_upstreams.go`, mode load/save в `dns_settings_state_mode.go`, smartdns env/systemd/normalize helper'ы в `dns_settings_state_smartdns.go`; DNS API-контракты и runtime-поведение сохранены - Step F1.11.71: [x] выполнен разнос `traffic_audit.go`: проверки/парсинг nft-правил вынесены в `traffic_audit_checks.go`, pretty-render блока в `traffic_audit_render.go`, базовый файл оставлен как HTTP handler-orchestration - Step F1.11.72: [x] выполнен разнос `routes_update_helpers.go`: list/io helper'ы вынесены в `routes_update_helpers_lists.go`, smartdns wildcard trace в `routes_update_helpers_trace.go`, fs/readiness helper'ы в `routes_update_helpers_fs.go`; базовый файл оставлен для table identifiers (`routesTableName/routesTableNum`) - Step F1.11.73: [x] выполнена декомпозиция `types_traffic.go`: traffic mode/candidates DTO вынесены в `types_traffic_mode.go`, app-marks/app-profiles DTO вынесены в `types_traffic_apps.go`; JSON-контракты сохранены без изменений - Step F1.11.74: [x] выполнен разнос `transport_handlers_actions.go`: action-routing оставлен в `transport_handlers_actions.go`, heavy action execution блоки (`health/metrics/provision/start-stop-restart`) вынесены в `transport_handlers_actions_exec.go` без изменения transport lifecycle/API поведения - Step F1.11.75: [x] выполнен разнос `transport_handlers_policy_mutations.go`: thin wrappers оставлены в базовом файле, validate/apply/rollback execution вынесены в `transport_handlers_policy_mutations_{validate,apply,rollback}.go` без изменения policy-контрактов (`confirm token`, `snapshot/rollback`, `conflicts`) - Step F1.11.76: [x] выполнен разнос `resolver_pipeline.go`: `buildResolverJobContext` вынесен в `resolver_pipeline_context.go`, базовый `resolver_pipeline.go` оставлен для execution pipeline (`planning -> batch -> artifacts -> summary -> precheck finalize`) - Step F1.11.77: [x] выполнен разнос `resolver_dns_policy.go`: DNS attempt-policy helper'ы вынесены в `resolver_dns_attempt_policy.go`, cooldown runtime в `resolver_dns_cooldown.go`, precheck force flags в `resolver_precheck_flags.go`; базовый файл оставлен с типами policy/cooldown state - Step F1.11.78: [x] выполнен разнос `trace_handlers.go`: read endpoints вынесены в `trace_handlers_read.go`, append/write helper'ы в `trace_handlers_write.go`, bounded tail reader в `trace_tail.go`; базовый `trace_handlers.go` оставлен как module doc/header - Step F1.11.79: [x] выполнен разнос `transport_policy_validate.go`: normalization/CIDR helper'ы вынесены в `transport_policy_validate_normalize.go`, diff+conflict-dedupe блок в `transport_policy_validate_diff.go`; базовый файл оставлен для основной валидации ownership/overlap - Step F1.11.80: [x] выполнен разнос `watchers.go`: bootstrap/start блок вынесен в `watchers_start.go`, file/hash watchers в `watchers_state_files.go`, runtime/systemd watchers в `watchers_runtime.go`; базовый `watchers.go` оставлен как module doc/header - Step F1.11.81: [x] выполнен разнос `domains_handlers.go`: table endpoint вынесен в `domains_handlers_table.go`, file CRUD в `domains_handlers_file.go`, smartdns wildcard endpoints в `domains_handlers_smartdns.go`, observed-hosts helper в `domains_handlers_helpers.go`; базовый `domains_handlers.go` оставлен для shared mapping `domainFiles` - Step F1.11.82: [x] выполнен разнос `egress_identity_probe.go`: external probe блок вынесен в `egress_identity_probe_external.go`, netns/interface/proxy probe блок в `egress_identity_probe_netns.go`, endpoint/socks/bind-address helper'ы в `egress_identity_probe_helpers.go`; базовый файл оставлен как модульный entrypoint - Step F1.11.83: [x] выполнен разнос `traffic_appmarks_handlers.go`: POST mutation flow вынесен в `traffic_appmarks_handlers_post.go`, items list endpoint вынесен в `traffic_appmarks_handlers_items.go`, базовый handler оставлен для method routing + status response - Step F1.11.84: [x] выполнен разнос `transport_handlers_netns.go`: per-client apply/restart/provision + response finalize вынесены в `transport_handlers_netns_apply.go`, базовый файл оставлен для HTTP endpoint + target resolution/orchestration - Step F1.11.85: [x] выполнен разнос `transport_handlers_clients.go`: list/create endpoints вынесены в `transport_handlers_clients_list_create.go`, card get/patch/delete endpoints вынесены в `transport_handlers_clients_card_ops.go`, базовый handler оставлен для route-dispatch (`/transport/clients`, `/transport/clients/{id}`) без изменения API-контрактов - Step F1.11.86: [x] выполнен разнос `smartdns_runtime.go`: config detect/apply/normalize вынесены в `smartdns_runtime_config.go`, state load/save/infer вынесены в `smartdns_runtime_state.go`, базовый `smartdns_runtime.go` оставлен для shared runtime model/constants + status snapshot - Step F1.11.87: [x] выполнен разнос `traffic_mode_handlers.go`: POST apply-flow вынесен в `traffic_mode_handlers_apply.go`, advanced reset endpoint вынесен в `traffic_mode_handlers_advanced.go`, базовый `traffic_mode_handlers.go` оставлен для GET/interface/test endpoint-роутинга и lock helper'а - Step F1.11.88: [x] выполнен разнос `routes_handlers.go`: status endpoint вынесен в `routes_handlers_status.go`, policy-fix endpoint вынесен в `routes_handlers_policy_fix.go`, async update endpoint вынесен в `routes_handlers_update.go`; базовый `routes_handlers.go` оставлен для service command wrapper `makeCmdHandler` - Step F1.11.89: [x] выполнен разнос `dns_smartdns_handlers.go`: runtime endpoint (`GET/POST /dns/smartdns/runtime`) вынесен в `dns_smartdns_handlers_runtime.go`, prewarm endpoint + helper'ы вынесены в `dns_smartdns_handlers_prewarm.go`, базовый файл оставлен как module header - Step F1.11.90: [x] выполнен разнос `traffic_appmarks_runtime.go`: state/prune helper'ы вынесены в `traffic_appmarks_runtime_state.go`, restore sequence вынесен в `traffic_appmarks_runtime_restore.go`, базовый runtime-файл оставлен для NFT adapters/config helper'ов - Step F1.11.91: [x] выполнен разнос `dns_settings.go`: upstream endpoints вынесены в `dns_settings_handlers_upstreams.go`, mode/status handlers вынесены в `dns_settings_handlers_mode.go`, smartdns service handlers вынесены в `dns_settings_handlers_smartdns_service.go`; базовый файл оставлен как module header - Step F1.11.92: [x] выполнен разнос `autoloop_helpers.go`: login-state/parser helper'ы вынесены в `autoloop_helpers_login.go`, location spec/ISO resolve helper'ы вынесены в `autoloop_helpers_location.go`, базовый файл оставлен для shared `autoloopLocationSpec` и email regexp - Step F1.11.93: [x] выполнен разнос `traffic_app_profiles.go`: store/conversion/sanitize helper'ы вынесены в `traffic_app_profiles_store.go`, базовый файл оставлен для HTTP handler `/traffic/app-profiles` и package-level store init - Step F1.11.94: [x] выполнен разнос `routes_handlers_ops.go`: core lock/clear/status snapshot логика вынесена в `routes_handlers_ops_core.go`, базовый `routes_handlers_ops.go` оставлен для rollback/cache-restore/precheck-debug endpoint-слоя - Step F1.11.95: [x] выполнен разнос `transport_singbox_profiles_state.go`: state load/save вынесены в `transport_singbox_profiles_state_store.go`, profile/state normalize + active-id/derive-id helper'ы вынесены в `transport_singbox_profiles_state_normalize.go`, базовый файл оставлен для shared primitive helper'ов (`mode/schema/int64/cloneMap`) - Step F1.11.96: [x] выполнен разнос `transport_singbox_profiles_runtime.go`: client/profile selection helper'ы вынесены в `transport_singbox_profiles_runtime_select.go`, preflight render/write flow вынесен в `transport_singbox_profiles_runtime_prepare.go`, backend provision/start/restart runtime flow вынесен в `transport_singbox_profiles_runtime_apply.go`; базовый файл оставлен как module header - Step F1.11.97: [x] выполнен разнос `transport_handlers_actions_exec.go`: health/metrics handlers вынесены в `transport_handlers_actions_exec_health.go`, provision handler вынесен в `transport_handlers_actions_exec_provision.go`, lifecycle handler (`start/stop/restart`) вынесен в `transport_handlers_actions_exec_lifecycle.go`; базовый файл оставлен как module header - Step F1.11.98: [x] выполнен разнос `routes_update_stage_resolve.go`: доменное расширение/каппинг/wildcard trace вынесены в `routes_update_stage_resolve_domains.go`, bootstrap временных файлов вынесен в `routes_update_stage_resolve_tempfiles.go`; базовый stage-файл оставлен для resolver orchestration + runtime wildcard merge + artifact writes - Step F1.11.99: [x] выполнен разнос `transport_backends.go`: transportcfg wrapper/helper слой вынесен в `transport_backends_config_helpers.go`, unsupported runtime backend methods вынесены в `transport_backends_runtime_unsupported.go`, базовый файл оставлен для core backend types/constants + backend selection - Step F1.11.100: [x] выполнен разнос `transport_singbox_dns_migration.go`: migration entry/settings/path helper'ы оставлены в базовом файле, apply flow вынесен в `transport_singbox_dns_migration_apply.go`, DNS map conversion вынесен в `transport_singbox_dns_migration_map.go` - Step F1.11.101: [x] выполнен разнос `traffic_appmarks_ops.go`: status summary helper оставлен в базовом файле, mutation-операции (`add/delete/clear`) вынесены в `traffic_appmarks_ops_mutations.go` - Step F1.11.102: [x] выполнен разнос `routes_cache_helpers.go`: file io/helper'ы вынесены в `routes_cache_helpers_files.go`, route parse/restore в `routes_cache_helpers_routes.go`, nft snapshot/parser в `routes_cache_helpers_nft.go`, meta helpers в `routes_cache_helpers_meta.go`; базовый файл оставлен как module header - Step F1.11.103: [x] выполнен разнос `transport_client_runtime_alloc_normalize.go`: reconcile marks/prefs allocation блок вынесен в `transport_client_runtime_alloc_reconcile.go`, базовый normalize-файл оставлен для state canonicalization и deterministic client merge - Step F1.11.104: [x] выполнен разнос `dns_settings_benchmark.go`: HTTP benchmark endpoint вынесен в `dns_settings_benchmark_handler.go`, базовый файл оставлен для benchmark normalize/run/topN helper'ов и package aliases - Step F1.11.105: [x] выполнен разнос `transport_singbox_profiles_card.go`: route dispatch + card method switch оставлены в базовом файле, `GET` вынесен в `transport_singbox_profiles_card_get.go`, `PATCH` в `transport_singbox_profiles_card_patch.go`, `DELETE` в `transport_singbox_profiles_card_delete.go` - Step F1.11.106: [x] выполнен разнос `types_singbox.go`: профильные DTO вынесены в `types_singbox_profiles.go`, validate/render/apply/rollback DTO вынесены в `types_singbox_flow.go`, history DTO вынесены в `types_singbox_history.go`; базовый файл оставлен как module header - Step F1.11.107: [x] выполнен разнос `routes_cache.go`: save snapshot flow вынесен в `routes_cache_save.go`, restore flow вынесен в `routes_cache_restore.go`, базовый файл оставлен для shared cache meta type - Step F1.11.108: [x] выполнен разнос `transport_tokens_state.go`: clients state load/save вынесены в `transport_tokens_state_clients.go`, policy state/snapshot load/save вынесены в `transport_tokens_state_policy.go`, conflicts state load/save вынесены в `transport_tokens_state_conflicts.go`; базовый файл оставлен для token/digest helper'ов - Step F1.11.109: [x] выполнен разнос `routes_handlers_service.go`: routes service action handler/helper'ы вынесены в `routes_handlers_service_action.go`, timer handlers/helper'ы вынесены в `routes_handlers_service_timer.go`, базовый файл оставлен как module header - Step F1.11.110: [x] выполнен разнос `vpn_login_session_handlers.go`: login state helper вынесен в `vpn_login_session_handlers_state_helper.go`, start/state/action/stop endpoints вынесены в `vpn_login_session_handlers_{start,state,action,stop}.go`, базовый файл оставлен как module header - Step F1.11.111: [x] выполнен разнос `resolver_bridge_pipeline.go`: planning/runtime tuning wrappers вынесены в `resolver_bridge_pipeline_planning.go`, execution/recheck/artifacts wrappers вынесены в `resolver_bridge_pipeline_exec.go`, базовый файл оставлен как module header - Step F1.11.112: [x] выполнен разнос `transport_singbox_profiles_flow_rollback.go`: HTTP wrapper + decode body вынесены в базовый файл, rollback execution flow вынесен в `transport_singbox_profiles_flow_rollback_exec.go` без изменения кодов/контрактов endpoint `POST /api/v1/transport/singbox/profiles/{id}/rollback` - Step F1.11.113: [x] выполнен stage-разнос `transport_singbox_profiles_flow_apply_exec.go`: preflight/validate/render и dispatch оставлены в базовом execution-файле, target-config/runtime/commit вынесены в `transport_singbox_profiles_flow_apply_exec_rendered.go` без изменения API-контрактов `POST /api/v1/transport/singbox/profiles/{id}/apply` - Step F1.11.114: [x] выполнен stage-разнос `transport_backends_adapter_systemd_action.go`: lifecycle action orchestration оставлена в базовом файле, pre/post hooks вынесены в `transport_backends_adapter_systemd_action_hooks.go`, unit execution + auto-provision path вынесены в `transport_backends_adapter_systemd_action_exec.go` (без изменения backend/runtime контрактов) - Step F1.11.115: [x] выполнен stage-разнос `transport_singbox_profiles_flow_rollback_exec.go`: prepare/select/restore шаг оставлен в базовом execution-файле, runtime+commit/history шаг вынесен в `transport_singbox_profiles_flow_rollback_exec_restored.go` без изменения API-контракта rollback endpoint - Step F1.11.116: [x] выполнен разнос `transport_singbox_profiles_flow_render.go`: HTTP wrapper + body decode оставлены в базовом файле, render execution flow вынесен в `transport_singbox_profiles_flow_render_exec.go` без изменения API-контракта endpoint `POST /api/v1/transport/singbox/profiles/{id}/render` - Step F1.11.117: [x] выполнен stage-разнос `routes_update_stage_resolve.go`: wildcard runtime merge вынесен в `routes_update_stage_resolve_wildcard_runtime.go`, запись resolver artifacts вынесена в `routes_update_stage_resolve_artifacts.go`, базовый stage-файл оставлен orchestration-слоем (без изменения routes-update поведения) - Step F1.11.118: [x] выполнен stage-разнос `transport_backends_adapter_systemd_provision.go`: input/preflight build вынесен в `transport_backends_adapter_systemd_provision_inputs.go`, unit write + daemon-reload/enable finalize вынесены в `transport_backends_adapter_systemd_provision_finalize.go`, базовый `Provision()` оставлен orchestration-слоем без изменения runtime-контрактов - Step F1.11.119: [x] выполнен разнос `transport_client_runtime_state.go`: client create/kind/capabilities вынесены в `transport_client_runtime_create.go`, shared helpers (`find index`, `cloneMap`) вынесены в `transport_client_runtime_helpers.go`, базовый state-файл оставлен для runtime snapshot/normalize/uptime helper'ов - Step F1.11.120: [x] выполнен разнос `traffic_appmarks_ops_mutations.go`: mutation flow разделён на role-файлы `traffic_appmarks_ops_mutations_add.go`, `traffic_appmarks_ops_mutations_delete.go`, `traffic_appmarks_ops_mutations_clear.go`; базовый файл оставлен как module header без изменения appmarks API/runtime поведения - Step F1.11.121: [x] выполнен stage-разнос `transport_handlers_actions_exec_lifecycle.go`: HTTP lifecycle handler оставлен в базовом файле, preflight flow вынесен в `transport_handlers_actions_exec_lifecycle_preflight.go`, locked execution/save/response flow вынесен в `transport_handlers_actions_exec_lifecycle_locked.go` без изменения lifecycle API-контракта - Step F1.11.122: [x] выполнен stage-разнос `dns_settings_state_upstreams.go`: pool store/load/save вынесены в `dns_settings_state_upstreams_pool_store.go`, conf file load/save вынесены в `dns_settings_state_upstreams_conf_store.go`, базовый upstream-state файл оставлен для conversion/normalize/load-enabled и legacy facade методов - Step F1.11.123: [x] выполнен stage-разнос `traffic_appmarks_handlers_post.go`: request decode/validate вынесен в `traffic_appmarks_handlers_post_parse.go`, op-specific execution вынесен в `traffic_appmarks_handlers_post_ops.go`, базовый POST handler оставлен как thin dispatch без изменения API-контракта - Step F1.11.124: [x] выполнен stage-разнос `traffic_audit_checks.go`: duplicate-detector helper'ы вынесены в `traffic_audit_duplicates.go`, NFT appmark rule parse helper'ы вынесены в `traffic_audit_nft_parse.go`, базовый checks-файл оставлен для audit-orchestration (`auditNftAppMarks`) без изменения audit API/вывода - Step F1.11.125: [x] зафиксирован stop-condition декомпозиции Go-ядра: max non-test файл в `selective-vpn-api/app` = `195` строк; дальнейший разнос отложен как low-priority polish - Step F1.12: [x] выполнить пакетную декомпозицию `selective-vpn-gui/api_client.py` в `selective-vpn-gui/api/*` с сохранением legacy facade `api_client.py` - Step F1.13: [~] выполнить декомпозицию `selective-vpn-api/app/traffic_mode.go` на подпакеты `app/trafficmode/*` без изменения policy-routing поведения - Step F1.13.1: [x] вынесены normalize helper'ы (`tokenize/subnet/uid/cgroup`) в `app/trafficmode/normalize.go`, в `app/traffic_mode.go` оставлены фасады - Step F1.13.2: [x] вынесен parser auto-local-bypass маршрутов в `app/trafficmode/autolocal.go` (`ParseAutoBypassRoutes` + helper'ы), в `app/traffic_mode.go` сохранены совместимые фасады для `traffic_candidates` - Step F1.13.3: [x] вынесен cgroup->uid resolution блок в `app/trafficmode/cgroup.go` (`CgroupCandidates/ResolveCgroupPath/CollectPIDsFromCgroup/UIDRangeForPID/ResolveCgroupUIDRanges`), в `app/traffic_mode.go` сохранены фасады - Step F1.13.4: [x] вынесен ingress-reply bypass nft-контур в `app/trafficmode/ingress.go` (`Ensure/Flush/Enable/Disable/Active`) с сохранением фасадов в `app/traffic_mode.go` - Step F1.13.5: [x] вынесен route-rules/probe блок в `app/trafficmode/rules.go` (`ReadRules/DetectAppliedMode/ProbeMode/PrefStr`) с сохранением фасадов и `trafficRulesState` alias в `app/traffic_mode.go` - Step F1.13.6: [x] удалены лишние внутренние wrapper-функции `traffic_mode.go` (cgroup/nftObjectMissing), `buildEffectiveOverrides` переведён на прямые вызовы `trafficmodepkg.ResolveCgroupUIDRanges` - Step F1.13.7: [x] вынесен `ip rule` apply/remove блок в `app/trafficmode/apply.go` (`RemoveRulesForTable`, `ApplyRule`, `ApplyOverrides`) с сохранением фасадов в `app/traffic_mode.go` - Step F1.13.8: [x] вынесен interface selection/resolve блок в `app/trafficmode/interfaces.go` (`NormalizePreferredIface`, `IfaceExists`, `ListUpIfaces`, `ListSelectableIfaces`, `ResolveTrafficIface`, `IsVPNLikeIface`), `app/traffic_mode.go` переведён на thin-facade - Step F1.13.9: [x] вынесены HTTP/lock handlers traffic-mode в `app/traffic_mode_handlers.go` (`mode get/post`, `advanced reset`, `interfaces`, `mode test`, apply-lock); `app/traffic_mode.go` сокращён до core policy/apply/evaluate блока - Step F1.13.10: [x] вынесены state/normalization/persistence helper'ы traffic-mode в `app/traffic_mode_state.go` (`normalize*`, `load/save/infer state`), `app/traffic_mode.go` оставлен для routing apply/evaluate orchestration - Step F1.13.11: [x] завершена декомпозиция `traffic_mode` core: вынесены `iface/config` helper'ы в `app/traffic_mode_iface.go`, apply-orchestration в `app/traffic_mode_apply.go`, runtime-check/evaluate в `app/traffic_mode_evaluate.go`; `app/traffic_mode.go` оставлен с константами/описанием модуля - Step F3: [ ] (deferred) выделить собственную библиотеку модулей Go-ядра (linux-first), чтобы backend собирался из переиспользуемых пакетов - Step F3.1: [ ] вынести `netns` как отдельный debug-модуль библиотеки (`pkg/netnsdebug` / `pkg/netns`), зафиксировать Linux-only scope - Step F3.2: [ ] зафиксировать, что `netns` модуль не является целью для mobile SDK (iOS/Android out-of-scope), используется для backend/desktop test-contour - Step F3.2.1: [ ] не реализовывать `netns` интеграцию для mobile-клиентов; для iOS/Android использовать только API control-plane без system/netns модулей - Step F3.3: [ ] выделить стабильные библиотечные пакеты ядра (`pkg/orchestrator`, `pkg/transport`, `pkg/pbr`, `pkg/resolver`) с явными интерфейсами зависимостей - Step F3.4: [ ] перевести backend на использование этих пакетов через thin-facade слой `app/*` без изменения внешнего API-контракта - Step F3.5: [ ] подготовить отдельный документ архитектуры библиотеки модулей и runbook миграции (`docs/phase-f/F3_CORE_MODULE_LIBRARY_PLAN.md`) - Step F1.14: [~] выполнить декомпозицию `selective-vpn-api/app/egress_identity.go` на подпакеты `app/egress*` без изменения egress API/SWR-поведения - Step F1.14.1: [x] вынесены egress utility helper'ы в `app/egressutil/util.go` (parse ip/geo, endpoint lists, curl/wget path, socks url parse, URL host resolve, generic any/string helpers) - Step F1.14.2: [x] удалены лишние локальные egress helper-функции в `app/egress_identity.go` (dead wrappers), оставлены только необходимые facade entrypoints - Step F1.14.3: [x] вынесены helper'ы scope/identity/probe-loop в `app/egressutil` (`ParseScope`, `IdentityChanged`, `ProbeFirstSuccess`) с сохранением app-facade функций - Step F1.14.4: [x] вынесен HTTP fetch helper в `app/egressutil/http.go` (`HTTPGetBody`), egress probe/geo вызовы в `app/egress_identity.go` переведены на пакетный вызов - Step F1.14.5: [x] удалены чистые egress wrapper-функции в `app/egress_identity.go` (parse geo/ip, country normalize, curl/wget/timeout/resolve helpers); app-тесты переведены на прямые вызовы `app/egressutil` - Step F1.15: [~] выполнить декомпозицию `selective-vpn-api/app/dns_settings.go` на подпакеты `app/dnscfg/*` без изменения DNS API/SmartDNS поведения - Step F1.15.1: [x] вынесены SmartDNS addr/upstream normalizers в `app/dnscfg/smartdns.go` (`ResolveDefaultSmartDNSAddr`, `SmartDNSAddrFromConfig`, `NormalizeDNSUpstream`, `NormalizeSmartDNSAddr`) с сохранением фасадов в `app/dns_settings.go` - Step F1.15.2: [x] вынесен SmartDNS systemd helper-блок в `app/dnscfg/systemd.go` (`UnitState`, `RunUnitAction`) с сохранением фасада `runSmartdnsUnitAction` и прежнего success-message формата - Step F1.15.3: [x] вынесен DNS benchmark engine в `app/dnscfg/benchmark.go` (`NormalizeProfile/Options/Upstreams/Domains`, `BenchmarkDNSUpstream`, `DNSLookupAOnce`, `BenchmarkTopN`) с сохранением HTTP/API-контракта в `app/dns_settings.go` - Step F1.15.4: [x] вынесены pool helper'ы в `app/dnscfg/pool.go` (`NormalizeUpstreamPoolItems`, `UpstreamPoolFromLegacy`, `UpstreamPoolToLegacy`, `EnabledPool`) с сохранением фасадов в `app/dns_settings.go` - Step F1.15.5: [x] вынесен mode-state storage в `app/dnscfg/mode.go` (`ModeConfig`, `ModeState`, `LoadMode`, `SaveMode`) с сохранением JSON-контракта `dns_mode.json` - Step F1.15.6: [x] очищен `app/dns_settings.go` от лишних benchmark-wrapper'ов (прямые вызовы `dnscfg`), без изменения endpoint/DTO - Step F1.15.7: [x] вынесены parse/normalize/render helper'ы `dns-upstreams.conf` в `app/dnscfg/upstreams.go` (`ParseUpstreamsConf`, `NormalizeUpstreams`, `RenderUpstreamsConf`) с сохранением файлового/JSON mirror поведения - Step F1.15.8: [x] вынесен SmartDNS prewarm engine в `app/dnscfg/prewarm.go` (`RunPrewarm` + metrics/domain expansion/manual merge/log pipeline) через dependency-injection callbacks - Step F1.15.9: [x] runtime/prewarm HTTP handlers вынесены из `app/dns_settings.go` в `app/dns_smartdns_handlers.go` (в `dns_settings.go` оставлен DNS settings facade слой) - Step F2.1: [x] выполнен production-upgrade `SingBox` runtime на шаблонный `systemd` unit `singbox@.service` (instance-per-profile без генерации отдельного unit-файла на каждый профиль) - Step F2.2: [x] после `F2.1` внедрён миграционный и cleanup-контур (`old per-profile units -> template instances`, idempotent remove/disable/reset-failed, сохранение совместимости API) - Step F2.1.1: [x] добавлен template unit `singbox@.service` + drop-in env strategy (`SVPN_TRANSPORT_ID`, `SVPN_CONFIG_PATH`, `SVPN_NETNS_*`) и зафиксированы hardening/tuning defaults - Step F2.1.2: [x] backend renderer/provision переведён на instance model (`singbox@.service`) без изменения API-контракта `transport/clients/*` - Step F2.1.3: [x] lifecycle/health/metrics path переведён на instance model (`start/stop/restart/is-active/reset-failed`) с сохранением runtime-code/ошибок - Step F2.1.4: [x] добавлены e2e/smoke проверки instance model (`create/start/switch/stop/delete`) включая netns-enabled профили и auto-provision fallback - Step F2.2.1: [x] реализован one-shot migrator `legacy unit -> template instance` (deterministic mapping + marker/ownership check + dry-run) - Step F2.2.2: [x] реализован cleanup legacy artifacts (`disable/stop/reset-failed/remove unit file + daemon-reload`) с idempotent повторным запуском - Step F2.2.3: [x] добавлен rollback-план миграции (`template -> legacy`) для инцидентов деплоя и зафиксирован runbook - Step F2.2.4: [x] обновлены docs/scripts деплоя и preflight-check для новой модели (`singbox@.service` обязательный runtime dependency) - Step F1.3: [x] выполнить декомпозицию `selective-vpn-gui/dashboard_controller.py` на domain-контроллеры с сохранением фасада `DashboardController` - Step F1.4: [x] выполнить декомпозицию `selective-vpn-gui/vpn_dashboard_qt.py` на `main_window/*` mixin-модули (без изменения поведения) - Step Z1: [ ] (experimental tail) глобальный L7 orchestration-layer поверх текущего ядра для multi-engine (`AdGuardVPN` / `SingBox` / future transports) с единым policy-adapter слоем - Step Z1.1: [~] ранняя подготовка разрешена до старта Z1 runtime: единая policy-модель/intent ownership, scope-нормализация, telemetry schema и compatibility matrix; без включения L7 data-plane в прод-путь ## Что уже сделано - 2026-03-20: закрыт F2.2.1 (one-shot migrator legacy unit -> template instance) для SingBox systemd path: - в pre-action hook (start/restart) добавлен best-effort мигратор legacy unit singbox-.service -> template instance singbox@.service; - миграция использует deterministic candidate mapping (instance-id/unit + client-id sanitize), ownership-check по marker SVPN_TRANSPORT_ID и не трогает foreign unit-файлы; - добавлен dry-run режим через config.singbox_legacy_unit_migrate_dry_run=true (без stop/disable/remove), плюс флаг отключения config.singbox_legacy_unit_migrate=false; - после фактической миграции выполняются daemon-reload + reset-failed для legacy unit (best-effort, idempotent). - cleanup-path Cleanup() для template-instance расширен: удаляются и owned legacy unit-файлы singbox-.service (stop/disable/remove + daemon-reload/reset-failed), template unit singbox@.service сохраняется. - добавлены тесты transport_systemd_singbox_template_test.go: migrate owned legacy, dry-run, ownership mismatch; локально go test ./... (ok). - 2026-03-20: закрыт E3.3 (interface orchestrator + M3 owner-scope): - compile-plan перешёл на owner-scoped nft naming: `owner_scope=iface+client`, `nft_set=agvpn_pi__`; - для длинных `iface/client` добавлен deterministic hash fallback, длина set name ограничена 63 символами; - `TransportPolicyCompileRule/Set` расширены полем `owner_scope` для observability/runtime debug; - добавлены тесты: `TestCompileTransportPolicyPlanUsesOwnerScopedNftSets`, `TestTransportPolicyNftSetNameDeterministicAndBounded`; локально `go test ./...` (ok). - 2026-03-20: закрыт E3.5 domain destination-lock coverage: - `detectTransportDestinationLockConflicts` расширен на `domain` selector (включая `*.domain`), теперь owner-switch проверяет sticky-lock по `domain-cache` (`direct+wildcard`) без сетевых запросов; - добавлен `domain-cache` bridge hook в policy guard (`transportPolicyLoadDomainCacheState`, path: `/var/lib/selective-vpn/domain-cache.json`), с lazy-load и per-selector memoization; - добавлены тесты: `TestDetectTransportDestinationLockConflictsDomainFromCache`, `TestDetectTransportDestinationLockConflictsWildcardDomainFromCache`, `TestDetectTransportDestinationLockConflictsSkipsDomainWithoutCacheHit`; локально `go test ./...` (ok). - 2026-03-13: уменьшен trace-spam в transport-контуре: - добавлен rate-limited trace helper (`appendTraceLineRateLimited`) с дедупом повторяющихся строк в окне времени; - на throttled-логирование переведены шумные ветки (`singbox dns migrate warning`, `bootstrap bypass failed`, `netns setup/cleanup failed`, `systemctl unit missing/reset-failed`); - повторяющиеся сообщения теперь схлопываются с компактной `trace dedup: suppressed=...` строкой. - 2026-03-13: стартовал `F2.1` (SingBox instance-model foundation): - в `transportcfg.BackendUnit` для `kind=singbox` добавлен резолв unit в `singbox@.service`; - в прод-коде отключён auto-map legacy unit-имён `sing-box.service` / `singbox-*.service`; - для `kind=singbox` включена жёсткая нормализация `config.unit=singbox@.service` на create/patch/state-save; - добавлены/обновлены тесты `app/transportcfg/runtime_helpers_test.go` (default/template/custom/sanitize). - 2026-03-13: продолжен `F2.1` (template/drop-in provisioning для SingBox): - `systemd` provision-path для `singbox@.service` переведён на модель `template + per-instance drop-in`; - добавлена генерация/обновление `singbox@.service` (managed template) и drop-in `singbox@.service.d/10-selective-vpn.conf`; - в drop-in пробрасываются env-поля `SVPN_TRANSPORT_ID`, `SVPN_TRANSPORT_KIND`, `SVPN_CONFIG_PATH`, `SVPN_NETNS_*`; - cleanup для template-instance теперь удаляет клиентский drop-in (с ownership-check) и не удаляет общий template unit; - добавлены e2e-like unit tests: `transport_systemd_singbox_template_test.go` (provision, auto-provision-on-missing, cleanup-keep-template); - обновлены runtime dependency docs/script: добавлен check `singbox@.service` (legacy `sing-box.service` сохранён как compat). - 2026-03-13: завершён cleanup legacy unit-контур для SingBox: - state `/var/lib/selective-vpn/transport-clients.json` мигрирован на `config.unit=singbox@.service` для всех `kind=singbox` профилей; - legacy unit-файлы `singbox-*.service` удалены из `/etc/systemd/system`, оставлен только `singbox@.service` + instance drop-in; - после сборки/рестарта API прогнаны проверки: `go test ./...` (ok), `systemctl list-unit-files singbox*` (только template), runtime start через `singbox@.service` (ok). - 2026-03-13: расширены smoke/e2e проверки под instance-model: - `tests/transport_systemd_real_e2e.py` обновлён: SingBox проверяется через default instance unit (`singbox@.service`) + ownership в drop-in + сохранение `singbox@.service` после delete; - `tests/transport_production_like_e2e.py` обновлён: production-like SingBox-кейс переведён на instance model (проверка template/drop-in артефактов и cleanup); - добавлен unit-test на netns-instance drop-in env (`SVPN_NETNS_ENABLED`, `SVPN_NETNS_NAME`) в `transport_systemd_singbox_template_test.go`; - прогон локальных проверок: `go test ./...` (ok), `python3 -m py_compile` для обновлённых e2e-скриптов (ok). - 2026-03-12: продолжен рефакторинг `F1.11/F1.13` без изменения API-контрактов: - в `app/transportcfg` добавлен `secrets_helpers.go` (нормализация/маскирование/сравнение secret-map + файловые JSON helper'ы); - `app/transport_singbox_profiles.go` переведён на `transportcfg` secrets-helper'ы; удалены локальные дубли `normalizeSingBoxSecretUpdates`, `maskSingBoxSecrets`, `cloneStringMap`, `equalStringMap`; - `app/transport_singbox_profiles_flow.go` дополнительно очищен от локальных wrapper-функций `config io/file optional/diff`; вызовы переведены на прямой `transportcfg`; - state/normalization функции `singbox profiles` вынесены из `app/transport_singbox_profiles.go` в `app/transport_singbox_profiles_state.go` (декомпозиция без изменения DTO/handlers); - функции secrets patch/store + error helpers `singbox profiles` вынесены в отдельные файлы (`transport_singbox_profiles_secrets.go`, `transport_singbox_profiles_errors.go`); - create/patch mutate-логика `singbox profiles` вынесена в `transport_singbox_profiles_mutate.go`, а основной `transport_singbox_profiles.go` сокращён до `413` строк; - runtime/history helper'ы `singbox profile flow` вынесены в `transport_singbox_profiles_runtime.go` и `transport_singbox_profiles_history.go`; - eval/render helper'ы `singbox profile flow` вынесены в `transport_singbox_profiles_eval.go`; - state/normalization helper'ы `transport client runtime` вынесены в `transport_client_runtime_state.go`, а `transport_client_runtime.go` сокращён до orchestration/lifecycle части; - health/netns helper'ы `transport client runtime` вынесены в `transport_client_runtime_health.go` и `transport_client_runtime_netns.go`; - `transport_handlers_clients.go` декомпозирован: netns-toggle и action-dispatch вынесены в отдельные файлы `transport_handlers_netns.go`/`transport_handlers_actions.go` без изменения endpoint-контрактов; - helper-блок `routes_update.go` вынесен в `routes_update_helpers.go` (table/list/io/counter/fs/readiness), основной `routes_update.go` оставлен как orchestration-пайплайн; - helper/state блок `dns_settings.go` вынесен в `dns_settings_state.go` (upstream pool + mode/smartdns helpers + dnscfg adapters), handler-файл оставлен как control-plane API фасад; - DNS/SmartDNS типы вынесены из `types.go` в `types_dns.go` (контракты API/JSON не менялись); - `types.go` дополнительно разрезан на доменные файлы (`types_traffic.go`, `types_egress.go`, `types_transport.go`, `types_singbox.go`) с сохранением совместимости по полям/тегам; - `transport_handlers_policy.go` декомпозирован: мутационные endpoints (`validate/apply/rollback`) вынесены в `transport_handlers_policy_mutations.go`; - `handleTransportHealthRefresh` вынесен в `transport_handlers_health_refresh.go`, а `transport_health_refresh.go` оставлен runtime-блоком фоновых health probe; - из `egress_identity.go` вынесен probe/helper блок в `egress_identity_probe.go` (system/iface/netns/proxy probes + endpoint helpers + socks/iface bind helpers); - из `routes_handlers.go` вынесен service/timer блок в `routes_handlers_service.go` (systemd service action, timer get/toggle/set); - из `routes_handlers.go` вынесен clear/precheck/op-lock блок в `routes_handlers_ops.go` (clear/cache-restore/precheck-debug/status-snapshot/op-lock helpers); - `egress_identity` endpoint handlers (`GET/refresh`) вынесены в `egress_identity_handlers.go`, основной файл оставлен как service/runtime orchestration; - монолит `traffic_appmarks.go` разнесён по ролям на `traffic_appmarks_handlers.go` (HTTP), `traffic_appmarks_ops.go` (add/del/clear/status) и `traffic_appmarks_runtime.go` (nft/cgroup/state/restore), базовый файл оставлен с core-константами и alias-типами; - `transport_singbox_profiles_flow.go` сокращён с `1336` до `893` строк без изменения API-контрактов; - в `app/trafficmode` добавлен `interfaces.go` (iface detect/select/resolve), а `app/traffic_mode.go` переведён на thin-facade для этого блока; - handlers/apply-lock блок `traffic mode` вынесен в `app/traffic_mode_handlers.go` без изменения endpoint-контрактов (`/api/v1/traffic/mode`, `/traffic/interfaces`, `/traffic/mode/test`, `/traffic/advanced/reset`); - state/normalization/persistence блок `traffic mode` вынесен в `app/traffic_mode_state.go`, основной `traffic_mode.go` очищен до apply/evaluate orchestration; - завершён разнос remaining-core `traffic mode`: `traffic_mode_iface.go` (iface/table/config helpers), `traffic_mode_apply.go` (build/apply overrides + route base + apply mode), `traffic_mode_evaluate.go` (rules read/probe/evaluate health); - handlers/ops/runtime блок `traffic app marks` разнесён на отдельные файлы (`traffic_appmarks_handlers.go`, `traffic_appmarks_ops.go`, `traffic_appmarks_runtime.go`), core-файл `traffic_appmarks.go` сокращён до констант/типов (`34` строки); - размеры крупных файлов дополнительно снижены: `traffic_mode.go` `820 -> 35`; - helper-блок `routes cache` вынесен в `routes_cache_helpers.go`, основной `routes_cache.go` сокращён до orchestration restore/save пайплайна; - `egress identity` разнесён на файлы ролей: `providers`, `refresh`, `geo`, `scope`; базовый `egress_identity.go` сокращён до констант/типов/bootstrap; - `vpn_handlers.go` (`531` строка) разложен на отдельные роли (`auth/status/locations`) с сохранением endpoint-path и прежнего поведения location switch / autoconnect; - `autoloop.go` очищен от внутренних helper closure-блоков; helper-логика вынесена в `autoloop_helpers.go`, loop orchestration оставлен неизменным по таймингам и reconnect-пайплайну; - `transport_backends_adapter_systemd.go` разложен на role-файлы (`action/health/provision/cleanup`) без изменения lifecycle/runtime контрактов backend `systemd`; - выполнен stage-разнос `routes_update.go`: preflight/policy/nft/resolve/artifacts вынесены в `routes_update_stage_*.go`, основной файл оставлен orchestration-фасадом; - `vpn_login_session.go` разложен на manager/PTY и HTTP handlers: endpoint-обработчики вынесены в `vpn_login_session_handlers.go` без изменения API login session; - `vpn_locations_cache.go` разложен на role-файлы (`refresh`, `parse`, `store`) без изменения SWR-контракта (`stale/refresh_in_progress/next_retry_at`) и endpoint поведения; - `transport_health_refresh.go` разложен на role-файлы (`state`, `queue`, `probe`, `compare`) без изменения фонового health-refresh контракта (`fresh TTL`, `backoff`, `max parallel probes`, `persist min age`); - `transport_client_runtime_alloc.go` разложен на role-файлы (`normalize/reconcile`, `table/mark/pref slots`) без изменения allocation-контракта (`MarkHex`, `PriorityBase`, `RoutingTable`, duplicate-id winner); - `transport_netns.go` разложен на role-файлы (`spec`, `rules`, `run`) без изменения netns lifecycle-контракта (`ensure`, `cleanup`, nft NAT comment tags, policy-route update); - `transport_bootstrap_bypass.go` разложен на role-файлы (`candidates`, `resolve`, `routes`, `state`) без изменения bypass-контракта (`start/restart sync`, `stop cleanup`, strict mode, bootstrap state persistence); - `transport_singbox_profiles_flow.go` разложен на handler role-файлы (`validate`, `render`, `apply`, `rollback`, `history`) с сохранением endpoint-контрактов `/api/v1/transport/singbox/profiles/{id}/{action}`; - `transport_singbox_profiles.go` разложен на role-файлы (`list`, `card`, `features`) с сохранением endpoint-контрактов `/api/v1/transport/singbox/profiles*`; - benchmark-контур DNS вынесен из `dns_settings.go` в `dns_settings_benchmark.go` с сохранением API-контракта `POST /api/v1/dns/benchmark` и профилей `quick/load`; - `resolver_bridge.go` разложен на role-файлы (`utils`, `cache`, `dns`, `pipeline`) без изменения bridge-контракта с подпакетом `app/resolver`; - `transport_backends.go` разложен на role-файлы (`selection/config facade`, `probe adapters`, `runtime-mode backends`) без изменения backend action/health/provision/cleanup контрактов; - `vpn_login_session.go` разложен на role-файлы (`models/manager`, `state methods`, `pty lifecycle`, `http handlers`) без изменения login-session endpoint-контрактов; - размеры крупных файлов снижены: `traffic_mode.go` `923 -> 820`, `transport_singbox_profiles.go` `979 -> 897`, `transport_singbox_profiles_flow.go` `1372 -> 1336`; - после изменений выполнены `go test ./...` и `go build ./...` — успешно. - 2026-03-11: продолжен шаг `F1.11` (поэтапный вынос transport в подпакеты): - добавлен новый подпакет `selective-vpn-api/app/transportcfg` и файл `runtime_helpers.go`; - в подпакет перенесены runtime/config helper'ы transport backend: - `ConfigString`, `ConfigBool`; - `RuntimeMode`; - `BackendUnit`/`DefaultBackendUnit`; - `DNSTTSSHTunnelEnabled`, `DNSTTSSHUnit`; - `SystemdActionUnits`, `SystemdHealthUnits`; - добавлен `selective-vpn-api/app/transportcfg/systemd_helpers.go` и в подпакет перенесены systemd helper'ы transport backend: - unit-name/path/ownership/file-write helper'ы; - systemd unit renderer'ы (`primary` + `ssh-overlay`); - service tuning/hardening нормализация и prefix-fallback parser'ы; - добавлен `selective-vpn-api/app/transportcfg/exec_helpers.go` и в подпакет перенесены exec/binary helper'ы transport backend: - template command builders (`singbox`, `dnstt`, `phoenix`, `ssh-overlay`); - binary resolution/validation helpers (`ResolveBinary`, `FindBinaryPath`, `BinaryExists`, packaging profile); - shell/config helpers (`ShellJoinArgs`, `ShellQuoteArg`, `ConfigInt`, `DefaultConfigPath`); - добавлен `selective-vpn-api/app/transportcfg/probe_helpers.go` и в подпакет перенесены probe helper'ы transport backend: - latency probing pipeline (`ProbeClientLatency`, host/netns endpoint probe); - endpoint extraction/parsing/dedupe helpers для config/singbox-json; - shared parse helpers (`ParseInt`, `SplitCSV`). - app-facade сохранён внутри существующих transport-файлов (`transport_backends.go`, `transport_backends_adapter_systemd.go`), а отдельные файлы `transport_backends_runtime_helpers.go` и `transport_backends_systemd_helpers.go` удалены для снижения шума в корне `app/`; - отдельный файл `transport_backends_exec_helpers.go` удалён; совместимые фасады сохранены в `transport_backends.go`; - отдельный файл `transport_backends_probe_helpers.go` удалён; runtime dial/netns фасады и backward-compatible symbols для тестов собраны в `transport_backends.go`; - добавлен подпакет `selective-vpn-api/app/eventsbus` (`Bus`, `Push`, `Since`); `app/events_bus.go` переведён в thin-adapter/facade с сохранением контракта `events.push/events.since` и `envInt`; - добавлен подпакет `selective-vpn-api/app/apiroutes` (`Register` + доменные route-registrars), `app/api_routes.go` переведён на dependency-assembly facade; - удалены файлы route-registry из корня `app`: `api_routes_core.go`, `api_routes_dns.go`, `api_routes_trace.go`, `api_routes_traffic.go`, `api_routes_transport.go`, `api_routes_vpn.go`; - удалён файл `transport_backends_adapters_misc.go`; типы `transportUnsupportedRuntimeBackend` и `transportMockBackend` перенесены в `transport_backends.go` (единая точка transport-facade); - добавлен подпакет `selective-vpn-api/app/syscmd` и перенесены низкоуровневые command helper'ы (`RunCommand`, `RunCommandTimeout`, `CheckPolicyRoute`); `app/shell.go` оставлен как thin-facade; - добавлен подпакет `selective-vpn-api/app/httpx` и перенесены общие HTTP helper'ы (`LogRequests`, `WriteJSON`, `HandleHealthz`); `app/http_helpers.go` переведён в thin-facade; - добавлен подпакет `selective-vpn-api/app/eventstream` и перенесён SSE polling/heartbeat loop (`ParseSinceID`, `Serve`); `app/events_handlers.go` оставлен как adapter к `events.since`; - добавлен подпакет `selective-vpn-api/app/refreshcoord` и перенесена SWR/backoff state-machine (`Coordinator`, `Snapshot`); `app/refresh_coordinator.go` оставлен как thin-facade с прежними методами (`beginRefresh/finish*/snapshot`) для совместимости; - в `egress_identity.go` убран прямой доступ к внутренним полям SWR (`refreshInProgress`, `nextRetryAt`) — заменено на фасадные методы (`refreshInProgress()`, `nextRetryAt()`, `clearBackoff()`) для безопасной декомпозиции; - добавлен подпакет `selective-vpn-api/app/nftupdate` и перенесён алгоритм обновления nft-set (`interval compression`, `atomic transaction`, `chunked fallback`); - `app/nft_update.go` переведён в thin-facade с прежним публичным контрактом (`nftUpdateIPsSmart`, `nftUpdateSetIPsSmart`) и прежним trace-логированием через `appendTraceLine("routes", ...)`; - добавлен подпакет `selective-vpn-api/app/trafficcandidates` и перенесён сбор candidates (`subnets`, `units`, `uids`) в DI-модуль; - `app/traffic_candidates.go` переведён в thin-adapter (маппинг pkg DTO -> API DTO без изменения endpoint path/поля ответа); - для визуальной очистки корня `app/` удалены 16 отдельных resolver bridge-файлов: - `resolver_artifacts_bridge.go` - `resolver_dns_config_bridge.go` - `resolver_domain_cache_bridge.go` - `resolver_host_lookup_bridge.go` - `resolver_mode_runtime_bridge.go` - `resolver_planning_bridge.go` - `resolver_precheck_finalize_bridge.go` - `resolver_precheck_types_bridge.go` - `resolver_resolve_batch_bridge.go` - `resolver_runtime_tuning_bridge.go` - `resolver_start_log_bridge.go` - `resolver_static_labels_bridge.go` - `resolver_summary_log_bridge.go` - `resolver_timeout_recheck_bridge.go` - `resolver_types_bridge.go` - `resolver_wildcard_bridge.go` - вместо них добавлен единый `selective-vpn-api/app/resolver_bridge.go` (консолидированный bridge-слой resolver-пайплайна); - добавлен подпакет `selective-vpn-api/app/trafficprofiles` и вынесена state/store-логика профилей приложений (`list/upsert/delete/dedupe`, json persistence, id-derive); - `app/traffic_app_profiles.go` переведён в thin-adapter: HTTP decode/encode + mapping DTO `app <-> trafficprofiles`; - функция `sanitizeID` сохранена в `app` (runtime-совместимость с transport/egress/singbox контуром); - добавлен `selective-vpn-api/app/trafficprofiles/appkey.go` и в него вынесены `CanonicalizeAppKey` + `SplitCommandTokens` + wrapper parser helpers; - удалён root-файл `selective-vpn-api/app/traffic_appkey.go`; в `app/traffic_app_profiles.go` оставлены совместимые фасады `canonicalizeAppKey`/`splitCommandTokens` для существующих вызовов и тестов; - добавлен подпакет `selective-vpn-api/app/trafficappmarks` и вынесены хелперы состояния app-marks: - `LoadState` / `SaveState` (json persistence), - `DedupeItems` / `UpsertItem`, - `IsAllDigits`; - в `app/traffic_appmarks.go` сохранены совместимые фасады (`loadAppMarksState`, `saveAppMarksState`, `dedupeAppMarkItems`, `upsertAppMarkItem`, `isAllDigits`) и type-alias на пакетные модели (`appMarksState`, `appMarkItem`); - `traffic_appmarks.go` сокращён с `1140` до `1002` строк, без изменения endpoint-поведения и nft runtime-логики; - добавлен файл `selective-vpn-api/app/trafficappmarks/cgroup.go` и вынесены `ResolveCgroupV2PathForNft`, `NormalizeCgroupRelOnly`, `CgroupDirInode`; - в `app/traffic_appmarks.go` оставлены одноимённые фасады для совместимости текущих вызовов; - добавлен файл `selective-vpn-api/app/trafficappmarks/nft.go` и вынесены `InsertAppMarkRule/DeleteAppMarkRule/HasAppMarkRule`, `UpdateLocalBypassSet`, `ParseNftHandle`, `CompactIPv4IntervalElements`; - туда же вынесены cleanup helper'ы `CleanupLegacyRules` и `ClearManagedRules`, а в `app/traffic_appmarks.go` оставлены фасады `cleanupLegacyAppMarksRules`/`clearManagedAppMarkRules`; - в `selective-vpn-api/app/trafficappmarks/store.go` добавлен `PruneExpired` (TTL prune + safe handling corrupted `expires_at` + callback на удаление nft-rule); - в `selective-vpn-api/app/trafficappmarks/nft.go` добавлен `EnsureBase` (best-effort bootstrap table/chains/set + jump to app chain), фасад `ensureAppMarksNft` в `app` сохранён; - в `app/traffic_appmarks.go` сохранены совместимые фасады `nftInsertAppMarkRule/nftDeleteAppMarkRule/nftHasAppMarkRule/updateAppMarkLocalBypassSet/parseNftHandle`; - `traffic_appmarks.go` дополнительно сокращён до `723` строк; - добавлен подпакет `selective-vpn-api/app/trafficmode` и файл `normalize.go` (вынесены `TokenizeList`, `NormalizeSubnetList`, `NormalizeUIDToken`, `NormalizeUIDList`, `NormalizeCgroupList`); - `app/traffic_mode.go` переведён на фасады к `trafficmode` для normalize-контура (без изменения API/маршрутной логики); - добавлен `selective-vpn-api/app/trafficmode/autolocal.go` (вынесен parser `ip route show table main` для auto-local-bypass); - в `app/traffic_mode.go` оставлены фасады `parseRouteDevice/isContainerIface/routeLineIsLinkDown/isAutoBypassDestination` для backward-compatible вызовов из `traffic_candidates.go`; - добавлен `selective-vpn-api/app/trafficmode/cgroup.go` (вынесен cgroup scan/resolve и derive uidrange из `/proc//status`); - добавлен `selective-vpn-api/app/trafficmode/ingress.go` (вынесен ingress-reply bypass nft lifecycle и state-check); - добавлен `selective-vpn-api/app/trafficmode/rules.go` (вынесен parsing `ip rule show` + route-probe `ip route get`); - добавлен `selective-vpn-api/app/trafficmode/apply.go` (вынесен apply/remove управляемых `ip rule` и overlay overrides); - удалены промежуточные wrapper-функции cgroup/nftObjectMissing из `traffic_mode.go`; `buildEffectiveOverrides` использует пакетные вызовы напрямую; - `traffic_mode.go` сокращён с `1449` до `923` строк; - добавлен подпакет `selective-vpn-api/app/egressutil` и файл `util.go`; - в `app/egress_identity.go` переведены на фасады: `parseEgressIPFromBody`, `egressParseGeoResponse`, `normalizeCountryCode`, `egressIPEndpoints`, `egressGeoEndpointsForIP`, `egressLimitEndpointsForNetns`, `egressJoinErrorsCompact`, `egressParseSingBoxSOCKSProxyURL`, `egressResolvedHostForURL`, `resolveEgressCurlPath`, `resolveEgressWgetPath`, `egressTimeoutSec`; - удалены устаревшие внутренние переменные/функции egress (`egressCurlPathOnce/egressWgetPathOnce`, локальные any/url helpers); - удалены мёртвые egress wrappers (неиспользуемые after-extract), сохранён публичный runtime-контракт для активных вызовов; - `egress_identity.go` сокращён с `1208` до `932` строк; - добавлен подпакет `selective-vpn-api/app/dnscfg` и файл `smartdns.go`; - `app/dns_settings.go` переведён на фасады `dnscfg` для SmartDNS default/config parsing и DNS upstream normalization; - добавлен `selective-vpn-api/app/dnscfg/systemd.go` и вынесены helper'ы `smartdns` unit state/action; - добавлен `selective-vpn-api/app/dnscfg/benchmark.go` и вынесен benchmark-контур DNS (core scoring/probe/normalize) с callback-зависимостями (`splitDNS`, `classifyDNSError`, `isPrivateIPv4`); - `app/dns_settings.go` оставлен как facade HTTP/DTO слой для benchmark-endpoint, без изменения JSON-контракта; - добавлен `selective-vpn-api/app/dnscfg/pool.go` и вынесены helper'ы pool/legacy-конвертации; - `app/dns_settings.go` использует фасады `dnscfg` для pool нормализации/конвертации, сохраняя прежние JSON-модели и endpoint-поведение; - в `selective-vpn-api/app/dnscfg/mode.go` добавлены `ModeConfig/ModeState/LoadMode/SaveMode`; `app/dns_settings.go` переведён на тонкий фасад для чтения/сохранения `dns_mode.json`; - `dns_settings.go` дополнительно очищен от лишних benchmark-wrapper-функций; benchmark-контур работает через прямые вызовы `dnscfg` без изменения API-контракта; - добавлен `selective-vpn-api/app/dnscfg/upstreams.go` и вынесены parse/normalize/render helper'ы конфига `dns-upstreams.conf`; - `loadDNSUpstreamsConfFile/saveDNSUpstreamsConfFile` переведены на фасады `dnscfg` без изменения формата `dns-upstreams.conf` и legacy JSON mirror; - добавлен `selective-vpn-api/app/dnscfg/prewarm.go`; логика SmartDNS prewarm (domain expansion, parallel dig, runtime/manual nft merge, summary/per-upstream log) вынесена из `app/dns_settings.go` в пакет `dnscfg` через callback-deps; - добавлен `selective-vpn-api/app/dns_smartdns_handlers.go`; туда вынесены `handleSmartdnsRuntime`, `handleSmartdnsPrewarm`, `runSmartdnsPrewarm` (тонкий app-adapter к `dnscfg.RunPrewarm`); - `dns_settings.go` дополнительно сокращён до `749` строк (было `1073` до этого шага), без изменения endpoint-path/JSON-контрактов; - в `app/egressutil` добавлены `scope.go`/`identity.go`/`probe.go`/`http.go` (scope parser, identity diff, endpoint probe loop, HTTP body fetch helper); - в `app/egress_identity.go` сохранены совместимые фасады (`parseEgressScope`, `egressIdentityChanged`) и перевод вызовов probe/geo на пакетные helper'ы; - удалены локальные pure-wrapper'ы egress; `egress_identity_test.go` использует `egressutil` напрямую; - `egress_identity.go` сокращён до `854` строк (было `932` на старте блока F1.14); - добавлен `selective-vpn-api/app/transportcfg/singbox_helpers.go` и в него вынесены повторно используемые helper'ы профилей singbox (digest/diff/path/io/validators/stamp/messages); - `transport_singbox_profiles_flow.go` переведён на пакетные helper-вызовы `transportcfg` с сохранением API/flow-поведения; файл сокращён до `1624` строк; - добавлен `selective-vpn-api/app/transportcfg/history_helpers.go`; вынос history io/selection helpers из `transport_singbox_profiles_flow.go` выполнен через пакетные фасады; - удалены локальные helper'ы `findSingBoxBinary`/`sanitizeHistoryStamp` из `transport_singbox_profiles_flow.go` (замена на package helpers); - `transport_singbox_profiles_flow.go` дополнительно сокращён до `1563` строк; - `dns_settings.go` сокращён с `1521` до `1196` строк; - валидация: `go test ./...` и `go build ./...` в `selective-vpn-api` — успешно. - 2026-03-10: продолжен шаг `F1.11` (декомпозиция `transport_backends.go` без смены поведения): - вынесен systemd helper-блок в `selective-vpn-api/app/transport_backends_systemd_helpers.go`: - unit-name/ownership/file-write helpers; - unit renderer + ssh-overlay renderer; - tuning/hardening normalizers и prefix-fallback config helpers. - вынесен probe/latency helper-блок в `selective-vpn-api/app/transport_backends_probe_helpers.go`: - endpoint collector/parser/dedupe; - host/netns probe и latency sampling. - вынесен exec/binary helper-блок в `selective-vpn-api/app/transport_backends_exec_helpers.go`: - `resolveTransportPrimaryExecStart` + `buildTransport*Command`; - packaging/binary resolution helpers (`resolveTransportBinary`, `validateRequiredBinary`, `transportPackagingProfile`); - shell/config int helpers (`shellJoinArgs`, `shellQuoteArg`, `transportConfigInt`). - вынесен runtime helper-блок в `selective-vpn-api/app/transport_backends_runtime_helpers.go`: - `transportBackendUnit/defaultTransportBackendUnit`; - `transportRuntimeMode`; - `transportSystemdActionUnits/transportSystemdHealthUnits`; - `transportConfigString/transportConfigBool`. - вынесены adapter-реализации `unsupported/mock` в `selective-vpn-api/app/transport_backends_adapters_misc.go`. - вынесен `systemd` adapter в `selective-vpn-api/app/transport_backends_adapter_systemd.go` (действия/health/provision/cleanup + missing-unit helpers). - итоговая декомпозиция: `transport_backends.go` сокращён с `1954` до `73` строк (тонкий core/facade). - валидация: `go test ./app -run TestTransportSystemdBackendHealth -count=1`, `go test ./...`, `go build ./...` — успешно. - 2026-03-10: продолжен `F1.11` по resolver-контурy (введена отдельная папка `selective-vpn-api/app/resolver/`): - вынесен wildcard matcher в подпакет `selective-vpn-api/app/resolver/wildcard_matcher.go` (`NormalizeWildcardDomain/NewWildcardMatcher/Match/IsExact/Count`); - вынесены общие resolver helper'ы в `selective-vpn-api/app/resolver/common.go` (`UniqueStrings`, `PickDNSStartIndex`, `StripANSI`, `IsPrivateIPv4`); - вынесены DNS метрики/типы в `selective-vpn-api/app/resolver/dns_metrics.go` (`DNSErrorKind`, `DNSMetrics`, `DNSUpstreamMetrics`); - вынесены JSON/IO helper'ы в `selective-vpn-api/app/resolver/io_helpers.go` (`ReadLinesAllowMissing`, `LoadJSONMap`, `SaveJSON`, `LoadResolverPrecheck*`, `LoadResolverLiveBatch*`); - вынесены DNS parser/helper'ы в `selective-vpn-api/app/resolver/dns_helpers.go` (`SplitDNS`, `ClassifyDNSError`); - вынесен adaptive live-batch policy-калькулятор в `selective-vpn-api/app/resolver/live_batch.go` (`ComputeNextLiveBatchTarget`, `ComputeNextLiveBatchNXHeavyPct`) с сохранением прежней формулы; - вынесен live-batch selector в `selective-vpn-api/app/resolver/live_batch_select.go` (`ClassifyLiveBatchHost`, `SplitLiveBatchCandidates`, `PickAdaptiveLiveBatch`); - вынесен upstream-pool helper в `selective-vpn-api/app/resolver/dns_upstreams.go` (`BuildResolverFallbackPool`, `MergeDNSUpstreamPools`); - вынесен error-policy helper в `selective-vpn-api/app/resolver/error_policy.go` (`SmartDNSFallbackForTimeoutEnabled`, `ShouldFallbackToSmartDNS`, `ClassifyHostErrorKind`, `ShouldUseStaleOnError`); - полностью вынесен `domain cache` блок в `selective-vpn-api/app/resolver/domain_cache.go` (state/model + normalize/migrate + get/set/quarantine/stale + score/state policy + json render/summary); - вынесены precheck/live-batch типы в `selective-vpn-api/app/resolver/precheck_types.go` + bridge `selective-vpn-api/app/resolver_precheck_types_bridge.go`; - вынесено сохранение precheck-state в `selective-vpn-api/app/resolver/precheck_state.go` (`SaveResolverPrecheckState`) с bridge-вызовом из `app`; - вынесен static/PTR блок в `selective-vpn-api/app/resolver/static_labels.go` (`ParseStaticEntries`, `ResolveStaticLabels`, `DigPTR`) + bridge `selective-vpn-api/app/resolver_static_labels_bridge.go`; - вынесен timeout-quarantine recheck блок в `selective-vpn-api/app/resolver/timeout_recheck.go` (`RunTimeoutQuarantineRecheck`) + bridge `selective-vpn-api/app/resolver_timeout_recheck_bridge.go`; - вынесен загрузчик DNS-конфига в `selective-vpn-api/app/resolver/dns_config.go` (`LoadDNSConfig`) + bridge `selective-vpn-api/app/resolver_dns_config_bridge.go`; - вынесен host-lookup блок в `selective-vpn-api/app/resolver/host_lookup.go` (`ResolveHost`, `DigAWithPolicy`) + bridge `selective-vpn-api/app/resolver_host_lookup_bridge.go` (`resolveHostGo`, `digA`, `digAWithPolicy`); - типы DNS policy/cooldown (`dnsAttemptPolicy`, `dnsRunCooldown`, `dnsCooldownState`) перенесены из `resolver.go` в `selective-vpn-api/app/resolver_dns_policy.go` для очистки монолита; - вынесен runtime-tuning блок (`TTL/workers/dns timeout/precheck/live-batch/negative TTL`) в `selective-vpn-api/app/resolver/runtime_tuning.go` (`BuildResolverRuntimeTuning`) + bridge `selective-vpn-api/app/resolver_runtime_tuning_bridge.go`; - вынесен artifact-builder блок (`IPs/IPMap/Direct/Wildcard`) в `selective-vpn-api/app/resolver/artifacts.go` (`BuildResolverArtifacts`) + bridge `selective-vpn-api/app/resolver_artifacts_bridge.go`; - вынесен planning блок (`fresh/toResolve/cache_negative/quarantine/stale/precheck_scheduled`) в `selective-vpn-api/app/resolver/planning.go` (`BuildResolvePlanning`) + bridge `selective-vpn-api/app/resolver_planning_bridge.go`; - вынесен precheck finalize/save блок (next live-batch calc + state persist + force-file consume) в `selective-vpn-api/app/resolver/precheck_finalize.go` (`FinalizeResolverPrecheck`) + bridge `selective-vpn-api/app/resolver_precheck_finalize_bridge.go`; - вынесен concurrent resolve-блок (`workers/jobs/results` + stale-on-error apply) в `selective-vpn-api/app/resolver/resolve_batch.go` (`ExecuteResolveBatch`) + bridge `selective-vpn-api/app/resolver_resolve_batch_bridge.go`; - вынесен summary/breakdown/precheck-log блок в `selective-vpn-api/app/resolver/summary_log.go` (`LogResolverSummary`) + bridge `selective-vpn-api/app/resolver_summary_log_bridge.go`; - вынесен runtime DNS mode apply/log блок в `selective-vpn-api/app/resolver/mode_runtime.go` (`ApplyDNSModeRuntime`, `LogDNSMode`) + bridge `selective-vpn-api/app/resolver_mode_runtime_bridge.go`; - вынесен start/policy log блок в `selective-vpn-api/app/resolver/start_log.go` (`LogResolverStart`) + bridge `selective-vpn-api/app/resolver_start_log_bridge.go`; - финальный orchestration split: добавлен pipeline-файл `selective-vpn-api/app/resolver_pipeline.go` (`buildResolverJobContext`, `runResolverPipeline`), а `runResolverJob` оставлен тонким фасадом; - добавлен bridge `selective-vpn-api/app/resolver_domain_cache_bridge.go` (сохранены старые имена/методы `domainCache*` в пакете `app` через wrapper-слой); - добавлен совместимый bridge `selective-vpn-api/app/resolver_wildcard_bridge.go` (сохранены старые вызовы `normalizeWildcardDomain/newWildcardMatcher` в пакете `app`); - добавлен type bridge `selective-vpn-api/app/resolver_types_bridge.go` (alias-константы/типы для `dnsErrorKind`/`dnsMetrics` без смены API внутри `app`); - вынесен DNS policy/cooldown блок из монолита в `selective-vpn-api/app/resolver_dns_policy.go`; - `resolver.go` сокращён с `2983` до `41` строк без изменения API-контрактов; - валидация: `go test ./...` и `go build ./...` в `selective-vpn-api` — успешно. - 2026-03-10: закрыт шаг `F1.4` (декомпозиция `vpn_dashboard_qt.py`): - добавлен пакет `selective-vpn-gui/main_window/` и вынесены базовые модули: `constants.py` (shared UI constants), `workers.py` (`EventThread`, `LocationsThread`); - вынесен UI shell-контур (`build tabs + helpers + locations/egress`) в `main_window/ui_shell_mixin.py`; - вынесен крупный SingBox-контур в подпакет `main_window/singbox/*` (`editor`, `cards`, `links`, `runtime`) и сохранён фасад `main_window/singbox_mixin.py`; - вынесен runtime/refresh/actions контур в `main_window/runtime_actions_mixin.py` (events stream, refresh, login/auth, routes/dns/domains actions, close lifecycle); - добавлена вторичная декомпозиция mixin-слоёв: `ui_tabs_*`, `ui_helpers`, `ui_location_runtime`, `runtime_{state,refresh,auth,ops}`, `singbox/{links_*,runtime_*}`; фасады `ui_tabs_mixin.py`, `ui_shell_mixin.py`, `runtime_actions_mixin.py`, `singbox_mixin.py` сохранены; - отдельный split `ui_tabs_singbox_mixin.py` на `ui_tabs_singbox_{layout,editor}_mixin.py` (убран последний UI-файл >700 строк); - `selective-vpn-gui/vpn_dashboard_qt.py` сокращён с `6103` до `116` строк, при этом сохранён как thin-bootstrap+UI wiring; - валидация: `python3 -m py_compile vpn_dashboard_qt.py main_window/*.py main_window/singbox/*.py` и import-проверка `MainWindow` проходят. - 2026-03-10: hotfix `systemd stop` idempotency для transport lifecycle: - в `transportSystemdBackend.Action` добавлен no-op режим для `action=stop`, если `systemctl` возвращает `Unit ... not loaded/not found/unknown unit` (чтобы отсутствующий unit не валил switch-пайплайн); - добавлен unit-тест `TestTransportSystemdBackendStopMissingUnitIsNoop`; - runtime-валидация на живом API: `POST /api/v1/transport/clients/sg-finland-yur/start` и `.../sg-realnetns/start` проходят, `.../sg-torjantest-ylu9ja7f/stop` возвращает `ok=true` с warning вместо `TRANSPORT_BACKEND_ACTION_FAILED`. - 2026-03-10: hotfix `systemd start/restart` auto-provision для новых SingBox-профилей: - в `transportSystemdBackend.Action` добавлен общий fallback для `SingBox`: при `start/restart` и ошибке `unit not found/not loaded` backend автоматически выполняет `Provision` и повторяет `systemctl start/restart` один раз; - добавлен unit-тест `TestTransportSystemdBackendStartAutoProvisionOnMissingUnit`; - runtime-валидация: `POST /api/v1/transport/clients/sg-torjantest-ylu9ja7f/start` вернул `ok=true`, `status_after=up` без ручного `Prepare`. - 2026-03-10: закрыт шаг `F1.6` по `cmd/*` entrypoints в `selective-vpn-api`: - добавлены отдельные main-пакеты: `cmd/selective-vpn-api`, `cmd/selective-vpn-routes-update`, `cmd/selective-vpn-routes-clear`, `cmd/selective-vpn-autoloop`; - в `app/server.go` выделены явные entrypoint-функции `RunAPIServer`, `RunRoutesUpdateCLI`, `RunRoutesClearCLI`, `RunAutoloopCLI`, route registration вынесена в `registerAPIRoutes`; - legacy `main.go` сохранён для обратной совместимости (старые unit/скрипты продолжают работать через `app.Run()`). - 2026-03-10: выполнена повторная валидация `F1.6` (контрольный прогон перед следующим рефактором): - `go test ./...` и `go build ./...` в `selective-vpn-api` прошли успешно; - собраны отдельные бинарники из `cmd/*` в `/tmp` (`selective-vpn-api`, `selective-vpn-routes-update`, `selective-vpn-routes-clear`, `selective-vpn-autoloop`). - 2026-03-10: закрыт шаг `F1.7` (декомпозиция API bootstrap/роутера): - удалён монолит `selective-vpn-api/app/server.go` и введены файлы `entrypoints.go`, `api_bootstrap.go`, `api_routes.go`; - сигнатуры и поведение сохранены: `Run*CLI`, `RunAPIServer`, `runAPIServerAtAddr`, `registerAPIRoutes`; - `registerAPIRoutes` дополнительно разложен на доменные helper'ы (`registerCoreRoutes`, `registerRoutesControlRoutes`, `registerTrafficRoutes`, `registerTransportRoutes`, `registerDNSRoutes`, `registerVPNRoutes`) без изменения endpoint-path; - контрольная валидация `go test ./...` и `go build ./...` прошла успешно. - 2026-03-10: закрыт шаг `F1.8` (физическая декомпозиция route-registry по доменам): - `register*Routes` вынесены из общего файла в `app/api_routes_core.go`, `api_routes_traffic.go`, `api_routes_transport.go`, `api_routes_trace.go`, `api_routes_dns.go`, `api_routes_vpn.go`; - `app/api_routes.go` оставлен как thin-facade (`registerAPIRoutes`), который только собирает доменные registrars; - endpoint-path и handlers сохранены 1:1; контрольная валидация `go test ./...` и `go build ./...` прошла успешно. - 2026-03-10: закрыт шаг `F1.9` (переход от "всё в app" к подпапкам runtime-слоя): - добавлен подпакет `app/cli` с раннерами `routes-update`, `routes-clear`, `autoloop` и dependency-injection через callbacks; - добавлен подпакет `app/bootstrap` с HTTP server runner (`Run(ctx, Config)`), `app/api_bootstrap.go` переведён на thin-wrapper + `prepareAPIRuntime()`; - фасады в `app` сохранены (`RunRoutesUpdateCLI`, `RunRoutesClearCLI`, `RunAutoloopCLI`, `runAPIServerAtAddr`), поэтому внешний контракт и поведение не изменились; - контрольная валидация: `go test ./...` и `go build ./...` успешны. - 2026-03-10: закрыт шаг `F1.10` (декомпозиция transport handler-монолита): - удалён монолит `app/transport_handlers.go` (`~2440` строк), код разложен на модули: `transport_shared.go`, `transport_handlers_clients.go`, `transport_handlers_policy.go`, `transport_policy_validate.go`, `transport_client_runtime.go`, `transport_tokens_state.go`; - все endpoint-handler сигнатуры и policy/lifecycle поведение сохранены (роуты `transport/*` без изменений); - контрольная валидация: `go test ./...` и `go build ./...` успешны. - 2026-03-10: начат шаг `F1.11` (подпакеты без циклов): - вынесен token-store в `app/transporttoken/store.go` (issue/consume/ttl cleanup + token generation); - `transport_tokens_state.go` переведён на новый store через facade (`issueTransportConfirmToken`, `consumeTransportConfirmToken`, `newTransportToken`); - обновлены unit-тесты confirm-token lifecycle под новый store (`TestTransportConfirmStoreExpiresToken`), `go test ./...` и `go build ./...` успешны. - 2026-03-10: начат шаг `F1.12` (декомпозиция `api_client.py` по папкам): - добавлен пакет `selective-vpn-gui/api/` (`models.py`, `errors.py`, `utils.py`, `client.py`, `__init__.py`); - `api_client.py` переведён в backward-compatible facade (реэкспорт `ApiClient/ApiError/models/strip_ansi`); - сохранена обратная совместимость импортов (`from api_client import ...`) и добавлен новый путь (`from api import ...`); - проверка: `py_compile` + импорты `dashboard_controller.py`, `vpn_dashboard_qt.py`, `dns_benchmark_dialog.py` проходят. - 2026-03-10: закрыт шаг `F1.12` (полная доменная декомпозиция Python API-клиента): - `selective-vpn-gui/api/client.py` сокращён до base HTTP/SSE + shared helpers (`_request/_json/_to_int/_parse_cmd_result`); - доменные методы вынесены в mixin-модули: `api/status.py`, `api/routes.py`, `api/traffic.py`, `api/dns.py`, `api/domains.py`, `api/vpn.py`, `api/trace.py`; - дополнительный разрез transport-домена: `api/transport_clients.py`, `api/transport_policy.py`, `api/transport_singbox.py` + facade `api/transport.py`; - backward compatibility сохранена (`api_client.py` facade и `api.transport.TransportApiMixin`), `py_compile` и импорты GUI-модулей проходят. - 2026-03-10: закрыт шаг `F1.3` (декомпозиция `dashboard_controller.py`): - добавлен пакет `selective-vpn-gui/controllers/` с domain mixin-модулями: `status`, `vpn`, `routes`, `traffic`, `transport`, `dns`, `domains`, `trace`, плюс общий `core` и `views`; - `dashboard_controller.py` сокращён до thin-facade (`DashboardController`) с прежней точкой импорта и совместимыми экспортами (`TraceMode`, view-models); - проверка: `py_compile` + импорты `vpn_dashboard_qt.py`, `traffic_mode_dialog.py`, `dns_benchmark_dialog.py` проходят. - 2026-03-10: зафиксировано разделение зависимостей `go.mod` vs runtime services: - выполнен `go mod tidy` для `selective-vpn-api` (изменений не потребовалось; Go-модули актуальны); - добавлен preflight-check `scripts/check_runtime_dependencies.sh` (required/optional + `--strict`); - добавлен документ `docs/phase-b/B4_RUNTIME_DEPENDENCIES_AND_PREFLIGHT.md` с реестром бинарей/unit-зависимостей до начала крупного рефакторинга. - 2026-03-10: стабилизирован egress refresh для `AdGuardVPN` после смены локации: - в Go API (`/api/v1/vpn/location`) добавлен backend `egress refresh burst` для `adguardvpn` (force refresh сразу + отложенные итерации), чтобы egress IP/geo догонял фактическое переключение туннеля; - в `egress identity` force-refresh теперь обходит negative geo-cache (ошибка geo не блокирует повторную попытку при `force=true`), а TTL negative geo-cache снижен до `30s`; - в GUI (`refresh_vpn_tab`) добавлены дополнительные post-switch trigger'ы egress-refresh (на смену desired location и на завершение switching), а polling расширен до стабилизации `IP + country` без ранней остановки. - 2026-03-10: доведён anti-regression контур `SingBox netns + multi-profile switch`: - в backend lifecycle `POST /api/v1/transport/clients/{id}/start|restart` добавлен обязательный preflight для `SingBox` (render/validate + materialize `config_path`) до `systemctl`, чтобы старт не обходил профильную проверку; - при `start/restart` выбранного `SingBox` в `netns` backend теперь автоматически останавливает другие `SingBox`-клиенты в этом же namespace перед запуском (убран конфликт `listen 127.0.0.1:10808: address already in use` при switch между профилями); - в `systemd` backend добавлен best-effort `systemctl reset-failed ` перед `start/restart`, чтобы switch не упирался в `start-limit-hit` после прошлых неуспешных стартов; - закрыт legacy кейс `packet_encoding: none` (SingBox panic `unknown value`): нормализация выполняется в Go-render pipeline, а в GUI-editor/link-import `none` больше не пишется в raw config; - для `egress identity` при `transport:` и `netns_enabled=true` у `SingBox` direct-netns fallback полностью отключён: probe идёт только через локальный SOCKS inbound, иначе возвращается ошибка (без ложного AdGuard/system IP); - runtime-проверка: `sg-finland-yur` стартует даже после удаления `/etc/selective-vpn/transports/sg-finland-yur/singbox.json` (файл воссоздаётся preflight); при последовательном switch подтверждён ожидаемый egress: `sg-finland-yur -> FI (92.42.102.181)`, `sg-realnetns -> NL (46.17.97.149)`. - 2026-03-10: добавлен хвостовой экспериментальный этап `Z1` (глобальный L7 orchestration-layer) и отдельно зафиксировано правило: подготовку можно делать заранее, но runtime-включение только в конце планов. - 2026-03-10: зафиксирован отложенный этап `E7` (без реализации в текущем спринте): - целевое разделение `System selective PBR` (L3/L4 transport guardrails) и `SingBox L7 routing` (policy-engine); - зафиксирован полный список будущих работ по L7 правилам, renderer, pipeline `validate/apply/rollback`, DNS-стратегии и observability. - 2026-03-10: исправлен приоритет egress-проверки для `transport:` в `netns`: - для `SingBox` с локальным `socks` inbound probe теперь сначала идёт через `socks5h://127.0.0.1:` (реальный tunnel egress), а direct-netns probe оставлен только как fallback; - устранён ложный кейс, когда `transport:sg-realnetns` показывал системный/AdGuard IP вместо IP tunnel-профиля. - 2026-03-10: дополнили фиксы `SingBox + netns` и старт нового профиля: - для `transport:` при `netns + socks-inbound` direct-netns fallback отключён; при ошибке proxy probe возвращается ошибка, чтобы UI не показывал ложный AdGuard/system IP как egress transport-клиента; - в GUI `Run/Start` добавлен preflight `singbox profile apply` (`skip_runtime=true`) перед switch/start, чтобы materialize `config_path` и исключить падение нового клиента с ошибкой `open .../singbox.json: no such file or directory`. - 2026-03-10: исправлен конфликт `netns`/`agvpn` маршрутов для `SingBox Real NetNS`: - устранена причина флапов `lookup n3.elmprod.tech ... deadline exceeded` при живом профиле; - в `traffic_mode` авто-bypass больше не подхватывает `linkdown` маршруты и `svh*/svn*` интерфейсы; - в `transport_netns` добавлена принудительная синхронизация policy-route (`table agvpn`) для `netns_subnet` на актуальный host-veth при `start/restart` + cleanup при удалении namespace; - выполнена одноразовая очистка сиротского legacy namespace (`svpn-realnetns`) и проверен стабильный egress (`transport:sg-realnetns` -> `stale=false`, IP/geo обновляется). - 2026-03-10: доведён `E6` netns runtime contour для `transport:`: - netns-probe больше не зависит от DNS внутри namespace (`curl --resolve` с host-side резолвом endpoint хоста); - добавлен fallback probe через локальный `SingBox` SOCKS inbound (`socks5h://127.0.0.1:`) для изолированных netns; - ограничена длительность netns-probe (по умолчанию `SVPN_EGRESS_NETNS_MAX_ENDPOINTS=1`) и уплотнены сообщения ошибок (`last_error`) для UI. - 2026-03-10: закрыт E6.2–E6.5 (egress identity implementation): - добавлены endpoint'ы `GET /api/v1/egress/identity` и `POST /api/v1/egress/identity/refresh` в Go API; - реализованы scope-provider’ы `adguardvpn|system|transport:` с netns-aware probe для transport; - добавлены SWR/single-flight/backoff метаданные (`updated_at/stale/refresh_in_progress/last_error/next_retry_at`) и SSE `egress_identity_changed`; - добавлен GeoIP lookup (country_code/country_name) с кэшем; - desktop GUI подключён к новому API: `AdGuardVPN` и `SingBox` карточки показывают `IP + country`, флаг строится в UI из `country_code`. - 2026-03-10: закрыт E6.1 requirements freeze для общего `egress identity`: - добавлен контракт `docs/phase-e/E6_EGRESS_IDENTITY_API_CONTRACT.md`; - зафиксированы scope (`adguardvpn|transport:|system`), endpoint'ы `GET /api/v1/egress/identity` и `POST /api/v1/egress/identity/refresh`; - зафиксированы SWR/SSE поля и правило рендера флага в UI из `country_code`. - 2026-03-10: ускорен live health-probe для `SingBox`: - `transport` latency probe timeout снижен до `900ms`; - устранён lock-contention в background health refresh: долгий `backend.Health()` вынесен из-под `transportMu`, чтобы запросы `GET /transport/clients/{id}/health` не ждали второй probe под mutex. - 2026-03-09: полировка `SingBox` runtime UX и live latency: - удалён отдельный блок `Runtime details` (убран дублирующий UI-слой в `SingBox` вкладке); - `last update` перенесён в верхнюю карточку `Profile` (`id + updated timestamp`); - GUI переведён на live health для выбранного профиля через `GET /api/v1/transport/clients/{id}/health` (через `ApiClient`/`DashboardController`), поэтому `latency_ms` обновляется сразу после refresh/SSE, а не только по snapshot `/transport/clients`. - 2026-03-09: закрыт baseline по latency в transport health: - `systemd` backend теперь пишет `health.latency_ms` для активных клиентов через TCP probe endpoint'ов из runtime-конфига (`singbox outbounds`); - для `netns_enabled=true` probe выполняется через тот же netns (`ip netns exec/nsenter`), чтобы latency считался по фактическому runtime-контуру клиента; - при неуспешном probe статус не деградирует (остаётся `up`, latency остаётся пустым), чтобы избежать ложных падений lifecycle; - добавлены unit-тесты на endpoint extraction и latency sampling (`transport_backends_test.go`). - 2026-03-09: завершён перенос netns-toggle orchestration из GUI в Go-ядро: - добавлен и подключён endpoint `POST /api/v1/transport/netns/toggle` (config patch + provision + restart running clients); - `ApiClient`/`DashboardController` получили отдельный метод `transport_netns_toggle`; - `netns_debug.py` больше не выполняет локальную orchestration-цепочку и использует только backend call; - добавлены unit-тесты `applyTransportNetnsToggleLocked` (success / partial-failure / no-targets). - 2026-03-09: начата унификация SWR-механики для переиспользования: - добавлен общий модуль `selective-vpn-api/app/refresh_coordinator.go` (`freshTTL`, single-flight refresh gate, exponential backoff with cap, snapshot metadata); - `vpn_locations_cache.go` переведён на coordinator (без изменения endpoint `/api/v1/vpn/locations` и SSE `vpn_locations_changed`); - добавлены unit-тесты coordinator (`refresh_coordinator_test.go`). - 2026-03-09: SWR-схема расширена на transport health: - подключён endpoint `POST /api/v1/transport/health/refresh` (manual trigger/force queue, без блокировки UI); - `GET /api/v1/transport/clients` теперь ставит background health-refresh по eligible клиентам (`up|starting|degraded`) через shared coordinator (single-flight + backoff); - GUI подписан на SSE `transport_client_health_changed`, кнопка refresh сначала триггерит backend health-refresh (best-effort), затем обновляет карточки. - 2026-03-09: стабилизирован `SingBox + netns` runtime после регрессии: - backend переведён на adaptive `nsenter` (default) с fallback на `ip netns exec`; - netns setup/runtime используют единый exec selector (без дублирования режимов); - исправлен `nft` comment-tag для NAT-правил (совместимо с текущим синтаксисом nft); - добавлен документ ready-case: `docs/phase-d/D5_NETNS_RUNTIME_CASE.md`. - 2026-03-09: выполнен рефакторинг netns-модулей: - GUI логика переключения вынесена в `selective-vpn-gui/netns_debug.py` (state/button/toggle pipeline); - API exec-логика вынесена в `selective-vpn-api/app/transport_netns_exec.go`; - `vpn_dashboard_qt.py` и `transport_netns.go` оставлены как orchestrator/UI-обвязка. - Созданы фазы A–D в `docs/phase-{a,b,c,d}` с описанием целей, критериев и задач. - Задокументированы ожидания от Go API (routes, traffic, DNS, SmartDNS, VPN). - Подготовлен первоначальный план для проверки GUI+API разделения. - Закрыт этап E5.2 в Go API для `SingBox profiles`: - добавлены endpoint'ы `GET/POST /api/v1/transport/singbox/profiles`, `GET/PATCH/DELETE /api/v1/transport/singbox/profiles/{id}`, `GET /api/v1/transport/singbox/features`; - добавлен persistent state с ревизиями профилей (`profile_revision`) и active profile resolution; - добавлен secrets-store (`/var/lib/selective-vpn/transport/secrets/singbox/*.json`, `0600`) с masked выдачей в API и без утечки plaintext в state. - Добавлены unit-тесты E5.2 (`selective-vpn-api/app/transport_singbox_profiles_test.go`): CRUD+revision conflict, secrets persistence/masking, features endpoint. - Закрыт этап E5.3 в Go API для `SingBox profiles`: - добавлены endpoint'ы `POST /api/v1/transport/singbox/profiles/{id}/validate|render|apply|rollback` и `GET /api/v1/transport/singbox/profiles/{id}/history`; - реализован flow `validate -> render -> apply` с binary-check (`sing-box check`), persisted rendered config и runtime apply через существующий transport backend; - реализован rollback через history snapshots (включая restore предыдущего target config) + SSE события `singbox_profile_validated|rendered|applied|rollback|failed`. - Добавлены unit-тесты E5.3 (`selective-vpn-api/app/transport_singbox_profiles_flow_test.go`): render/apply/rollback/history сценарий и негативная валидация. - Усилены smoke-скрипты в `tests/`: - `api_sanity.sh` (валидация JSON ключей, проверка method guard, проверка SSE headers), - `events_stream.py` (живой SSE + триггер `trace_append`), - `vpn_login_flow.py` (start/state/action/stop), - `trace_append.sh` (append + readback через plain и json), - `transport_flow_smoke.py` (state-machine smoke: draft/validate/confirm/apply/rollback). - Добавлен общий раннер `tests/run_all.sh`. - Скрипты прогнаны локально с `API_URL=http://127.0.0.1:8080`: все 5 smoke-тестов прошли успешно. - Phase D дополнен endpoint-level матрицей (`53` legacy ручки) со статусами `ready-read/ready-write/ready-async/ready-interactive`. - По состоянию на 2026-03-07 в ядре `61` mux-маршрут (`53 legacy + 8 transport base`), плюс action-subpath `GET /api/v1/transport/clients/{id}/metrics` внутри `clients/{id}/*`. - В Phase C/Phase D добавлены архитектурные требования для переиспользования API на `web + iOS + Android` (единый `/api/v1`, TLS/token auth, mobile fallback/retry/idempotency). - Зафиксирован frontend-стек для web prototype: `Vite + React + TypeScript` (SPA). `Next.js` отложен как опция только при требованиях SSR/edge/BFF. - Запущен web prototype foundation в `selective-vpn-web/`: - App shell (sidebar/header/navigation) на `React Router`, - query-слой на `TanStack Query` с read-only snapshot (`healthz`, `status`, `vpn/status`, `vpn/login-state`), - SSE connectivity-hook для `/api/v1/events/stream`, - placeholder-страницы `VPN/Routes/DNS/Transport/Trace` для поэтапного подключения бизнес-логики. - Зафиксирован целевой набор внешних transport-клиентов: `sing-box` (клиент), `dnstt-client`, `phoenix` (подключение к `slipstream` серверу). - Подготовлен отдельный дизайн multi-client маршрутизации поверх единого PBR-ядра: per-client `fwmark/table/pref`, ownership-lock для доменов/IP, conntrack stickiness, UX предупреждения о конфликтных сценариях. - Подготовлен API-контракт `transport/*` с JSON-примерами, dry-run валидацией и apply-моделью (`docs/phase-e/E2_TRANSPORT_API_CONTRACT.md`). - В Go-ядре добавлены endpoint'ы `/api/v1/transport/*` (clients/policies/validate/apply/rollback/conflicts/capabilities + `clients/{id}/metrics`). - Реализованы `validate` и `apply` c anti-conflict guardrails, optimistic revision lock, confirm token для force override, snapshot предыдущей policy и SSE события. - Добавлены unit-тесты для transport validator/token lifecycle (`selective-vpn-api/app/transport_handlers_test.go`), `go test ./...` проходит. - Реализован allocator policy v2 для transport clients: резервные диапазоны `mark/pref`, автонормализация состояния, детерминированное восстановление и auto re-balance при коллизиях. - Реализован `POST /api/v1/transport/policies/rollback`: откат из snapshot с проверкой ревизии и валидацией перед применением. - Подготовлен документ UX предупреждений и подтверждения конфликтного apply (`docs/phase-e/E4_VALIDATE_CONFIRM_APPLY_UX.md`). - В `E1`/`E4` добавлены подпункты UX для переключения/подключения engine (`singbox|dnstt|phoenix`): `desired_engine vs active_engine`, switch states, guardrails и rollback action. - Усилен transport backend-контракт (D4.1) в Go API: - `GET /api/v1/transport/clients/{id}/metrics`, - унифицированные DTO `TransportClientLifecycleResponse`, `TransportClientHealthResponse`, `TransportClientMetricsResponse`, - runtime-поля клиента (`backend`, `allowed_actions`, counters/uptime, last_error с code/message), - lifecycle/health ответы теперь возвращают кодируемые backend-ошибки и метрики в одном формате. - Добавлен foundation backend-адаптеров (D4.2) в Go: - выбор backend по конфигу клиента (`runner=mock|systemd`, fallback в `mock`), - `provision/lifecycle/health` действия идут через единый adapter слой (без дублирования логики в UI), - добавлен `POST /api/v1/transport/clients/{id}/provision` для backend-side подготовки runner (systemd units), - для `dnstt` добавлен режим `ssh_tunnel/ssh_overlay` с orchestration двух unit (`ssh_unit` + `unit`) как единой системы. - Запущен пункт D4.2.1 (production templates): - `config.exec_start` переведён в optional override; - если override не задан, Go-ядро формирует `ExecStart` по шаблонам: - `singbox`: ` run -c `, - `dnstt`: resolver/pubkey/domain/local_addr, - `phoenix`: ` -config `; - добавлены unit-тесты `transport_backends_test.go` для manual override, template build и DNSTT required fields. - Запущен пункт D4.2.2 (systemd restart/watchdog tuning): - для transport unit добавлены настраиваемые `Restart/RestartSec`, `StartLimitIntervalSec/StartLimitBurst`, `TimeoutStartSec/TimeoutStopSec`, `WatchdogSec`; - для `dnstt + ssh overlay` добавлены отдельные `ssh_*` overrides tuning-полей для SSH unit; - добавлены unit-тесты рендера tuning-полей (`transport_backends_test.go`). - Запущен пункт D4.2.3 (unit hardening): - для systemd unit включён baseline hardening по умолчанию (`NoNewPrivileges`, `ProtectSystem`, `ProtectHome`, `RestrictSUIDSGID`, `UMask` и др.); - добавлен профиль `strict` и режим `off` через `config.hardening_profile`, а также `config.hardening_enabled`; - добавлены точечные overrides hardening-полей и `ssh_*` overrides для overlay unit; - добавлены unit-тесты hardening рендера (`baseline`, disable, `ssh_hardening_enabled=false`). - Запущен пункт D4.2.4 (`runtime_mode` architecture foundation): - в `client.config` введён `runtime_mode` (`exec|embedded|sidecar`) с нормализацией alias (`external|companion -> exec`); - `exec` использует текущую production-цепочку backend-адаптеров; - `embedded/sidecar` пока отвечают унифицированной ошибкой `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED` (без silent fallback); - `GET /api/v1/transport/capabilities` расширен матрицей `runtime_modes`. - Запущен пункт D4.2.5 (exec packaging profiles): - для template-команд добавлены профили установки бинарей: `packaging_profile=system|bundled`; - для `bundled` добавлен `bin_root` (default `/opt/selective-vpn/bin`) и switch `packaging_system_fallback`; - `require_binary=true` включает fail-fast проверку наличия бинаря (включая ручные `*_bin` override); - `GET /api/v1/transport/capabilities` расширен `packaging_profiles`. - Запущен пункт D4.2.6 (manual pinned packaging automation): - добавлены скрипты `scripts/transport-packaging/update.sh` и `rollback.sh` (checksum verify + atomic symlink switch + history rollback); - добавлен `manifest.example.json` для pinned версий/URL/checksum; - добавлен `manifest.production.json` с pinned артефактами для `singbox` (`v1.13.2`) и `phoenix` (`v1.0.1`) под `linux-amd64/linux-arm64`; для `dnstt` зафиксирован prebuilt-source (checksum pinned), но компонент по умолчанию `enabled=false` до trusted-source/signature этапа; - добавлен foundation trusted source/signature/canary policy: - `scripts/transport-packaging/source_policy.production.json` (trusted URL-prefix + signature mode policy); - `update.sh` поддерживает `--source-policy`, `signature.type=openssl-sha256`, `--rollout-stage stable|canary|any`, `--cohort-id`, `--force-rollout`/`--canary`; - для `manifest.production.json` policy подхватывается автоматически (если `source_policy.production.json` рядом); - добавлен локальный smoke `tests/transport_packaging_smoke.sh` и включён в `tests/run_all.sh`; - добавлен дополнительный smoke `tests/transport_packaging_policy_rollout.sh` (source trust + signature verify + canary gating); - default остаётся manual, но добавлен opt-in auto-update слой. - Запущен пункт D4.2.7 (auto-update opt-in): - добавлен `scripts/transport-packaging/auto_update.sh` (interval gate + jitter + flock lock + state files); - добавлены systemd-шаблоны `scripts/transport-packaging/systemd/transport-packaging-auto-update.{service,timer}` и env-шаблон; - добавлен smoke `tests/transport_packaging_auto_update.sh` и включён в `tests/run_all.sh`. - Запущен пункт D4.2.8a (singbox backend e2e): - добавлен `tests/transport_singbox_e2e.py`: - успешный lifecycle на `runner=mock` (`provision/start/health/restart/stop/metrics`); - negative guard `runtime_mode=embedded` -> `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED`; - fail-fast `require_binary=true` + missing `singbox_bin` -> `TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED`; - тест подключён в `tests/run_all.sh`. - Запущен пункт D4.2.8b (dnstt backend e2e): - добавлен `tests/transport_dnstt_e2e.py`: - успешный lifecycle на `runner=mock`; - guard для `ssh_overlay` конфигурации (`ssh_host` обязателен, `ssh_unit` должен быть валидным) -> `TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED`; - guard валидации шаблона DNSTT при неполном config -> `TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED`; - тест подключён в `tests/run_all.sh`. - Запущен пункт D4.2.8c (phoenix backend e2e): - добавлен `tests/transport_phoenix_e2e.py`: - успешный lifecycle на `runner=mock` (`provision/start/health/restart/stop/metrics`); - guard `runtime_mode=embedded` -> `TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED`; - fail-fast `require_binary=true` + missing `phoenix_bin` -> `TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED`; - тест подключён в `tests/run_all.sh`. - Верификация D4.2.8 на live backend (2026-03-07): - `selective-vpn-api` пересобран из текущего кода и перезапущен через systemd; - `./tests/run_all.sh` выполнен полностью без `SKIP` на `transport_singbox_e2e`, `transport_dnstt_e2e`, `transport_phoenix_e2e`; - подтверждён рабочий контракт `provision/start/health/restart/stop/metrics` и negative guards по каждому transport-клиенту. - Запущен пункт D4.2.9 (операционные runbook'и): - добавлен `scripts/transport_runbook.py` для воспроизводимого lifecycle-flow через API (`capabilities/create/provision/start/health/metrics/restart/stop/delete`); - добавлен smoke `tests/transport_runbook_cli_smoke.sh` (mock singbox lifecycle через runbook helper); - smoke подключён в `tests/run_all.sh`. - Запущен пункт D4.2.10 (real-systemd e2e + cleanup): - в backend добавлен cleanup для `runner=systemd` при `DELETE /api/v1/transport/clients/{id}`: - удаляются только unit-файлы с ownership-marker `SVPN_TRANSPORT_ID=`; - выполняется `stop/disable`, затем `daemon-reload` + `reset-failed` (best-effort, с предупреждением в `message`, не блокируя delete); - добавлен тест `tests/transport_systemd_real_e2e.py`: - lifecycle для `singbox`, `dnstt(+ssh_unit)`, `phoenix` на реальном `systemd` backend (`runner=systemd`, `exec_start=/usr/bin/sleep`); - проверка, что unit-файлы создаются с ownership-marker и удаляются после delete cleanup; - тест подключён в `tests/run_all.sh`. - Запущен пункт D4.2.11 (production-like transport e2e): - добавлен тест `tests/transport_production_like_e2e.py`; - проверяется lifecycle `provision/start/health/metrics/restart/stop/delete` для `singbox|dnstt(+ssh)|phoenix` на `runner=systemd`; - вместо manual `exec_start` проверяются template-команды и `packaging_profile=bundled` с `bin_root`/`require_binary=true`; - проверяется, что в unit-файлах присутствуют ожидаемые template-аргументы (`run -c`, `-config`, `-doh`/`domain`/`local_addr`) и ownership marker; - тест подключён в `tests/run_all.sh`. - Запущен пункт D4.2.12 (recovery runbook): - добавлен `scripts/transport_recovery_runbook.py`: - проверяет health клиента, делает до `N` попыток `restart`, - при необходимости выполняет fallback `provision -> start`, - при нерешённой деградации пишет diagnostics (`metrics`, `health`, `client_card`, шаги recovery) и возвращает `rc=2`; - добавлен smoke `tests/transport_recovery_runbook_smoke.sh`: - case1: успешное восстановление (`mock+exec`) -> `rc=0`, - case2: ожидаемый fail-path (`runtime_mode=embedded`) -> `rc=2` + diagnostics file; - smoke подключён в `tests/run_all.sh`. - Запущен пункт D4.3 (platform compatibility matrix): - добавлен артефакт `docs/phase-d/D4_PLATFORM_COMPATIBILITY_MATRIX.md` с матрицей `web + iOS + Android`; - зафиксированы ограничения runtime (`exec=true`, `embedded/sidecar=false`) и правило backend-only orchestration для `singbox/dnstt/phoenix`; - добавлен smoke `tests/transport_platform_compatibility_smoke.py` (capabilities + policy-contract checks), тест подключён в `tests/run_all.sh`. - Верификация D4.3 на live backend (2026-03-07): - `env API_URL=http://127.0.0.1:8080 ./tests/transport_platform_compatibility_smoke.py` -> `passed`; - полный `./tests/run_all.sh` выполнен успешно с включённым `transport_platform_compatibility`. - Запущен пункт D4.2.13 (singbox bootstrap-bypass): - добавлен backend-модуль `app/transport_bootstrap_bypass.go`; - для `runner=systemd` в `start/restart/stop` добавлена синхронизация bypass-маршрутов endpoint'ов transport-клиента; - `singbox` endpoint-hosts извлекаются из `client.config` и `singbox` config-файла (`outbounds[*].server/address/host`); - перед `start/restart` backend вычисляет `main` default-route и ставит `ip -4 route replace /32 table agvpn ...` (избежание bootstrap через `tun0`); - на `stop` backend удаляет привязанные bypass-маршруты клиента; - добавлен strict-режим `config.bootstrap_bypass_strict=true` (ошибка `TRANSPORT_BACKEND_BOOTSTRAP_BYPASS_FAILED` при невозможности применить bypass); - добавлены unit-тесты `transport_bootstrap_bypass_test.go`, `go test ./...` проходит. - Запущен пункт D4.2.14 (netns test contour): - добавлен backend-модуль `app/transport_netns.go`: - `netns` setup перед `start/restart` (veth pair, `ip_forward`, `nft` masquerade), - optional cleanup на `stop` (`config.netns_auto_cleanup=true`), - cleanup при backend `DELETE client` через `Cleanup()` (best-effort); - `Provision()` для systemd поддерживает запуск transport внутри namespace через adaptive exec (`nsenter` default, fallback `ip netns exec`) при `config.netns_enabled=true`; - для безопасного fail-fast добавлен strict-режим `config.netns_setup_strict=true` (`TRANSPORT_BACKEND_NETNS_SETUP_FAILED`); - добавлены unit-тесты `transport_netns_test.go`, `go test ./...` проходит. - Запущен пункт D4.2.15 (sing-box DNS migration): - добавлен модуль `app/transport_singbox_dns_migration.go` (best-effort миграция legacy DNS в typed format); - migration trigger встроен в `Provision()` для `singbox`: - `dns.servers[*].address` -> `dns.servers[*].type/server/server_port`, - `address_resolver/address_strategy` -> `domain_resolver/domain_strategy`, - удаление `detour=direct` для DNS servers (невалидно для новых версий), - backup оригинала: `.legacy-dns.bak`; - добавлены config-флаги: - `singbox_dns_migrate_legacy` (default `true`), - `singbox_dns_migrate_strict` (fail-fast с `TRANSPORT_BACKEND_SINGBOX_DNS_MIGRATE_FAILED`); - добавлены unit-тесты `transport_singbox_dns_migration_test.go`; - проверено на live client `sg-realnetns`: warning `legacy DNS servers is deprecated` исчез, transport после миграции поднимается и трафик через SOCKS проходит. - В `selective-vpn-gui` добавлены transport методы в `api_client.py` и transition-логика в `dashboard_controller.py`: - `draft -> validate -> (validated|risky) -> confirm -> apply`, - обработка `POLICY_REVISION_MISMATCH` с возвратом в `draft`, - общий foundation для последующего web/iOS/Android UX. - Запущен пункт E4.3.1 (GUI engine foundation): - в `selective-vpn-gui` на вкладке `AdGuardVPN` добавлен блок `Transport engine`; - реализованы действия `Prepare/Connect/Disconnect/Restart` для выбранного transport-клиента через Go API; - Запущен пункт E5.4.4 (GUI protocol editor flow): - `Save draft` теперь сохраняет VLESS raw profile через `POST/PATCH /api/v1/transport/singbox/profiles/*` (через controller/api_client); - при смене transport engine в `SingBox` вкладке editor автоматически подгружает профиль из Go API и отключается при `API unavailable/нет выбранного клиента`; - `Preview/Validate/Apply` перед вызовом action теперь автоматически синхронизируют текущий draft из формы (guardrail: валидация обязательных client-полей до API action). - добавлено состояние выбранного engine (status/iface/table/latency/last_error) и авто-обновление по transport SSE-событиям; - расширен client/controller слой: `ApiClient.transport_client_action()` + `DashboardController.transport_client_action()`. - Запущен пункт E5.4.5 (dashboard cards + context menu): - карточки `Connection profiles` переведены на widget-плитки со state-driven стилями; - активный runtime-профиль (`status=up`) подсвечивается зелёным; - добавлено правокликовое меню по карточке: `Run`, `Edit`, `Delete`; - `Edit` открывает отдельный modal-диалог и использует тот же VLESS form-editor (inline-редактор скрыт из основной вкладки); - для `Delete` добавлен GUI/API flow с поддержкой `force=true` при policy-ссылках. - Запущен пункт E5.4.6 (editor usability + profile creation): - `security/transport` в VLESS editor больше не сбрасывают введённые значения при переключении режима; - `Flow` переведён в editable режим: preset `xtls-rprx-vision` + возможность custom/raw значения; - в блок `Connection profiles` добавлена кнопка `Create connection` с режимами: - `Create from clipboard` (парсинг `vless://` из буфера), - `Create from link...` (вставка URL), - `Create manual` (пустой профиль с последующим edit); - для создания профиля добавлен GUI/API create-flow transport-клиента (`POST /api/v1/transport/clients`) и auto-seed editor + optional draft save. - Запущен пункт E5.4.7 (unified protocols import): - link-import больше не завязан на VLESS: добавлен общий dispatcher по схеме URL; - реализованы парсеры и raw-profile build для `vless/trojan/ss/hysteria2(hy2)/tuic`; - добавлен общий набор helper-функций (query/tls/transport/raw route/inbound builder), чтобы следующие протоколы подключались без повторения кода; - для non-VLESS профилей сохраняется raw-конфиг и доступен lifecycle (`Run/Validate/Apply`) через тот же pipeline. - Запущен пункт E5.4.8 (multi-protocol form editor): - `Protocol` в editor переключается между `vless/trojan/shadowsocks/hysteria2/tuic`; - форма стала модульной: protocol-specific поля (password/ss_method/hy2_obfs/tuic options) показываются/скрываются по выбранному протоколу; - `Save draft` теперь пишет `protocol=` и генерирует соответствующий outbound raw-конфиг; - сохранены guardrails по transport/security и обязательным полям каждого протокола. - Запущен пункт E5.4.9 (wireguard in common editor): - добавлен protocol `wireguard` в тот же form-editor, без отдельной ветки UI; - добавлены поля `private_key`, `peer_public_key`, `pre_shared_key`, `local_address`, `reserved`, `mtu`; - включена валидация обязательных WG полей и сохранение в raw outbound `type=wireguard`; - расширен universal link-import parser: поддержка `wireguard://` (и alias `wg://`) в `Create connection`. - Запущен пункт E4.3.2 (SingBox switch pipeline): - блок engine вынесен в отдельную вкладку `SingBox` (отдельно от `AdGuardVPN`); - `Connect/Switch` теперь выполняет policy pipeline `validate -> confirm -> apply` и только потом `start`; - при блокирующих конфликтах UI показывает confirm-диалог с force apply; - добавлена кнопка `Rollback policy` (через `POST /api/v1/transport/policies/rollback`). - Запущен пункт E5.1 (requirements freeze для протоколов SingBox): - добавлен документ `docs/phase-e/E5_SINGBOX_PROTOCOLS_REQUIREMENTS.md`; - зафиксированы архитектурные границы (`engine` vs `policy` vs `protocol profile`); - зафиксированы требования к новой группе API `/api/v1/transport/singbox/profiles/*` (`CRUD/validate/render/apply/rollback/history/features`); - зафиксированы требования к `typed + raw` режимам, secrets storage, versioning и SSE событиям. - Запущен пункт E5.1.2 (protocol field inventory): - добавлен шаблон `docs/phase-e/E5_SINGBOX_PROTOCOL_MATRIX_TEMPLATE.md`; - добавлена заполненная матрица `docs/phase-e/E5_SINGBOX_PROTOCOLS_MATRIX.md` (общие блоки + 6 протоколов + guardrails + MVP/Advanced/Raw-only split); - добавлен machine-readable пример `docs/phase-e/E5_SINGBOX_PROTOCOLS_MANIFEST.example.json` для последующей генерации UI-форм. - Запущен пункт E5.1.3 (client form baseline): - добавлен документ `docs/phase-e/E5_SINGBOX_CLIENT_FORM_MATRIX.md`; - зафиксированы UI-блоки клиентской формы (`Profile`, `Server/Auth`, `Transport`, `Security`, `Sniffing`, `Advanced Dial`); - явно исключены server-only поля (billing/traffic/expiry/subscription) и добавлены guardrails для `VLESS+Reality`. - Запущен пункт E5.4.1 (desktop dashboard foundation): - добавлен документ `docs/phase-e/E5_2_SINGBOX_DESKTOP_DASHBOARD_SPEC.md`; - вкладка `SingBox` перестроена на 3 зоны: runtime card, profile settings, global defaults; - внедрён `card-based dashboard`: верхние metric cards + grid profile cards (с выбором карточки); - включён compact UX: настройки и activity log открываются кнопками (по умолчанию скрыты); - добавлены `Use global` override-переключатели, effective summary и локальное сохранение настроек; - runtime pipeline `Prepare/Connect-Switch/Disconnect/Restart/Rollback` сохранён без изменения API-контракта. - Запущен пункт E5.4.2 (GUI wiring profile actions): - в `selective-vpn-gui/api_client.py` добавлены typed-модели и методы для `POST /api/v1/transport/singbox/profiles/{id}/validate|apply`; - в `selective-vpn-gui/dashboard_controller.py` добавлены `singbox_profile_validate_action()` и `singbox_profile_apply_action()` с унифицированным `ActionView`; - в `selective-vpn-gui/vpn_dashboard_qt.py` заглушки `Validate profile`/`Apply profile` заменены на реальные вызовы API с логированием в activity-log и авто-refresh runtime state. - Запущен пункт E5.4.3 (profile flow completeness + UX hardening): - в `selective-vpn-gui/api_client.py` добавлены методы `profiles list/get/create/patch/render/rollback/history` и соответствующие typed DTO; - в `selective-vpn-gui/dashboard_controller.py` добавлены: - `singbox_profile_render_preview_action()`, - `singbox_profile_rollback_action()`, - `singbox_profile_history_lines()`, - `singbox_profile_ensure_linked()` с auto-create профиля из `config_path` (raw mode) и auto-link `meta.client_id`; - в `selective-vpn-gui/vpn_dashboard_qt.py` добавлены кнопки `Preview render`, `Rollback profile`, `History`; - добавлена авто-синхронизация `engine -> profile` при выборе/refresh engine (`_sync_selected_singbox_profile_link`); - выполнен лёгкий рефакторинг profile handlers (`_selected_singbox_profile_context`, `_run_singbox_profile_action`) без смены backend-контракта. - Запущен пункт B3 (resolver diff + improvement plan): - добавлен документ `docs/phase-b/B2_RESOLVER_DIFF_AND_IMPROVEMENT_PLAN.md`; - зафиксированы границы ролей `system resolver` (authoritative для PBR) и `singbox DNS` (transport-level resolver); - зафиксирован приоритетный backlog улучшений resolver (`R1-R4`: надёжность, качество резолва, observability API/SSE, multi-client ownership safety). - Запущен пункт F1.1 (план модульности): - добавлен документ `docs/phase-f/F1_REFACTOR_MODULARITY_PLAN.md`; - зафиксированы целевые разрезы для `vpn_dashboard_qt.py`, `api_client.py`, `dashboard_controller.py`, `transport_handlers.go`; - зафиксирован безопасный порядок реализации и DoD без изменения API-контракта. - Текущий статус: transport backend/API foundation и базовый desktop UI-flow включены (`SingBox` tab: lifecycle actions + switch pipeline + rollback). - Зафиксирован план по VPN локациям: - без большой кнопки `Refresh`/`Apply`, но с лёгким icon-trigger refresh (фоновый, неблокирующий), - в будущем без кнопки `Apply location` (выбор в списке сразу инициирует connect/reconnect), - список грузится асинхронно, UI не блокируется при недоступном CLI/большом latency, - используется кеш последнего успешного списка + SWR (stale-while-revalidate) + backoff/retry + single-flight lock. - Диагностирован кейс `eu.posthog.com` (страница "region unavailable" в браузере при включенном wildcard): - root cause: `smartdns` runtime был с `nftset-timeout yes`, а `agvpn_dyn4` создан без `timeout`-флага (`flags interval`), из-за чего runtime-добавление IP не происходило; - applied fix: `nftset-timeout no` в `smartdns.conf`; - verification: после `smartdns-local` restart + DNS query через `127.0.0.1#6053` все актуальные `A`-IP `eu.posthog.com` появились в `inet/agvpn/agvpn_dyn4`. - Диагностирован второй корень проблемы для `selective`: - `routes/update` перезаписывал `agvpn_dyn4` только списком из resolver cache, из-за чего runtime-IP (SmartDNS nftset) периодически терялись; - applied fix в Go (`routes_update.go`): merge `resolver wildcard IPs` + `existing agvpn_dyn4` при `runtime_nftset=true`; - verification: после `smartdns` query и `POST /api/v1/routes/update` PostHog IP (`3.121.142.0`, `3.67.52.82`, `18.158.106.188`, `63.183.90.15`, `63.177.143.4`, `63.182.85.19`) не исчезают из `agvpn_dyn4`. - По запросу добавлен временный static fallback для PostHog в `/etc/selective-vpn/static-ips.txt`: - `3.121.142.0/24`, `3.67.52.0/24`, `18.158.106.0/24`, `63.177.143.0/24`, `63.182.85.0/24`, `63.183.90.0/24`; - verification: после `routes/update` подсети присутствуют в `inet/agvpn/agvpn4`. - Расширено покрытие PostHog-хостов для selective: - в wildcard state добавлены `app-static.eu.posthog.com` и `internal-j.posthog.com`; - добавлен временный static fallback для `internal-j.posthog.com`: `3.88.247.0/24`, `3.95.129.0/24`, `54.90.36.0/24`; - verification: по packet capture (`tcpdump`) HTTPS к `internal-j.posthog.com` уходит через `tun0` (маркировка срабатывает). - Финальная проверка: пользователь подтвердил, что `eu.posthog.com` в режиме `selective` открывается и работает штатно. - Реализован backend cache/SWR для `/api/v1/vpn/locations`: - мгновенный ответ из кеша (`/var/lib/selective-vpn/vpn-locations-cache.json`), - фоновый refresh `list-locations` с single-flight lock, - метаданные ответа: `updated_at`, `stale`, `refresh_in_progress`, `last_error`, `next_retry_at`, - backoff на ошибках, SSE-событие `vpn_locations_changed`. - Реализована стабилизация GUI по локациям: - загрузка списка через отдельный `QThread` (без блокировки главного потока), - убрана кнопка `Apply & restart loop`, включён auto-apply при выборе локации в `QComboBox`, - добавлена status/meta строка для состояния кеша/обновления/ошибок. - Реализован V2 "умный поиск" без отдельного поля ввода: - typed-buffer в списке локаций с live-фильтрацией и сортировкой по релевантности, - матч по началу ISO/строки/слова, backspace уменьшает фильтр, - таймаут-сброс буфера. - Исправлена точность применения VPN-локации: - вместо `ISO` в `connect -l` теперь передаётся точный `target` локации (например `United States Los Angeles`), - устранён кейс, когда в UI выбран один город, а autoloop подключается к другому дефолтному городу страны. - Усилен safety flow для `set location` (L1 hardening): - backend теперь резолвит пользовательский выбор в безопасный `connect`-аргумент (`city` или `ISO`) по каталогу локаций, - при нераспознанной локации возвращается `422` без рестарта autoloop (текущий туннель сохраняется), - в GUI при apply передаётся `target + iso + label`, чтобы повысить точность матчинга и убрать ложные реконнекты. - Добавлены проверки для блока локаций: - `tests/vpn_locations_swr.sh`, - unit-тесты parser-а `selective-vpn-api/app/vpn_locations_cache_test.go`. - Smoke-набор `tests/run_all.sh` обновлён и прогнан успешно (включая новый `vpn_locations_swr`). - В GUI локаций `Sort/Refresh` оставлены снаружи списка (рядом с `Location`), popup содержит только локации. - `Refresh` отправляет trigger `GET /api/v1/vpn/locations?refresh=1` в фоне через `LocationsThread`, UI не блокируется. ## Следующие шаги - M1 (priority): закрыть backend multi-interface foundation: - interface orchestrator (`E3.3`), ownership registry (`E3.4`), anti-mixing guard (`E3.5`), transaction pipeline (`E3.6`); - критерий: минимум 2 engine работают одновременно без пересечения table/mark/pref и без ложного egress mixing. - M2 (priority): довести runtime наблюдаемость (`E6.6`) для всех engine в едином контракте ядра. - M3 (priority): после backend foundation включить GUI-флоу выбора/переключения engine-профилей поверх нового orchestration-слоя (тонкий UI, без бизнес-логики в GUI). - E5.4: реализовать GUI-блок протоколов в `SingBox` вкладке (list/editor/validate/preview/apply/rollback). - B3.1: реализовать resolver status snapshot API + SSE (`resolver_status_changed`, `resolver_refresh_completed`). - B3.2: реализовать adaptive upstream scoring + negative cache TTL policy. - B3.3: внедрить `domain_owner_map` и conflict guard для multi-client DNS ownership. - D4.2 (supporting track): поддерживать backend-ready состояние `dnstt-client` и `phoenix` без UI-расширения в текущем этапе. - P1.1: спроектировать `service profiles` (например, `PostHog`) как пресеты доменов/wildcard/static-fallback/policy. - P1.2: добавить API для профилей (`list/apply/remove/export`) и idempotent apply в Go-ядре. - P1.3: добавить в GUI чекбоксы/тогглы профилей с применением в 1 клик и rollback. - P1.4: подготовить переносимый формат профилей для web + iOS + Android (единый JSON schema). - V2.2: собрать пользовательский отчёт по UX поиска/автоприменения локаций в desktop (latency, false matches, edge-cases). - V2.3: при подтверждении UX перенести одинаковую модель локаций (`cache/SWR + typed-buffer + auto-apply`) в web/mobile клиенты. - Доработать Phase B (описание контроллеров, зависимости, условия запуска). - Завершить Phase D (матрица endpoint со статусом `web-ready` и явные блокеры для веб-прототипа). - Формализовать реализацию D3.1-D3.4 (gateway/auth, web старт, mobile transport profile, запуск iOS/Android на общем API). - E4.4: перенести foundation state-machine в web prototype UI (`React + TypeScript` на `Vite`) без изменения API-контракта Go. - E4.5: добавить настройки видимости protocol tabs (включать/выключать вкладки `SingBox/DNSTT/Phoenix`) через UI settings/profile. - F1.4: выполнить декомпозицию `selective-vpn-gui/vpn_dashboard_qt.py` на tab/modules и вынести event/locations сервисы. - F1.11: вынести переиспользуемую transport-логику в подпакеты (`app/transport/*`) для частей без циклических зависимостей (через facade+deps). - F1.11.r1: продолжить resolver-декомпозицию в отдельной папке `app/resolver/*` через bridge-слой (`domain cache`, `adaptive live-batch`, `io/json helpers`) без смены внешнего поведения. - F2.1 (post-refactor): перейти на `singbox@.service` как целевой production-runtime (instance-per-profile + без лавины unit-файлов). - F2.2 (post-refactor): сделать управляемую миграцию/cleanup старых `singbox-*.service` и зафиксировать runbook для деплоя/отката. ## Дальний backlog (после завершения текущего приложения) - L1.1: `AmneziaWG` интеграция как отдельный transport executor/backend kind (`amneziawg`) через единый Go control-plane API. - L1.2: поддержка `AmneziaWG` в GUI как отдельный engine/profile workflow (без смешивания с текущим `wireguard`/`singbox` этапом). - L1.3: обновление capability-матрицы, runbook/e2e и packaging-профилей для `amneziawg` после полного закрытия приоритетных задач desktop (`SingBox` + core stability).