ZapretKVN-android/docs/IMPLEMENTATION_PLAN.md
2026-07-24 23:46:40 +03:00

637 lines
76 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Zapret KVN Android — план реализации
> Рабочий TODO-лист проекта. Отмечать выполненное заменой `[ ]` на `[x]` только после прохождения указанного gate. Архитектурные решения здесь не переопределяются: источник истины — [главная архитектура](ARCHITECTURE.md), [DNS ADR](DNS_ARCHITECTURE.md), [Routing ADR](ROUTING_ARCHITECTURE.md), [VPN Hiding ADR](VPN_HIDING_ARCHITECTURE.md) и [политика форматов импорта](IMPORT_FORMATS.md).
| Поле | Значение |
|---|---|
| Статус | Публичный MVP завершён и выпущен как stable `v0.2.4`; расширенная физическая матрица продолжается по пользовательским отчётам |
| Текущий этап | Пострелизное тестирование и исправление подтверждённых проблем |
| Минимальная ОС | Android 8.0, API 26 |
| Устройства MVP | Телефоны |
| Ядро | `sing-box-extended` `v1.13.14-extended-2.5.2` |
| Commit ядра | `ff11f007ec798136a5de258f947a4f34011a37ea` |
| Модули | Только `app` |
## Как вести план
- Выполнять этапы по порядку; внутри этапа допустимы небольшие независимые задачи.
- Не отмечать gate по факту компиляции: требуются перечисленные тесты и сохранённый результат.
- Баг, блокирующий gate, добавлять прямо под соответствующим этапом как `- [ ] BUG-...`.
- Решение, меняющее TUN, DNS, routing, хранение или источник истины, сначала вносить в соответствующий ADR.
- Не добавлять новый слой, dependency, service, process или фоновую задачу «на будущее».
- После этапа обновлять строку «Текущий этап» и раздел «Текущее состояние» внизу.
## Зафиксировано до начала кода
- [x] Принята архитектура с одним product-модулем `app` и узкими направленными libraries (`network-bootstrap`, import), без Room, Hilt, WebView и WorkManager.
- [x] Разделены Android per-app scope и sing-box destination routing.
- [x] Зафиксированы один process, один `VpnService`, один TUN и один libbox instance.
- [x] Зафиксирована DNS-архитектура без FakeIP: Auto `профиль → Android → DoH`, а `fallback/parallel` используется только внутри защищённого этапа.
- [x] Зафиксирована маршрутизация `proxy` / `direct` / `reject` через настоящий JSON.
- [x] Зафиксирован точный commit ядра.
- [x] Подготовлены и проверены 7/7 эталонных JSON, включая Android WireGuard ClientBind.
- [x] Пройден `go test ./dns/... ./route/rule ./experimental/libbox`.
- [x] До первой публичной сборки выбрать окончательные `applicationId`, namespace и имя signing key.
- [x] Для production rule-set зафиксированы источники, лицензии, commit и SHA-256 списков.
## Definition of Done MVP
- [x] Публичный MVP принят владельцем 24 июля 2026 года после установки и проверки
production-signed stable `v0.2.4` на реальном устройстве.
- [x] Новый пользователь может установить APK, импортировать VLESS/другой поддержанный
URI или JSON, выбрать приложения и подключиться без ручного редактирования JSON.
- [x] Автоматизированы проверки per-app bypass, `proxy/direct/reject`, DNS, fail-close,
JSON round-trip/backup, lifecycle, redaction и updater.
- [x] Пройдены обязательные для публикации fixture, unit, instrumented, manifest,
signing, revision и release-аудиты.
- [x] Release APK содержит ядро нужного commit, подписан постоянным ключом и опубликован с SHA-256.
Расширенная проверка редких сочетаний устройств, операторов, IPv6/NAT64, captive
portal и физической энергии не блокирует статус MVP. Она продолжается после релиза
по пользовательским отчётам и не считается выполненной без соответствующего evidence.
## Этап 0 — каркас и воспроизводимая сборка
Цель: минимальный устанавливаемый APK и CI, доказывающий происхождение ядра.
### Репозиторий и Android
- [x] `I0-01` Инициализировать Git-репозиторий и добавить `.gitignore`, `docs/LICENSE`, `docs/README.md`.
- [x] `I0-02` Создать Gradle wrapper и один модуль `app`; Kotlin, Compose и Material 3, minSdk 26.
- [x] `I0-03` Зафиксировать версии AGP/Kotlin/JDK; не добавлять convention plugins и version catalog без реальной необходимости.
- [x] `I0-04` Создать `ZapretApplication`, ручной `AppContainer`, `MainActivity` и четыре нижние вкладки-заглушки.
- [x] `I0-05` Реализовать системную светлую/тёмную тему, Dynamic Color на API 31+ и встроенную палитру на API 26–30.
- [x] `I0-06` Добавить базовый manifest с минимальными разрешениями; не запрашивать `WAKE_LOCK` и `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`.
- [x] `I0-07` Добавить debug/release build types, R8 и resource shrinking для release.
### Ядро и CI
- [x] `I0-08` Добавить скрипт сборки libbox AAR и audit CLI из полного pinned SHA.
- [x] `I0-09` Проверять, что HEAD исходника и embedded revision CLI равны pinned SHA.
- [x] `I0-10` Первоначально выбрать для MVP `arm64-v8a`; решение заменено `I7-10` после проверки размера универсального тестового APK.
- [x] `I0-11` В CI запускать `sing-box check` для всех файлов `testdata/**/*.json`.
- [x] `I0-12` В CI запускать pinned Go tests и Android unit tests.
- [x] `I0-13` Собирать debug APK в GitHub Actions и сохранять core version/revision рядом с artifact.
- [x] `I0-14` Добавить `docs/LICENSE`/`docs/NOTICE` для приложения, sing-box-extended и включённых библиотек.
### Gate 0
- [x] Чистый checkout одной командой собирает debug APK.
- [x] APK устанавливается и открывает четыре вкладки на API 26 и современной версии Android.
- [x] CI подтверждает exact core revision и принимает 7/7 fixtures.
- [x] В release manifest нет запрещённых разрешений, второго process или второго VPN service.
## Этап 1 — профили и настоящий JSON
Цель: импортировать, безопасно хранить, редактировать и проверять JSON без VPN.
- [x] `I1-01` Создать пакеты `profiles/`, `config/`, `importer/` внутри `app`, без отдельных Gradle-модулей.
- [x] `I1-02` Реализовать `ProfileStore`: `files/profiles/index.json`, `<id>.json`, одна `<id>.json.bak` через `AtomicFile`.
- [x] `I1-03` Хранить в index только UI metadata; DNS, routes и outbounds остаются исключительно в JSON профиля.
- [x] `I1-04` Реализовать атомарные create/read/update/delete/restore операции и очистку orphan temp-файлов.
- [x] `I1-05` Реализовать импорт raw JSON из системного file picker и буфера после явного нажатия.
- [x] `I1-06` Вызывать libbox `CheckConfig()` до сохранения изменённого профиля и показывать понятную ошибку.
- [x] `I1-07` Реализовать JSON-tree editor на `kotlinx.serialization.json`, сохраняющий неизвестные поля.
- [x] `I1-08` Сделать простой raw editor: моноширинный текст, поиск, format, validate, отмена несохранённых изменений.
- [x] `I1-09` Добавить список профилей, выбор активного профиля, переименование, удаление с подтверждением и восстановление backup.
- [x] `I1-10` Хранить тему, активный профиль и UI-настройки в DataStore; не копировать туда сетевую конфигурацию.
- [x] `I1-11` Реализовать `ManagedProfileFactory`: маленькие base/protocol/selector builders вместо набора полноразмерных JSON-шаблонов.
- [x] `I1-12` Для одиночной ссылки создавать один server outbound и selector `zapret-proxy`; для subscription — несколько server outbounds в том же selector.
- [x] `I1-13` Генерировать уникальные стабильные server tags без credentials; при совпадении имён добавлять детерминированный suffix.
- [x] `I1-14` Сохранять выбранный сервер только в `selector.default` настоящего JSON; не использовать DataStore или `experimental.cache_file`.
- [x] `I1-15` Анализировать raw JSON: показывать существующие selector-группы без скрытой нормализации; managed `zapret-proxy` создавать только после явного выбора пользователя.
- [x] `I1-16` Хранить профили только в app-private storage и выключить Android Auto Backup для файлов с credentials.
### Тесты и Gate 1
- [x] Unit: JSON round-trip и GUI edit сохраняют неизвестные extended-поля.
- [x] Unit: сбой записи оставляет старый профиль читаемым; backup восстанавливается.
- [x] Unit: malformed JSON не изменяет существующий профиль.
- [x] Unit: index не содержит DNS/outbound/route objects или credentials.
- [x] Unit + native instrumented: managed single/multi-server builders проходят `CheckConfig()` и имеют валидные ссылки selector → server tags.
- [x] Unit: смена `selector.default` не изменяет остальные outbounds, DNS, routes и unknown fields.
- [x] Instrumented: file picker и clipboard import работают на API 26 и современной ОС.
- [x] Gate: пользователь может импортировать JSON, проверить, изменить, перезапустить приложение и получить тот же профиль.
## Этап 2 — минимальный рабочий VPN
Цель: один профиль и выбранное приложение реально проходят через один TUN/libbox.
### Per-app scope
- [x] `I2-01` Реализовать глобальный include allowlist в DataStore.
- [x] `I2-02` Создать полноэкранный picker приложений: поиск, установленные пользовательские приложения, скрытые системные.
- [x] `I2-03` Добавить встроенные suggestions для Instagram, X/Twitter, YouTube, Telegram Stable/Beta/Direct, Telegram X, ZaStoGram и распространённых форков, WhatsApp, Discord, Signal, ChatGPT, Claude, Gemini, Perplexity, Copilot, DeepSeek, Grok, Suno, Spotify, Notion, Chromium/Ultimatum и популярных браузеров; без TikTok. Только при первой инициализации выбирать все установленные recommendations, включая отключённые предустановленные Gemini/YouTube/YouTube Music, но не обычные системные службы. Установленные Android browser handlers и обработчики `tg://` также выбирать без бесконечного списка package вручную. После инициализации обновления никогда автоматически не меняют include/exclude-список пользователя.
- [x] `I2-04` Не запускать VPN при пустой effective allowlist или ошибке `addAllowedApplication()`; исчезнувшие package пропускать до Builder, писать в bounded diagnostic log и продолжать при наличии хотя бы одного установленного приложения. Отключённый, но установленный package разрешать выбрать и передавать Builder: после включения приложения сохранённая allowlist действует без повторной настройки.
- [x] `I2-05` Внутренне включать package Zapret KVN для health-check, не показывая его как пользовательский выбор.
### Service и libbox
- [x] `I2-06` Создать один foreground `VpnService`, `VpnController` и закрытый набор состояний подключения.
- [x] `I2-07` Реализовать один service-lock, generation token и идемпотентный `stop()`.
- [x] `I2-08` Интегрировать libbox в том же Android process; вызвать `Libbox.SetMemoryLimit(false)`.
- [x] `I2-09` Настроить release `Debug=false`, managed `log.level=warn`, `LogMaxLines=256`, без runtime-лога на диск.
- [x] `I2-10` Реализовать минимальный platform adapter: TUN PFD, `protect(fd)`, package owner lookup и platform callbacks.
- [x] `I2-11` Перед запуском очищать package include/exclude только в runtime-копии JSON и применять одну глобальную allowlist.
- [x] `I2-12` Валидировать один TUN, IPv4+IPv6 full routes, `auto_route`, package conflicts и запрещённые bind/mark/netns-поля.
- [x] `I2-13` Выполнять `CheckConfig()` до `VpnService.Builder.establish()`.
- [x] `I2-14` Реализовать foreground notification только с состоянием и действиями открыть/остановить; без live speed.
- [x] `I2-15` При revoke/error/stop закрывать core, PFD, callbacks, streams и foreground notification ровно один раз.
- [x] `I2-16` Получать selector-группы/текущий server через libbox и переключать активный server вызовом `CommandClient.SelectOutbound()` без restart TUN/core.
- [x] `I2-17` Перед switch собрать JSON с новым `selector.default`, выполнить `CheckConfig()` и атомарно сохранить; при ошибке runtime switch сделать один контролируемый restart.
- [x] `I2-18` Для managed selector включить `interrupt_exist_connections=true`; проверить, что закрываются только proxy-соединения selector, а `direct` и приложения вне TUN не затрагиваются.
- [x] `I2-18A` Поверх exact core commit применять один проверяемый Android data-plane patch: vanilla WireGuard и AmneziaWG используют раздельные pinned движки metacubex, общий protected batch-1 ClientBind, roaming lock до peers и порядок `device → IPC → TUN Up`; публиковать версии модулей и patch SHA-256, а checkout возвращать чистым. Physical A/B остаётся в `P16`.
### Тесты и Gate 2
- [x] Instrumented: выбранное приложение видно в TUN; контрольный невыбранный UID не виден и работает напрямую.
- [x] Instrumented: проверены IPv4, IPv6, TCP, UDP и реальный Hysteria2/QUIC transport.
- [x] Instrumented: `protect(fd) == false`, revoke и ошибка после `establish()` приводят к полному stop.
- [x] Instrumented: ноль/два TUN, partial routes и конфликт package list отклоняются до подключения.
- [ ] Instrumented: server switch меняет внешний IP без пересоздания TUN; после restart остаётся выбранный server.
- [x] Leak test: после 20 connect/stop нет роста PFD/threads/libbox instances; TUN, adapters и callbacks закрываются в каждом цикле.
- [x] Gate: минимальный JSON-профиль подключается на API 26 и современной ОС, а невыбранный трафик не создаёт per-packet работу приложения.
## Этап 3 — DNS, bootstrap и сетевой lifecycle
Цель: соединение либо полностью работает, либо полностью закрывается без сломанного DNS Android.
- [x] `I3-01` Реализовать `DefaultNetworkMonitor` для underlying non-VPN network без polling.
- [x] `I3-02` Реализовать bootstrap resolver: `DnsResolver` на API 29+, `Network.getAllByName()` на API 26–28.
- [x] `I3-03` Реализовать маленький LKG cache адреса proxy: fresh 24 часа, аварийный срок до 7 дней, исходное имя сохранять для TLS/Reality SNI.
- [x] `I3-04` Детектировать Private DNS off/automatic/strict через `LinkProperties`; системную настройку никогда не менять.
- [x] `I3-05` Реализовать четыре режима GUI: Автоматически, DNS Android, Защищённый через VPN, Из JSON.
- [x] `I3-05A` Добавить включённый по умолчанию «Только IPv4 через VPN»: `ipv4_only` применяется к generated DNS rules proxy-доменов в Secure/DNS Android и managed-этапах Auto, не меняя первую попытку с DNS профиля, direct/LAN, TUN IPv6 и режим «Из JSON»; пользователь может вернуть dual-stack, но WireGuard требует настоящий внутренний IPv6-адрес.
- [x] `I3-05B` Добавить одну глобальную редактируемую пару DNS override для managed-режимов, по умолчанию включённую как `ntc.party → 130.255.77.28`: `hosts` и точное правило стоят после `reject`, но до resolver rules; «Из JSON», routing, TLS/SNI и встроенный DoH не меняются, diagnostic не раскрывает пару.
- [x] `I3-05C` В Auto использовать конечную цепочку `DNS профиля → DNS Android → DoH`: переходить только после typed DNS health failure, полностью закрывать предыдущие core/TUN/callbacks, не переключаться на ошибке JSON/proxy/HTTPS и не давать fallback явным режимам.
- [x] `I3-06` Создать `RuntimeConfigBuilder`, который добавляет только `zapret-*` overlays и не меняет сохранённый JSON.
- [x] `I3-07` В явном Secure и последнем Auto-этапе использовать реальные IP, `reverse_mapping`, cache 4096 и пять DoH (Quad9, Google, OpenDNS, Cloudflare, Yandex) через `fallback/parallel`; exact `sequential` не достигает резерва при зависшем основном DoH; FakeIP не создавать.
- [x] `I3-08` Брать внутренний DNS через `TunOptions.GetDNSServerAddress()` и передавать его в `VpnService.Builder.addDnsServer()`.
- [x] `I3-09` Перехватывать стандартный DNS правилом port 53 / `hijack-dns`; не обещать перехват DoT, встроенного DoH и mDNS.
- [x] `I3-10` При strict Private DNS блокировать managed Auto/Secure до `establish()`; DNS Android разрешать только при active+validated strict, иначе fail-close без plaintext fallback; «Из JSON» не переписывать.
- [x] `I3-11` Реализовать последовательный health pipeline: proxy socket, DNS через TUN, HTTPS Cloudflare, затем Google и OpenDNS только после ошибок предыдущих endpoints.
- [x] `I3-12` Показывать «Подключено» только после всех проверок; любая ошибка закрывает TUN.
- [x] `I3-13` При смене сети или DNS/captive policy state обновлять underlying Network, сбрасывать transport и выполнять один контролируемый restart с debounce/generation token.
- [x] `I3-14` Не добавлять периодический health-check, бесконечный retry или plaintext DNS fallback.
- [x] `I3-15` Кнопку «Очистить DNS-кэш» реализовать контролируемым restart core, честно не обещая очистить Android resolver cache.
`I3-13`: baseline текущей сессии неизменяем. Transient callback `A → B → A`
отменяет pending restart, а итоговый policy key повторно проверяется после debounce.
### Тесты и Gate 3
- [x] Unit/core: четыре DNS fixtures проходят exact CLI; JVM-тест подтверждает managed `fallback/parallel` без FakeIP.
- [x] Unit: Auto выбирает DNS профиля, Android и DoH в фиксированном порядке, остаётся bounded и не скрывает non-DNS ошибки; явные режимы имеют ровно одну попытку.
- [x] Core: воспроизводимый тест внутри exact pinned package проверяет success, transport error, hang с общим context и `NXDOMAIN`/`SERVFAIL`/`REFUSED` без ошибочного fallback.
- [x] Instrumented на AVD API 28/29/36: Private DNS off/automatic/strict working/strict broken; managed Auto/Secure блокируются до TUN, а поломка strict Android DNS во время активной сессии event-driven закрывает TUN без plaintext fallback.
- [x] Instrumented на AVD: реальные Wi-Fi, mobile, Wi-Fi ↔ mobile и IPv6; один контролируемый restart на каждую смену; captive-portal fail-close покрыт детерминированной fault injection до TUN.
- [ ] Physical lab: настоящий captive portal и IPv6-only/NAT64 на целевых устройствах/сетях.
- [x] Instrumented: блокировка системного resolver с fresh/stale/no LKG; реальный managed DoH/proxy success, отказ всех managed DoH при недоступном proxy и полная очистка lifecycle.
- [ ] Physical lab: повторить blocked system DNS/LKG и DoH failure на реальной Wi-Fi/mobile сети, а не только через детерминированную fault injection.
- [x] Instrumented: мёртвый внутренний DNS после TUN закрывает core/PFD; Android немедленно получает обычную non-VPN сеть и снова разрешает DNS.
- [ ] Gate: вся матрица DNS ADR проходит, активный TUN никогда не остаётся с неработающим DNS.
Physical Test 17 на Pixel 9 Pro XL (API 37) подтвердил ответ WireGuard handshake и
чистый fail-close без утечки PFD/core/callback. Он одновременно выявил три независимые
ошибки после handshake: `ipv4_only` не применялся к proxy-доменам режима DNS Android;
`fallback/sequential` не мог перейти к резервному DoH после зависания первого; одиночный
Google 204 probe давал ложный `VPN-200`, а IPv6-попытка WireGuard-профиля без внутреннего
IPv6 завершалась `missing IPv6 local address`. Test 18 подтвердил WireGuard handshake,
но реальные логи затем показали блокировку managed DoH и необходимость предпочитать
уже выбранный DNS профиля. Цепочка Auto исправлена; physical подтверждение Test 21
остаётся частью открытого Gate 3.
## Этап 4 — маршрутизация и rule-set
Цель: один понятный UI управляет destination routing внутри настоящего JSON.
- [x] `I4-01` Реализовать экран из карточек «Область VPN», «Правило трафика» и читаемый «Итог».
- [x] `I4-02` Реализовать presets: Всё через VPN, Обход LAN, Только выбранные сайты, Россия напрямую, Россия через VPN, Пользовательский.
- [x] `I4-03` Реализовать действия правила: Через VPN, Напрямую, Блокировать.
- [x] `I4-04` Соблюдать порядок exact reject → direct exceptions → proxy rules → final.
- [x] `I4-05` Не генерировать package route rules для обычной allowlist: Android уже выполнил это отсечение.
- [x] `I4-06` Поставлять локальные binary `.srs` внутри APK с manifest, version, license и SHA-256.
- [x] `I4-07` Реализовать согласованное domain block: DNS `reject` + route `reject`; IP/CIDR block: route `reject`.
- [x] `I4-08` Не включать global sniff, legacy GeoIP/Geosite, remote managed rule-set или Android route exclusions.
- [x] `I4-09` Реализовать advanced include/exclude mode с явным предупреждением; пустой exclude-list блокирует запуск.
- [x] `I4-10` Дать GUI-редактор основных domain/IP/rule-set/outbound полей, редкое оставить raw JSON.
- [x] `I4-11` После каждой GUI-операции показывать effective summary и diff управляемых `zapret-*` объектов.
- [x] `I4-12` Добавить отдельный пустой по умолчанию blocklist приложений: в include
объединять его с Android TUN boundary, в exclude запрещать пересечение с direct,
а в runtime-копии ставить первые DNS/route `package_name → reject`.
### Тесты и Gate 4
- [x] Core/fixture: `ru-rule-set.json` и `block-rule.json` проходят exact CLI.
- [x] Instrumented: RU/non-RU domain и IPv4/IPv6 во всех presets.
- [x] Instrumented: selected app получает proxy/direct/reject; unselected app всегда остаётся вне TUN.
- [x] Instrumented: приложение со встроенным DoH демонстрирует задокументированное ограничение domain-only block.
- [x] Performance: измерить cold start, lookup CPU/RAM и размер production `.srs`.
- [x] Gate: UI summary, effective JSON и реальный сетевой путь совпадают для каждого preset.
Gate 4 закрыт автоматизированно 22 июля 2026 года на AVD API 26 и 36. Все шесть presets прошли реальный TUN-путь через локальный SOCKS5 proxy/direct/reject для RU/non-RU domain и IPv4/IPv6; отдельный UID-тест подтвердил, что невыбранное приложение остаётся вне TUN. Стандартный DNS block и HTTPS DoH→numeric path воспроизводят документированную границу domain-only block. UI summary, сохранённый JSON и фактический outbound совпали для каждого preset.
Exact pinned core загрузил два `.srs` за 1 114 мкс при 758 624 байтах allocations; lookup benchmark — 329 нс/op, 1 104 B/op, 2 allocs/op. Суммарный размер assets — 50 089 байт. В полном debug-прогоне API 36: extraction 2 мс, cold connect 41 мс, 40 реальных flows — 100 мс CPU и +356 КиБ PSS; API 26: 5 мс, 63 мс, 300 мс CPU и без роста PSS. Это закрывает автоматизированный performance-пункт, но не release-gate энергии на физических устройствах.
## Этап 5 — ссылки, QR и подписки
Цель: основной пользователь импортирует конфигурацию без знания JSON.
- [x] `I5-01` Реализовать единый `ImportParser` без отдельной внутренней модели маршрутизации.
- [x] `I5-02` Поддержать обычный sing-box JSON и subscription, возвращающий JSON.
- [x] `I5-03` Поддержать URI-листы минимум VLESS, VMess, Trojan, Shadowsocks, Hysteria2 и TUIC; каждый результат преобразовывать сразу в sing-box JSON.
- [x] `I5-04` Поддержать plain/base64 subscription со списком известных URI; неизвестную строку не угадывать.
- [x] `I5-05` Добавить URL import и ручное обновление subscription groups с preview до сохранения.
- [x] `I5-06` Добавить QR scanner только по действию пользователя; камера запрашивается в момент открытия.
- [x] `I5-07` Добавить импорт из буфера только после нажатия и импорт файла через системный picker.
- [x] `I5-08` После успешного импорта не подключаться автоматически: предложить выбрать приложения, затем явную кнопку подключения.
- [x] `I5-09` Перед запуском сканировать импортированный JSON на `urltest`, NTP, remote rule-set, external Clash controller, verbose log и explicit keepalive.
- [x] `I5-10` Показывать одно предупреждение о фоновой/внешней активности; пользовательский JSON скрыто не очищать.
- [x] `I5-11` Маскировать UUID, password, token, subscription query и URL credentials во всех preview/log/error.
- [x] `I5-12` Не добавлять фоновое обновление подписок или ядра.
- [x] `I5-13` При subscription refresh сохранять выбранный server tag, если он остался; иначе выбрать первый доступный и явно сообщить пользователю.
- [x] `I5-14` Активный профиль после ручного refresh перезапускать только после подтверждения пользователя; не менять работающий server скрыто.
- [x] `I5-15` Для одиночного URI предлагать «Новый профиль» или «Добавить в существующую managed-группу»; никогда не сливать профили автоматически.
### Отложенные форматы импорта
- [x] `F-IMPORT-WG` Реализовать строгий импорт WireGuard/AmneziaWG 2.0 native `.conf` напрямую в sing-box `endpoints`, native/core tests и открытие через Android `ACTION_VIEW`; unknown-field drop запрещён.
- [x] `F-IMPORT-01` Проверить спрос и корректную семантику Clash YAML. Реальный спрос и риск silent field loss подтверждены; точный core parser не экспортирован в libbox, поэтому второй YAML-конвертер не входит в MVP. Решение и gate зафиксированы в [политике форматов](IMPORT_FORMATS.md#f-import-01--clash-yaml).
- [x] `F-IMPORT-02A` Сверить URI inventory точного ядра. Единственный отсутствующий URI из pinned parser — Hysteria v1; это кандидат, а не реализованная поддержка.
- [ ] `F-IMPORT-02` Собрать и проанализировать реальные неподдержанные ссылки: минимум 3 обезличенных Hysteria v1 URI и образцы пробелов текущих VLESS/SS/TUIC/Hysteria2 parser; затем выбрать по данным.
- [ ] `F-IMPORT-03` Добавлять Clash YAML только после полного gate из [политики форматов](IMPORT_FORMATS.md#f-import-01--clash-yaml); не импортировать Clash DNS/routes/groups.
Здесь `[x]` у исследовательского пункта означает завершённый аудит, а не
наличие формата в APK.
### Тесты и Gate 5
- [x] Golden tests для каждого URI, IPv6 host, percent/base64 encoding, transport, TLS, Reality и пустых/битых полей.
- [x] Fuzz/property tests: parser не падает и не зависает на произвольном вводе.
- [x] Integration: subscription update атомарен; битый ответ не меняет старые профили.
- [ ] Security: секреты отсутствуют в Logcat, crash message, notification и redacted export.
- [ ] Gate: путь URL/QR/clipboard/file → preview → JSON → выбор приложений → connect работает без raw editor.
Открытая часть Gate 5 требует реальной камеры/HTTPS subscription endpoint и физического
устройства. Redacted export из `I6-15`–`I6-16` теперь проверен instrumented-тестом:
профиль, package list, endpoint, UUID/password/token и внешний IP в отчёт не попадают.
Полный security-gate всё ещё требует проверки Logcat/crash/notification на физическом
устройстве. Автотестами также подтверждены parser/preview, native `CheckConfig`,
атомарный malformed refresh и общая redaction ошибок/debug core log.
## Этап 6 — продуктовый UI и диагностика
Цель: компактный Material You клиент, пригодный для ежедневного использования.
### Главная и состояние
- [x] `I6-01` Реализовать компактную connection card: состояние, профиль, сервер, IP, ping и время.
- [x] `I6-01A` По нажатию на сервер открывать bottom sheet selector-группы с именем, протоколом, endpoint, session ping и отметкой текущего выбора.
- [x] `I6-02` Использовать внутренний libbox traffic manager без external controller/listener.
- [x] `I6-03` Подписывать `CommandStatus` с интервалом 1 секунда только пока главная в lifecycle `STARTED`.
- [x] `I6-04` Хранить 60 download/upload значений в кольцевом массиве текущей сессии.
- [x] `I6-05` Никогда не включать `CommandConnections`; bounded `CommandLog` открывать на время connect health-check и затем только на экране диагностики.
- [x] `I6-06` IP запрашивать один раз после connect, ping — при connect и вручную.
- [x] `I6-07` Уведомление не обновлять каждую секунду и не рисовать там скорость.
Главная и диагностика проверены 22 июля 2026 года: 85/85 JVM и текущие 67/67
instrumented на AVD API 36; базовая матрица 66/66 также пройдена на API 26/29.
Постоянным остаётся только event-driven `CommandGroup`;
`CommandStatus` создаётся при видимой главной, а отдельный `CommandLog` — на время
connect health-check и затем только при видимой диагностике. Оба непостоянных клиента
физически закрываются вне своего узкого lifecycle. `CommandConnections` отсутствует.
### Остальные экраны
- [x] `I6-08` Завершить Профили: группы, source, last update, add actions и ошибки.
- [x] `I6-09` Завершить Маршрутизацию: две карточки, rules, app picker, advanced JSON.
- [x] `I6-10` Завершить Настройки: тема, DNS, Stable/Beta, Диагностика, Сообщество, О приложении.
- [x] `I6-10A` Добавить отдельный экран «Скрытие VPN» и rootless runtime-модуль без нового process/thread/polling: localhost endpoints закрыты по умолчанию, stored JSON не меняется.
- [ ] `I6-10B` Завершить physical gate MTU 1500 на IPv4/IPv6/NAT64/QUIC, операторах, OEM и энергии; initial device-test позволил включить default с явным откатом к profile/core.
- [x] `I6-10C` Повторно проверить на физическом Android WireGuard/AWG после исправления:
stable 0.2.3 доказал внешний TUN 9000 при endpoint 1280; stable `v0.2.4` задаёт
`min(1500, endpoint MTU)`, то есть 1280 для проверенного профиля. Владелец установил
production APK на то же реальное устройство и подтвердил работу WireGuard и остальных
использованных профилей. Полная OEM/оператор/IPv6 матрица остаётся в `I6-10B`/`R8`.
- [x] `I6-11` Разместить Telegram-ссылки только в «Настройки → Сообщество».
- [x] `I6-12` Добавить accessibility labels, нормальный back navigation, состояния loading/empty/error и крупные touch targets.
- [x] `I6-13` Ограничить анимации стандартными Compose/Material; не добавлять тяжёлый dashboard.
Остальные экраны завершены 22 июля 2026 года без нового слоя навигации, DI или
зависимостей. Профили группируются только по UI metadata и показывают источник и
`updated_at`; сетевой JSON остаётся единственным источником истины. Маршрутизация
имеет две главные карточки: Android per-app scope и destination routing, а кнопка
«Расширенный JSON» открывает активный настоящий профиль. Stable/Beta хранится как
UI-настройка и не запускает фоновые обновления; updater остаётся этапом 7.
Три Telegram URL присутствуют в production-коде только внутри экрана
«Настройки → Сообщество». Подэкраны настроек, picker приложений и JSON-редактор
обрабатывают системный Back; интерактивные строки имеют минимум 56–72 dp и
понятные semantics. Собственных бесконечных/тяжёлых анимаций нет — используются
только штатные состояния Compose/Material. Проверено в общей матрице: 85/85 JVM,
67/67 instrumented tests на API 36 и базовые 66/66 на API 26/29. Основные кнопки соединения имеют
явный минимум 48 dp; этот инвариант и подписи нижней навигации проверяются smoke-тестом.
### Диагностика
- [x] `I6-14` Показывать короткий тип ошибки и раскрываемые последние bounded log lines.
- [x] `I6-14A` Записывать bounded event timeline каждой попытки подключения/restart: общая длительность, статусы этапов и самый долгий этап; только monotonic event timestamps, без ticker/polling.
- [x] `I6-14B` Атомарно хранить только последний redacted uncaught Kotlin/Java crash в `noBackupFilesDir`; runtime/core traffic logs на диск не писать. На API 30+ читать одну системную process-exit запись для native crash/ANR без копирования тяжёлого trace.
- [x] `I6-14C` Заменить head/tail startup-окно на priority ring: handshake/endpoint/TUN/error сохраняются, повторы схлопываются, один callback принимает не более 48 записей, а received/coalesced/dropped counters экспортируются.
- [x] `I6-15` Создавать redacted diagnostic JSON только по действию пользователя.
- [x] `I6-16` Экспортировать через Android Sharesheet/FileProvider и удалять временный файл при следующем запуске.
- [x] `I6-17` Включать core/app version, Android/API, network state, Private DNS mode и effective overlay без секретов.
Диагностика не является отдельным Gradle-модулем и не добавляет постоянного фонового сборщика. `CommandLog` подписывается
отдельным клиентом на время connect health-check и при `Activity STARTED` с открытым
экраном. В памяти остаются три последние попытки, до 48 приоритетных startup core-записей
на каждую и отдельные 80 общих записей; повторы схлопываются, шум вытесняется раньше
handshake/TUN/errors, а counters честно показывают отброшенное. Клиент закрывается идемпотентно после успешной
проверки, вместе с экраном или service. Runtime-лог на диск не пишется. Effective overlay — структурная
сводка managed `zapret-*`: режим DNS, наличие dual-stack TUN, типы managed DNS,
количество правил/actions, локальные rule-set и bounded hardening-state без endpoint,
match values или секретов.
Вся suspend-цепочка connect/restart ограничена одним 30-секундным deadline. Timeout
завершается fail-close с `VPN-120` и не создаёт периодический watchdog или retry-loop.
Отчёт версии 3 создаётся только кнопкой, содержит app/core revision+patch SHA-256, Android/API/ABI,
VPN/non-VPN network state, Private DNS, последнюю классифицированную ошибку, безопасный
overlay, bounded connection timeline, runtime resource/log counters, последний redacted app crash,
одну Android process-exit запись и bounded log lines. Он не содержит raw JSON, имя профиля, packages, внешний IP
или credentials. Единственный временный файл находится в `cache/diagnostics/`, доступен
через `FileProvider` с `exported=false` и read grant Sharesheet, а `AppContainer` удаляет
его при следующем запуске.
Полевой отчёт v1 от 22 июля 2026 года подтвердил живую сессию на Android 17/API 37:
Wi-Fi validated, managed DNS `Automatic`, Private DNS off, dual-stack TUN и VLESS/Reality без
зафиксированной ошибки. В захваченном steady-state трафике DNS lookup занимал 1–3 мс,
а выход VLESS на TLS/Reality — около 177–196 мс. Отчёт был создан через 83,7 с после
`connected_at`, а первая log line была получена через 78,1 с, поэтом v1 не доказывает
длительность первичного connect и не закрывает причину задержки. Для этого нужна одна
новая репродукция на APK с отчётом v2 и его connection timeline.
### Тесты и Gate 6
- [x] Compose UI tests для всех состояний главной и четырёх вкладок.
- [x] Lifecycle test: после ухода с главной status ticker закрыт; после ухода из диагностики log stream закрыт.
- [x] Rotation/process recreation не создаёт второй service/core и не теряет фактическое состояние VPN.
- [x] Accessibility smoke test на светлой/тёмной теме и API 26/31+.
- [x] Gate: полный путь «установил → импортировал → выбрал → подключил → диагностировал» не требует JSON.
Автоматизированный Gate 6 закрыт 22 июля 2026 года на AVD API 26 и API 36. UI-тесты
покрывают отсутствие профиля/приложений и состояния `Stopped`, `Starting`, `Connected`,
`Stopping`, `Error`, а также основные экраны всех четырёх вкладок. В светлой и тёмной
темах проверены accessibility labels нижней навигации и touch targets не меньше 48 dp.
Полный сценарий использует обычную Shadowsocks-ссылку из буфера: создаёт managed JSON
через preview, выбирает установленное приложение, поднимает настоящий Android TUN и
libbox, переживает rotation с тем же `connectedAt` и единственным core, затем открывает
диагностику. Сетевой health-result в тесте детерминирован, поэтому сценарий не зависит
от внешнего VPN-сервера, но profile parser, native `CheckConfig`, service, TUN и libbox
не подменяются. Raw JSON editor нигде не открывается.
Отдельный `scripts/verify-process-recreation.sh` проверяет hard process death. Android
в этом случае закономерно уничтожает service/TUN/core; новый процесс обязан показать
фактическое `Stopped` с нулевыми ресурсами, а следующее подключение — создать ровно один
service/core/TUN. Probe receiver существует только в debug source set, защищён системным
`android.permission.DUMP`, а CI дополнительно запрещает его присутствие в release APK.
## Этап 7 — обновление APK и выпуск
Цель: безопасный GitHub Release без динамического ядра и мусора.
- [x] `I7-01` Проверять GitHub Releases один раз при запуске и вручную: Stable принимает только обычный release, Beta — любой prerelease; при обновлении показывать release notes.
- [x] `I7-02` Скачать APK во внутренний cache, проверить опубликованный SHA-256 и передать системному installer.
- [x] `I7-03` Проверять package name и совместимость подписи; не обещать silent install.
- [x] `I7-04` Удалять скачанные/незавершённые APK после install handoff, отмены, ошибки и при следующем запуске; diagnostic temp-файлы — при следующем запуске.
- [x] `I7-05` Никогда не скачивать core отдельно: libbox обновляется только вместе с APK.
- [x] `I7-06` Настроить release workflow: exact core SHA → CLI/AAR → tests → APK → checksum → metadata.
- [ ] `I7-07` Хранить signing secrets только в GitHub Secrets; сделать зашифрованную офлайн-копию ключа и инструкции восстановления.
- [x] `I7-08` Публиковать release notes с app version, core tag, full core SHA, ABI и checksum.
- [x] `I7-09` Проверить same-key upgrade с предыдущей версией и сохранение профилей/DataStore.
- [x] `I7-10` Публиковать отдельные `arm64-v8a`, `armeabi-v7a`, `x86_64` APK без чужих native-библиотек; updater выбирает первый совместимый ABI устройства.
- [x] `I7-11` Вынести updater в направленный `app-updater` library-модуль. После retryable сетевой ошибки один раз повторять текущую проверку или загрузку через временный VPN runtime overlay, ограниченный package приложения и GitHub-хостами; после операции восстанавливать предыдущее состояние VPN.
Updater проверяет выбранный канал один раз на запуск процесса и по кнопке, не имеет
scheduler/service и не выполняет периодический polling. Stable использует самый новый
не-prerelease, Beta — самый новый prerelease независимо от имени тега. Release считается
валидным при единственном поддерживаемом metadata-файле, полной матрице одно-ABI APK и отдельных
`APK.sha256`; metadata, checksum и GitHub asset digest обязаны совпасть. Переходный schema-1
`release-metadata.json` указывает на arm64 APK для уже установленных старых клиентов. Разрешены только
HTTPS-хосты GitHub с ограниченными redirect/размером/таймаутом.
`app-updater` не зависит от UI, профилей, libbox или VPN lifecycle. Интеграционный callback
в `app` включается только после сетевой ошибки/HTTP 403, 429, 451 или 5xx. Если VPN уже
работает, сервис выполняет контролируемый restart с временным `package_name + domain_suffix`
правилом; если VPN был выключен и consent уже выдан, временно запускается активный профиль.
После запроса исходное состояние восстанавливается в `finally`, включая отмену. Правило не
записывается в профиль и не влияет на GitHub-трафик других приложений. Без consent или
активного профиля updater показывает обычную ошибку с причиной недоступности VPN-повтора.
После загрузки Android читает сам APK: package должен совпасть с установленным, versionCode
должен быть строго больше, versionName/versionCode — совпасть с metadata, minSdk — подходить,
а текущий сертификат — входить в доказанную signing history нового APK. Для multi-signer
требуется точное равенство набора. Только после этих проверок non-exported FileProvider
передаёт content URI штатному installer; silent install не используется. Частичный, битый,
отменённый или оставшийся после process restart APK удаляется из `cache/updates`.
Локальный stable publisher принимает строгий tag `vMAJOR.MINOR.PATCH`, выводит
детерминированный монотонный versionCode, собирает exact pinned core, тестирует,
подписывает отдельные arm64-v8a/armeabi-v7a/x86_64 APK постоянным owner-only ключом,
проверяет `apksigner`/manifest/единственность ABI и публикует APK, SHA-256, metadata и
notes один раз без замены уже опубликованных assets. После публикации он запускает
GitHub Actions как независимую фоновую проверку и не ждёт её. Workflow не имеет
production key и ничего не публикует.
Постоянный ключ создан, проверен и хранится локально вне репозитория; публичный
fingerprint закреплён в `release.properties`. Локальный publisher, фоновый verification
workflow и [инструкция](SIGNING.md) готовы. `I7-07` остаётся открытым только до
подтверждения двух отдельных зашифрованных офлайн-копий ключа.
### Gate 7
- [x] Неверный checksum, другая подпись, прерванная загрузка и downgrade обрабатываются безопасно.
- [x] После update/cancel/restart cache не содержит старых APK.
- [x] Release APK воспроизводимо содержит указанный core revision.
- [x] GitHub Release содержит APK, SHA-256 и достаточную информацию для независимой проверки.
Автоматизированный Gate 7 пройден на AVD API 26/36. `scripts/verify-same-key-upgrade.sh`
установил build 701001, записал настоящий профиль/DataStore/allowlist, обновил тем же ключом
до 701002 и подтвердил сохранность. Затем Android отклонил downgrade и переподписанный
другим ключом APK как `VERSION_DOWNGRADE`/`UPDATE_INCOMPATIBLE`, не повредив данные.
Unit/instrumented тесты отдельно проверяют rotation lineage, multi-signer, package/version,
неверный checksum, прерванный `.part`, installer cancel и startup cleanup. Публичные
`v0.2.1-beta.30` и stable `v0.2.1` содержат три ABI, отдельные SHA-256 и обе
metadata-схемы.
## Этап 8 — пострелизная физическая матрица
Цель: постепенно расширять доказанную совместимость на реальных Android-устройствах
и сетях. По решению владельца эта матрица выполняется пользователями после публикации
MVP и не блокирует уже выпущенный stable; открытая галочка означает непроверенное
сочетание, а не известную поломку.
### Устройства и сеть
- [ ] `R8-01` Реальное слабое устройство API 26/28.
- [x] `R8-02` Android 10/API 29 AVD: `DnsResolver`, Private DNS boundary и 66/66 tests.
- [x] `R8-03` Современный Android API 36 AVD: 66/66 tests; физическое устройство остаётся в energy/OEM gate.
- [ ] `R8-04` Wi-Fi, cellular, IPv4, IPv6/NAT64, captive portal и смена сети.
- [x] `R8-05` Always-on/Lockdown читается через public API 29+, объясняется до TUN и не включается приложением.
### Корректность
- [ ] `R8-06` Повторить полную DNS matrix из DNS ADR.
- [ ] `R8-07` Повторить routing matrix из Routing ADR.
- [ ] `R8-08` Проверить каждый заявленный outbound отдельно на connect, UDP/QUIC и `protect(fd)` loop.
- [ ] `R8-09` Проверить shared UID, удалённый package, пустые include/exclude и revoke.
- [x] `R8-10` 100 connect/stop и 50 Wi-Fi/mobile transitions пройдены на API 29/36 AVD без deadlock, duplicate restart и утечек.
Протокол и честная граница automated/physical прогона зафиксированы в
[`GATE8_RESULTS.md`](GATE8_RESULTS.md). `R8-01`, полный `R8-04`, `R8-06…R8-09`
остаются открытыми до реальных устройств/сетей/серверов.
### CPU, память и батарея
- [ ] `R8-11` Пять повторов: VPN off baseline и VPN idle с погашенным экраном. API 36 AVD 5/5 пройден; физическая energy-часть открыта.
- [ ] `R8-12` Невыбранное приложение передаёт фиксированный объём без пропорционального CPU/network роста Zapret KVN. На AVD 8 MiB дали median TUN=0; физический OEM-повтор открыт.
- [ ] `R8-13` Сравнить выбранный `direct` и `proxy`: CPU, throughput, RSS/PSS, GC и энергия. AVD CPU/throughput/RSS/PSS готовы; реальные proxy, GC trace и энергия открыты.
- [ ] `R8-14` Сравнить главную видимую/закрытую и diagnostics открыта/закрыта. Настоящие Compose-экраны AVD измерены; физическая энергия открыта.
- [ ] `R8-15` Измерить принятый managed `parallel` против контрольного `sequential` на уникальных именах. Test 17 уже доказал функциональную регрессию `sequential` при hang; AVD 5×12 готов, физическая энергия/cache burst открыты.
- [ ] `R8-16` Сравнить текущий `mixed` stack с `system`; AVD-разница ниже 5%, default не изменён; физическая матрица открыта.
- [ ] `R8-17` Сравнить default MTU 1500 и profile/core; initial physical test дал заметный выигрыш, но IPv6/NAT64/QUIC, оператор/OEM и энергия открыты.
- [ ] `R8-18` Сравнить `SetMemoryLimit(false)` с экспериментальным GC=10: AVD CPU/PSS/RSS/throughput ниже порога; Go GC count/pause, OOM и физическая энергия открыты.
- [x] `R8-19` Harness сохраняет System Trace, batterystats и 130 raw-файлов с SHA-256; manual CI загружает их artifact. Физические PowerMetric/ODPM входят в открытые R8-11…R8-18.
- [x] `R8-20` Порог 5% применяется агрегатором и проверяется negative/positive self-test в `verify-project.sh`.
Точный AVD-протокол и граница оставшейся физической проверки находятся в
[`GATE8_RESULTS.md`](GATE8_RESULTS.md). Harness дополнительно прошёл сокращённую матрицу
5 × 16 на API 26; это только проверка minSdk-совместимости, не energy evidence. Ни один
production default по AVD-данным не изменён.
### Security и release candidate
- [x] `R8-21` Secrets проверяются до export во внутреннем bounded-log, в redacted export,
foreground notification и app-private temp/history после явного чтения clipboard; stale diagnostics/APK
очищаются отдельными тестами. Уведомление принимает только закрытый enum состояний.
- [x] `R8-22` Managed runtime удаляет все listener/UI/secret поля `experimental.clash_api`,
не меняя сохранённый JSON. Явный raw user JSON остаётся под контролем пользователя.
- [x] `R8-23` Exact merged-manifest audit проверяет permission allowlist, все exported-компоненты,
signature-permission AndroidX, FileProvider, backup/cleartext/process/VPN contract и `Debug=false`.
- [x] `R8-24` Arm64 release проверяет APK/R8 mapping и exact native symbols; minified x86_64
release на API 36 прошёл 5 cold starts (median 407 ms) без process crash.
- [x] `R8-25` Локальный minified RC прошёл clean install, same-key update,
downgrade/signature rejection; production-signed stable `v0.2.4` установлен и
запущен владельцем на реальном устройстве.
### Финальный gate
- [x] Нет failed fixture, unit или instrumented test: exact/compat fixtures 7/7,
JVM 85/85, API 36 instrumented 67/67; API 26/29 baseline также пройден.
- [x] Нет core revision/ABI/signature mismatch. Exact core и ABI проверены release-аудитом,
same-key/foreign-key/downgrade tests пройдены, production-signed stable установлен
на реальном arm64-устройстве.
- [x] Нет app-owned WakeLock, alarm/job, скрытого polling или бесконечного reconnect.
CI проверяет manifest и production sources; разрешён только lifecycle ticker видимой
главной и одноразовый debounce смены сети.
- [x] Managed idle не создаёт периодический сетевой трафик: 5/5 окон дали 0 UID RX+TX
и 0 status/log clients; физическая energy-матрица остаётся отдельным R8 gate.
- [x] Невыбранный трафик не проходит через TUN/libbox: 5 × 8 MiB дали 0 UID RX+TX
Zapret KVN и median 0 TUN bytes (единственный интерфейсный шум — 96 bytes).
- [x] Все известные ограничения синхронно перечислены в UI, README и генерируемых
release notes; это проверяет `scripts/verify-project.sh`.
- [x] Первый production GitHub Release опубликован; актуальный stable `v0.2.4`
доступен с тремя ABI, SHA-256 и updater metadata.
Публичный MVP завершён. Открытые `R8-*` и physical/energy gates из
[`GATE8_RESULTS.md`](GATE8_RESULTS.md) являются пострелизной программой совместимости:
их закрываем только реальными пользовательскими evidence, но они не возвращают
статус уже принятого MVP в незавершённый.
## После MVP — только по измерениям и запросам пользователей
Эти пункты не входят в текущий final gate и не реализуются «на всякий случай». Галочка
означает не наличие кода, а прохождение указанного ниже отдельного activation gate.
- [x] `F-01` Whole-app block активирован 24 июля 2026 года по явному запросу пользователя:
отдельный пустой по умолчанию список создаёт узкое `package_name → reject` внутри
TUN и не превращает Android include/exclude boundary в третий глобальный режим.
- [ ] `F-02` Clash YAML import. Требуется полный gate из `IMPORT_FORMATS.md`: libbox binding
либо отдельное обоснованное решение о parser, минимум 10 реальных образцов и доказанное
отсутствие silent field loss. DNS/routes/groups из Clash автоматически не сливать.
- [ ] `F-03` Планшетный/navigation rail layout. Активировать только после возврата планшетов
в product scope и UI/accessibility matrix на compact/medium/expanded width.
- [ ] `F-04` Выбор `mixed/system` stack в GUI. AVD-разница ниже 5%; оставить upstream
`mixed`, пока физические API 26/современный Android не покажут устойчивый выигрыш или
несовместимость хотя бы одного режима.
- [x] `F-05` MTU 1500 продвинут в default после initial device-test; экран сохраняет явный
откат к profile/core. Полная физическая матрица IPv4, IPv6/NAT64, QUIC, PMTU, CPU
и энергии остаётся в `I6-10B`. Это только внешний Android TUN: внутренний
WireGuard/AWG endpoint без явного `mtu` получает Android-совместимое значение 1280;
его physical gate отслеживается отдельно в `I6-10C`.
- [ ] `F-06` Patch owner/process lookup. Сначала P4 benchmark текущего core и отдельной
экспериментальной сборки на реальных устройствах; требуются существенный повторяемый
выигрыш, собственный patch hash и полный regression/release matrix.
- [ ] `F-07` FakeIP. Только явно экспериментальный режим с отдельной ADR: per-profile
ranges/cache identity, очистка при stop/delete/network change и тесты, исключающие
сохранение FakeIP вне TUN. Managed default остаётся без FakeIP.
- [ ] `F-08` Remote rule-set updater. Только после подтверждённого сценария, политики
лицензий/version/SHA/rollback и явного ручного UX; не добавлять скрытый worker/polling и
не объединять с APK updater.
## Текущее состояние
- [x] Архитектурный аудит завершён 22 июля 2026 года.
- [x] Exact CLI: 7/7 fixtures.
- [x] Compatibility CLI: 7/7 fixtures.
- [x] Go packages compile/tests pass; дополнительный audit test временно встраивается в exact pinned fallback package и проверяет success/error/hang/RCODE.
- [x] Markdown fences, локальные ссылки и SHA-256 fixtures проверены.
- [x] Android/Gradle-проект создан.
- [x] Debug и R8 release APK собраны.
- [x] Runtime smoke пройден на эмуляторах API 26 и API 36.
- [x] Повторный аудит I0-08–I0-14: exact origin/tag/SHA, рекурсивные fixtures, arm64 release и pinned CI actions.
- [x] JVM unit tests: 165/165 в `:app:testDebugUnitTest`; добавлены Auto DNS fallback,
app-scoped health route, GitHub updater/signing, security, Always-on/Lockdown policy
и полная блокировка приложений.
- [x] Android instrumented tests: текущие 67/67 пройдены на API 36; базовые 66/66 — на API 26/29, security delta 3/3 — на API 26.
- [x] Hard process recreation: `scripts/verify-process-recreation.sh` пройден на API 26/36; после смерти процесса ноль session/core/TUN/callback/client, после нового connect ровно один экземпляр.
- [x] Packaged `.srs`: exact CLI RU/non-RU domain/IPv4/IPv6, manifest/license/SHA, atomic repair; 50 089 байт, cold install 14 мс на AVD API 36.
- [x] Routing lookup/cold-start CPU/RAM measurements выполнены на AVD API 26/36 и exact core benchmark.
- [x] Test 18 собран локально из commit `e637391` для arm64-v8a, armeabi-v7a и x86_64 и опубликован отдельным GitHub prerelease с SHA-256/metadata.
- [x] Test 20 опубликован из commit `352fb31` с редактируемым DNS override; Auto DNS fallback в него ещё не входил.
- [x] Test 21 собран локально из commit `d78ad72` для arm64-v8a, armeabi-v7a и x86_64 и опубликован с новой цепочкой Auto DNS, SHA-256 и metadata; физическая проверка остаётся открытой.
- [x] Test 22 собран локально из commit `479a480` для arm64-v8a, armeabi-v7a и x86_64 и опубликован с Markdown-оформлением release notes, SHA-256 и metadata.
- [x] По отчётам Test 21 подтверждён Android WireGuard GRO/standard-bind сбой: handshake и первый DNS проходят, затем return data зависает. Runtime-only direct detour переводит endpoint без явного `detour` на `ClientBind`; health-route теперь обязателен во всех DNS-режимах, а профиль без DNS получает минимальный Android fallback. Exact pinned CLI и JVM regression tests проходят; физическая проверка остаётся открытой.
- [x] Beta 23 опубликована из commit `6be23f4`, но проверка живого GitHub API показала, что старый updater сортирует `test.*` выше более нового `beta.*`; release и tag сохранены для аудита.
- [x] Test 23 опубликован из commit `f602c19` для arm64-v8a, armeabi-v7a и x86_64 с versionCode `200123`, тем же debug signer, SHA-256 и updater metadata. Он дополнительно сортирует prerelease по `published_at` и находится первым для Test 21/22.
- [x] Диагностика Test 23 доказала более широкий дефект pinned Android WireGuard: handshake и первый UDP DNS успешны, но TCP SYN/data-plane зависает; «Из JSON» давал ложный успех через direct IPv6. Android data-plane заменён без второго TUN на pinned vanilla/AWG движки metacubex.
- [x] Test 24 опубликован из commit `ba395b4` для arm64-v8a, armeabi-v7a и x86_64 с versionCode `200124`, тем же debug signer, SHA-256, updater metadata и точными версиями vanilla WireGuard/AmneziaWG data-plane модулей; физическая проверка новой архитектуры остаётся открытой.
- [x] ADB evidence Test 24 подтвердил исправный Android TUN/VPN lifecycle и самозакрытие ровно через 12 секунд в добавленном `wireguard_data_plane`. Этот gate проверял доступность трёх публичных HTTPS-сайтов, а не handshake, дублировал общий health pipeline и скрывал core-log; он удалён. Общая HTTPS-проба теперь получает app-scoped TLS sniff и обязательный route в selected outbound, bounded core-log открывается до первой сетевой проверки.
- [x] Test 25 опубликован из commit `a1ee9cb` для arm64-v8a, armeabi-v7a и x86_64 с versionCode `200125`, прежним debug signer, новым patch SHA-256, per-APK checksums и updater metadata; Git tag указывает точно на проверенный app commit.
- [x] Evidence Test 25 исключил MTU как самостоятельную причину видеозависания: одинаковое поведение воспроизводится при внешнем TUN `1500` и profile/core `9000`. Найден общий для raw/WireGuard/VLESS runtime-дефект: exact core без `log.level` выбирает `trace`; теперь runtime всех профилей ограничен `warn` (или более строгим сохранённым уровнем), `log.output` удаляется, а профиль на диске не меняется.
- [x] Test 26 опубликован из commit `9103ed1` для arm64-v8a, armeabi-v7a и x86_64 с versionCode `200126`, прежним debug signer, per-APK checksums и updater metadata. Он изолированно проверяет новую runtime logging policy при неизменных core и WireGuard data-plane.
- [x] IPv4-only userspace WireGuard распознаётся по внутренним endpoint addresses.
Connect-time HTTPS health-check выбирает IPv4 через resolver VPN `Network`,
подключается к числовому адресу с исходными TLS SNI/certificate hostname/HTTP
Host и возвращает `VPN-201`, если требуемого семейства нет. Сохранённый JSON не
переписывается; неиспользуемые DNS servers только явно предупреждаются.
- [x] Beta `v0.2.4-beta.1` опубликована из commit `4dff6b3` с production-подписью
для arm64-v8a, armeabi-v7a и x86_64. Runtime userspace WireGuard теперь задаёт
внешний Android TUN `min(1500, endpoint MTU)` во всех DNS-режимах; exact fixture,
JVM, lint и release-candidate проверки проходят. Физическое подтверждение остаётся
открытым пунктом `I6-10C`.
- [x] Stable `v0.2.4` опубликован из commit `ec6aad4` с versionCode `200499`,
production-подписью и отдельными arm64-v8a, armeabi-v7a и x86_64 APK.
GitHub Release содержит SHA-256 и schema 1/2 metadata; tag указывает на exact
локально проверенный source commit.
- [ ] Idle CPU/battery проверены на физических устройствах; пункт пострелизный и
закрывается по измерениям, а не субъективному времени работы.
**Следующее действие:** принимать пользовательские отчёты по stable `v0.2.4`,
воспроизводить только подтверждённые проблемы по ADB evidence и выпускать patch-релизы.
Постепенно закрывать физическую матрицу: captive portal, IPv6-only/NAT64,
camera/HTTPS subscription, blocked-DNS/LKG/DoH, OEM per-app/routing и энергия.
Также создать две зашифрованные офлайн-копии production signing key по `SIGNING.md`.