Files
elmprodvpn/docs/EXECUTION_TRACKER.md

188 KiB
Raw Permalink Blame History

Execution Tracker

Дата обновления: 2026-03-15 Владелец: Engineering

Статусы фаз

  • 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-ядре.
    1. Затем переиспользуем backend-контракт в desktop GUI (без дублирования бизнес-логики).
    1. Веб-прототип (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:<id>|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.godns_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@<profile_id>.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_<owner_scope>_<selector>;
    • для длинных 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@<client_id>.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@<id>.service переведён на модель template + per-instance drop-in;
    • добавлена генерация/обновление singbox@.service (managed template) и drop-in singbox@<id>.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@<id>.service (ok).
  • 2026-03-13: расширены smoke/e2e проверки под instance-model:
    • tests/transport_systemd_real_e2e.py обновлён: SingBox проверяется через default instance unit (singbox@<client_id>.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/<pid>/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 <unit> перед 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:<id> и 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:<id> в netns:
    • для SingBox с локальным socks inbound probe теперь сначала идёт через socks5h://127.0.0.1:<port> (реальный tunnel egress), а direct-netns probe оставлен только как fallback;
    • устранён ложный кейс, когда transport:sg-realnetns показывал системный/AdGuard IP вместо IP tunnel-профиля.
  • 2026-03-10: дополнили фиксы SingBox + netns и старт нового профиля:
    • для transport:<id> при 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:<id>:
    • netns-probe больше не зависит от DNS внутри namespace (curl --resolve с host-side резолвом endpoint хоста);
    • добавлен fallback probe через локальный SingBox SOCKS inbound (socks5h://127.0.0.1:<port>) для изолированных netns;
    • ограничена длительность netns-probe (по умолчанию SVPN_EGRESS_NETNS_MAX_ENDPOINTS=1) и уплотнены сообщения ошибок (last_error) для UI.
  • 2026-03-10: закрыт E6.2E6.5 (egress identity implementation):
    • добавлены endpoint'ы GET /api/v1/egress/identity и POST /api/v1/egress/identity/refresh в Go API;
    • реализованы scope-providerы adguardvpn|system|transport:<id> с 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:<id>|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-обвязка.
  • Созданы фазы AD в 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: <bin> run -c <config_path>,
      • dnstt: resolver/pubkey/domain/local_addr,
      • phoenix: <bin> -config <config_path>;
    • добавлены 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=<client_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 <endpoint>/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 оригинала: <config>.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=<selected> и генерирует соответствующий 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).