68 KiB
Zapret KVN Android — план реализации
Рабочий TODO-лист проекта. Отмечать выполненное заменой
[ ]на[x]только после прохождения указанного gate. Архитектурные решения здесь не переопределяются: источник истины — главная архитектура, DNS ADR, Routing ADR, VPN Hiding ADR и политика форматов импорта.
| Поле | Значение |
|---|---|
| Статус | Этапы 0–7 и automated Gate 8 реализованы; внешний release/signing setup и физическая матрица остаются открыты |
| Текущий этап | Этап 8: обязательная выпускная матрица |
| Минимальная ОС | 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 или фоновую задачу «на будущее».
- После этапа обновлять строку «Текущий этап» и раздел «Текущее состояние» внизу.
Зафиксировано до начала кода
- Принята архитектура с одним product-модулем
appи узкими направленными libraries (network-bootstrap, import), без Room, Hilt, WebView и WorkManager. - Разделены Android per-app scope и sing-box destination routing.
- Зафиксированы один process, один
VpnService, один TUN и один libbox instance. - Зафиксирована DNS-архитектура без FakeIP: Auto
профиль → Android → DoH, аfallback/parallelиспользуется только внутри защищённого этапа. - Зафиксирована маршрутизация
proxy/direct/rejectчерез настоящий JSON. - Зафиксирован точный commit ядра.
- Подготовлены и проверены 6/6 эталонных JSON.
- Пройден
go test ./dns/... ./route/rule ./experimental/libbox. - До первой публичной сборки выбрать окончательные
applicationId, namespace и имя signing key. - Для production rule-set зафиксированы источники, лицензии, commit и SHA-256 списков.
Definition of Done MVP
MVP готов только когда выполнены все пункты:
- Новый пользователь устанавливает APK, импортирует VLESS/другой поддержанный URI или JSON, выбирает приложения и подключается без ручного редактирования JSON.
- Невыбранное контрольное приложение не попадает в TUN и продолжает работать напрямую.
- Выбранное приложение проходит правила
proxy/direct/reject, DNS и IPv4/IPv6 без утечек и циклов. - При сломанном сервере или DNS TUN полностью закрывается, обычная сеть восстанавливается.
- Импорт, GUI-редактирование и backup не теряют неизвестные extended-поля JSON.
- Пройдены fixture, unit, instrumented, lifecycle, redaction, update и energy release-gates.
- Release APK содержит ядро нужного commit, подписан постоянным ключом и опубликован с SHA-256.
Этап 0 — каркас и воспроизводимая сборка
Цель: минимальный устанавливаемый APK и CI, доказывающий происхождение ядра.
Репозиторий и Android
I0-01Инициализировать Git-репозиторий и добавить.gitignore,LICENSE,README.md.I0-02Создать Gradle wrapper и один модульapp; Kotlin, Compose и Material 3, minSdk 26.I0-03Зафиксировать версии AGP/Kotlin/JDK; не добавлять convention plugins и version catalog без реальной необходимости.I0-04СоздатьZapretApplication, ручнойAppContainer,MainActivityи четыре нижние вкладки-заглушки.I0-05Реализовать системную светлую/тёмную тему, Dynamic Color на API 31+ и встроенную палитру на API 26–30.I0-06Добавить базовый manifest с минимальными разрешениями; не запрашиватьWAKE_LOCKиREQUEST_IGNORE_BATTERY_OPTIMIZATIONS.I0-07Добавить debug/release build types, R8 и resource shrinking для release.
Ядро и CI
I0-08Добавить скрипт сборки libbox AAR и audit CLI из полного pinned SHA.I0-09Проверять, что HEAD исходника и embedded revision CLI равны pinned SHA.I0-10Первоначально выбрать для MVParm64-v8a; решение замененоI7-10после проверки размера универсального тестового APK.I0-11В CI запускатьsing-box checkдля всех файловtestdata/**/*.json.I0-12В CI запускать pinned Go tests и Android unit tests.I0-13Собирать debug APK в GitHub Actions и сохранять core version/revision рядом с artifact.I0-14Добавить license/NOTICE для приложения, sing-box-extended и включённых библиотек.
Gate 0
- Чистый checkout одной командой собирает debug APK.
- APK устанавливается и открывает четыре вкладки на API 26 и современной версии Android.
- CI подтверждает exact core revision и принимает 6/6 fixtures.
- В release manifest нет запрещённых разрешений, второго process или второго VPN service.
Этап 1 — профили и настоящий JSON
Цель: импортировать, безопасно хранить, редактировать и проверять JSON без VPN.
I1-01Создать пакетыprofiles/,config/,importer/внутриapp, без отдельных Gradle-модулей.I1-02РеализоватьProfileStore:files/profiles/index.json,<id>.json, одна<id>.json.bakчерезAtomicFile.I1-03Хранить в index только UI metadata; DNS, routes и outbounds остаются исключительно в JSON профиля.I1-04Реализовать атомарные create/read/update/delete/restore операции и очистку orphan temp-файлов.I1-05Реализовать импорт raw JSON из системного file picker и буфера после явного нажатия.I1-06Вызывать libboxCheckConfig()до сохранения изменённого профиля и показывать понятную ошибку.I1-07Реализовать JSON-tree editor наkotlinx.serialization.json, сохраняющий неизвестные поля.I1-08Сделать простой raw editor: моноширинный текст, поиск, format, validate, отмена несохранённых изменений.I1-09Добавить список профилей, выбор активного профиля, переименование, удаление с подтверждением и восстановление backup.I1-10Хранить тему, активный профиль и UI-настройки в DataStore; не копировать туда сетевую конфигурацию.I1-11РеализоватьManagedProfileFactory: маленькие base/protocol/selector builders вместо набора полноразмерных JSON-шаблонов.I1-12Для одиночной ссылки создавать один server outbound и selectorzapret-proxy; для subscription — несколько server outbounds в том же selector.I1-13Генерировать уникальные стабильные server tags без credentials; при совпадении имён добавлять детерминированный suffix.I1-14Сохранять выбранный сервер только вselector.defaultнастоящего JSON; не использовать DataStore илиexperimental.cache_file.I1-15Анализировать raw JSON: показывать существующие selector-группы без скрытой нормализации; managedzapret-proxyсоздавать только после явного выбора пользователя.I1-16Хранить профили только в app-private storage и выключить Android Auto Backup для файлов с credentials.
Тесты и Gate 1
- Unit: JSON round-trip и GUI edit сохраняют неизвестные extended-поля.
- Unit: сбой записи оставляет старый профиль читаемым; backup восстанавливается.
- Unit: malformed JSON не изменяет существующий профиль.
- Unit: index не содержит DNS/outbound/route objects или credentials.
- Unit + native instrumented: managed single/multi-server builders проходят
CheckConfig()и имеют валидные ссылки selector → server tags. - Unit: смена
selector.defaultне изменяет остальные outbounds, DNS, routes и unknown fields. - Instrumented: file picker и clipboard import работают на API 26 и современной ОС.
- Gate: пользователь может импортировать JSON, проверить, изменить, перезапустить приложение и получить тот же профиль.
Этап 2 — минимальный рабочий VPN
Цель: один профиль и выбранное приложение реально проходят через один TUN/libbox.
Per-app scope
I2-01Реализовать глобальный include allowlist в DataStore.I2-02Создать полноэкранный picker приложений: поиск, установленные пользовательские приложения, скрытые системные.I2-03Добавить встроенные suggestions для Instagram, YouTube, Telegram Stable/Beta/Direct, Telegram X, ZaStoGram и распространённых форков, WhatsApp, Discord, Signal, Chromium/Ultimatum и популярных браузеров; без TikTok. При первой инициализации также выбирать установленные Android browser handlers и обработчикиtg://, не поддерживая бесконечный список package вручную.I2-04Не запускать VPN при пустой effective allowlist или ошибкеaddAllowedApplication(); исчезнувшие/отключённые package пропускать до Builder, писать в bounded diagnostic log и продолжать при наличии хотя бы одного доступного приложения.I2-05Внутренне включать package Zapret KVN для health-check, не показывая его как пользовательский выбор.
Service и libbox
I2-06Создать один foregroundVpnService,VpnControllerи закрытый набор состояний подключения.I2-07Реализовать один service-lock, generation token и идемпотентныйstop().I2-08Интегрировать libbox в том же Android process; вызватьLibbox.SetMemoryLimit(false).I2-09Настроить releaseDebug=false, managedlog.level=warn,LogMaxLines=256, без runtime-лога на диск.I2-10Реализовать минимальный platform adapter: TUN PFD,protect(fd), package owner lookup и platform callbacks.I2-11Перед запуском очищать package include/exclude только в runtime-копии JSON и применять одну глобальную allowlist.I2-12Валидировать один TUN, IPv4+IPv6 full routes,auto_route, package conflicts и запрещённые bind/mark/netns-поля.I2-13ВыполнятьCheckConfig()доVpnService.Builder.establish().I2-14Реализовать foreground notification только с состоянием и действиями открыть/остановить; без live speed.I2-15При revoke/error/stop закрывать core, PFD, callbacks, streams и foreground notification ровно один раз.I2-16Получать selector-группы/текущий server через libbox и переключать активный server вызовомCommandClient.SelectOutbound()без restart TUN/core.I2-17Перед switch собрать JSON с новымselector.default, выполнитьCheckConfig()и атомарно сохранить; при ошибке runtime switch сделать один контролируемый restart.I2-18Для managed selector включитьinterrupt_exist_connections=true; проверить, что закрываются только proxy-соединения selector, аdirectи приложения вне TUN не затрагиваются.I2-18AПоверх exact core commit применять один проверяемый Android patch, вызывающий WireGuardDisableSomeRoamingForBrokenMobileSemantics()послеIpcSet; публиковать patch SHA-256 во всех build/release/diagnostic metadata и возвращать source checkout в чистое состояние после сборки. Physical A/B остаётся вP16.
Тесты и Gate 2
- Instrumented: выбранное приложение видно в TUN; контрольный невыбранный UID не виден и работает напрямую.
- Instrumented: проверены IPv4, IPv6, TCP, UDP и реальный Hysteria2/QUIC transport.
- Instrumented:
protect(fd) == false, revoke и ошибка послеestablish()приводят к полному stop. - Instrumented: ноль/два TUN, partial routes и конфликт package list отклоняются до подключения.
- Instrumented: server switch меняет внешний IP без пересоздания TUN; после restart остаётся выбранный server.
- Leak test: после 20 connect/stop нет роста PFD/threads/libbox instances; TUN, adapters и callbacks закрываются в каждом цикле.
- Gate: минимальный JSON-профиль подключается на API 26 и современной ОС, а невыбранный трафик не создаёт per-packet работу приложения.
Этап 3 — DNS, bootstrap и сетевой lifecycle
Цель: соединение либо полностью работает, либо полностью закрывается без сломанного DNS Android.
I3-01РеализоватьDefaultNetworkMonitorдля underlying non-VPN network без polling.I3-02Реализовать bootstrap resolver:DnsResolverна API 29+,Network.getAllByName()на API 26–28.I3-03Реализовать маленький LKG cache адреса proxy: fresh 24 часа, аварийный срок до 7 дней, исходное имя сохранять для TLS/Reality SNI.I3-04Детектировать Private DNS off/automatic/strict черезLinkProperties; системную настройку никогда не менять.I3-05Реализовать четыре режима GUI: Автоматически, DNS Android, Защищённый через VPN, Из JSON.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-адрес.I3-05BДобавить одну глобальную редактируемую пару DNS override для managed-режимов, по умолчанию включённую какntc.party → 130.255.77.28:hostsи точное правило стоят послеreject, но до resolver rules; «Из JSON», routing, TLS/SNI и встроенный DoH не меняются, diagnostic не раскрывает пару.I3-05CВ Auto использовать конечную цепочкуDNS профиля → DNS Android → DoH: переходить только после typed DNS health failure, полностью закрывать предыдущие core/TUN/callbacks, не переключаться на ошибке JSON/proxy/HTTPS и не давать fallback явным режимам.I3-06СоздатьRuntimeConfigBuilder, который добавляет толькоzapret-*overlays и не меняет сохранённый JSON.I3-07В явном Secure и последнем Auto-этапе использовать реальные IP,reverse_mapping, cache 4096 и три DoH (Cloudflare, Google, OpenDNS) черезfallback/parallel; exactsequentialне достигает резерва при зависшем основном DoH; FakeIP не создавать.I3-08Брать внутренний DNS черезTunOptions.GetDNSServerAddress()и передавать его вVpnService.Builder.addDnsServer().I3-09Перехватывать стандартный DNS правилом port 53 /hijack-dns; не обещать перехват DoT, встроенного DoH и mDNS.I3-10При strict Private DNS блокировать managed Auto/Secure доestablish(); DNS Android разрешать только при active+validated strict, иначе fail-close без plaintext fallback; «Из JSON» не переписывать.I3-11Реализовать последовательный health pipeline: proxy socket, DNS через TUN, HTTPS Cloudflare, затем Google и OpenDNS только после ошибок предыдущих endpoints.I3-12Показывать «Подключено» только после всех проверок; любая ошибка закрывает TUN.I3-13При смене сети или DNS/captive policy state обновлять underlying Network, сбрасывать transport и выполнять один контролируемый restart с debounce/generation token.I3-14Не добавлять периодический health-check, бесконечный retry или plaintext DNS fallback.I3-15Кнопку «Очистить DNS-кэш» реализовать контролируемым restart core, честно не обещая очистить Android resolver cache.
I3-13: baseline текущей сессии неизменяем. Transient callback A → B → A
отменяет pending restart, а итоговый policy key повторно проверяется после debounce.
Тесты и Gate 3
- Unit/core: четыре DNS fixtures проходят exact CLI; JVM-тест подтверждает managed
fallback/parallelбез FakeIP. - Unit: Auto выбирает DNS профиля, Android и DoH в фиксированном порядке, остаётся bounded и не скрывает non-DNS ошибки; явные режимы имеют ровно одну попытку.
- Core: воспроизводимый тест внутри exact pinned package проверяет success, transport error, hang с общим context и
NXDOMAIN/SERVFAIL/REFUSEDбез ошибочного fallback. - 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.
- Instrumented на AVD: реальные Wi-Fi, mobile, Wi-Fi ↔ mobile и IPv6; один контролируемый restart на каждую смену; captive-portal fail-close покрыт детерминированной fault injection до TUN.
- Physical lab: настоящий captive portal и IPv6-only/NAT64 на целевых устройствах/сетях.
- 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.
- 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.
I4-01Реализовать экран из карточек «Область VPN», «Правило трафика» и читаемый «Итог».I4-02Реализовать presets: Всё через VPN, Обход LAN, Только выбранные сайты, Россия напрямую, Россия через VPN, Пользовательский.I4-03Реализовать действия правила: Через VPN, Напрямую, Блокировать.I4-04Соблюдать порядок exact reject → direct exceptions → proxy rules → final.I4-05Не генерировать package route rules для обычной allowlist: Android уже выполнил это отсечение.I4-06Поставлять локальные binary.srsвнутри APK с manifest, version, license и SHA-256.I4-07Реализовать согласованное domain block: DNSreject+ routereject; IP/CIDR block: routereject.I4-08Не включать global sniff, legacy GeoIP/Geosite, remote managed rule-set или Android route exclusions.I4-09Реализовать advanced include/exclude mode с явным предупреждением; пустой exclude-list блокирует запуск.I4-10Дать GUI-редактор основных domain/IP/rule-set/outbound полей, редкое оставить raw JSON.I4-11После каждой GUI-операции показывать effective summary и diff управляемыхzapret-*объектов.
Тесты и Gate 4
- Core/fixture:
ru-rule-set.jsonиblock-rule.jsonпроходят exact CLI. - Instrumented: RU/non-RU domain и IPv4/IPv6 во всех presets.
- Instrumented: selected app получает proxy/direct/reject; unselected app всегда остаётся вне TUN.
- Instrumented: приложение со встроенным DoH демонстрирует задокументированное ограничение domain-only block.
- Performance: измерить cold start, lookup CPU/RAM и размер production
.srs. - 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.
I5-01Реализовать единыйImportParserбез отдельной внутренней модели маршрутизации.I5-02Поддержать обычный sing-box JSON и subscription, возвращающий JSON.I5-03Поддержать URI-листы минимум VLESS, VMess, Trojan, Shadowsocks, Hysteria2 и TUIC; каждый результат преобразовывать сразу в sing-box JSON.I5-04Поддержать plain/base64 subscription со списком известных URI; неизвестную строку не угадывать.I5-05Добавить URL import и ручное обновление subscription groups с preview до сохранения.I5-06Добавить QR scanner только по действию пользователя; камера запрашивается в момент открытия.I5-07Добавить импорт из буфера только после нажатия и импорт файла через системный picker.I5-08После успешного импорта не подключаться автоматически: предложить выбрать приложения, затем явную кнопку подключения.I5-09Перед запуском сканировать импортированный JSON наurltest, NTP, remote rule-set, external Clash controller, verbose log и explicit keepalive.I5-10Показывать одно предупреждение о фоновой/внешней активности; пользовательский JSON скрыто не очищать.I5-11Маскировать UUID, password, token, subscription query и URL credentials во всех preview/log/error.I5-12Не добавлять фоновое обновление подписок или ядра.I5-13При subscription refresh сохранять выбранный server tag, если он остался; иначе выбрать первый доступный и явно сообщить пользователю.I5-14Активный профиль после ручного refresh перезапускать только после подтверждения пользователя; не менять работающий server скрыто.I5-15Для одиночного URI предлагать «Новый профиль» или «Добавить в существующую managed-группу»; никогда не сливать профили автоматически.
Отложенные форматы импорта
-
F-IMPORT-WGРеализовать строгий импорт WireGuard/AmneziaWG 2.0 native.confнапрямую в sing-boxendpoints, native/core tests и открытие через AndroidACTION_VIEW; unknown-field drop запрещён. -
F-IMPORT-01Проверить спрос и корректную семантику Clash YAML. Реальный спрос и риск silent field loss подтверждены; точный core parser не экспортирован в libbox, поэтому второй YAML-конвертер не входит в MVP. Решение и gate зафиксированы в политике форматов. -
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 из политики форматов; не импортировать Clash DNS/routes/groups.
Здесь [x] у исследовательского пункта означает завершённый аудит, а не
наличие формата в APK.
Тесты и Gate 5
- Golden tests для каждого URI, IPv6 host, percent/base64 encoding, transport, TLS, Reality и пустых/битых полей.
- Fuzz/property tests: parser не падает и не зависает на произвольном вводе.
- 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 клиент, пригодный для ежедневного использования.
Главная и состояние
I6-01Реализовать компактную connection card: состояние, профиль, сервер, IP, ping и время.I6-01AПо нажатию на сервер открывать bottom sheet selector-группы с именем, протоколом, endpoint, session ping и отметкой текущего выбора.I6-02Использовать внутренний libbox traffic manager без external controller/listener.I6-03ПодписыватьCommandStatusс интервалом 1 секунда только пока главная в lifecycleSTARTED.I6-04Хранить 60 download/upload значений в кольцевом массиве текущей сессии.I6-05Никогда не включатьCommandConnections; boundedCommandLogоткрывать на время connect health-check и затем только на экране диагностики.I6-06IP запрашивать один раз после connect, ping — при connect и вручную.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 отсутствует.
Остальные экраны
I6-08Завершить Профили: группы, source, last update, add actions и ошибки.I6-09Завершить Маршрутизацию: две карточки, rules, app picker, advanced JSON.I6-10Завершить Настройки: тема, DNS, Stable/Beta, Диагностика, Сообщество, О приложении.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.I6-10CПроверить на физическом Android WireGuard/AWG с раздельными MTU: внешний TUN 1500 и внутренний endpoint 1280 по умолчанию; явный endpointmtuне переопределяется.I6-11Разместить Telegram-ссылки только в «Настройки → Сообщество».I6-12Добавить accessibility labels, нормальный back navigation, состояния loading/empty/error и крупные touch targets.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-тестом.
Диагностика
I6-14Показывать короткий тип ошибки и раскрываемые последние bounded log lines.I6-14AЗаписывать bounded event timeline каждой попытки подключения/restart: общая длительность, статусы этапов и самый долгий этап; только monotonic event timestamps, без ticker/polling.I6-14BАтомарно хранить только последний redacted uncaught Kotlin/Java crash вnoBackupFilesDir; runtime/core traffic logs на диск не писать. На API 30+ читать одну системную process-exit запись для native crash/ANR без копирования тяжёлого trace.I6-14CЗаменить head/tail startup-окно на priority ring: handshake/endpoint/TUN/error сохраняются, повторы схлопываются, один callback принимает не более 48 записей, а received/coalesced/dropped counters экспортируются.I6-15Создавать redacted diagnostic JSON только по действию пользователя.I6-16Экспортировать через Android Sharesheet/FileProvider и удалять временный файл при следующем запуске.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
- Compose UI tests для всех состояний главной и четырёх вкладок.
- Lifecycle test: после ухода с главной status ticker закрыт; после ухода из диагностики log stream закрыт.
- Rotation/process recreation не создаёт второй service/core и не теряет фактическое состояние VPN.
- Accessibility smoke test на светлой/тёмной теме и API 26/31+.
- 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 без динамического ядра и мусора.
I7-01Проверять GitHub Releases один раз при запуске и вручную: Stable принимает только обычный release, Beta — любой prerelease; при обновлении показывать release notes.I7-02Скачать APK во внутренний cache, проверить опубликованный SHA-256 и передать системному installer.I7-03Проверять package name и совместимость подписи; не обещать silent install.I7-04Удалять скачанные/незавершённые APK после install handoff, отмены, ошибки и при следующем запуске; diagnostic temp-файлы — при следующем запуске.I7-05Никогда не скачивать core отдельно: libbox обновляется только вместе с APK.I7-06Настроить release workflow: exact core SHA → CLI/AAR → tests → APK → checksum → metadata.I7-07Хранить signing secrets только в GitHub Secrets; сделать зашифрованную офлайн-копию ключа и инструкции восстановления.I7-08Публиковать release notes с app version, core tag, full core SHA, ABI и checksum.I7-09Проверить same-key upgrade с предыдущей версией и сохранение профилей/DataStore.I7-10Публиковать отдельныеarm64-v8a,armeabi-v7a,x86_64APK без чужих native-библиотек; updater выбирает первый совместимый ABI устройства.I7-11Вынести updater в направленныйapp-updaterlibrary-модуль. После 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.
Release workflow принимает строгие tags vMAJOR.MINOR.PATCH и -beta.N, выводит
детерминированный монотонный versionCode, собирает exact pinned core, тестирует, подписывает
отдельные arm64-v8a/armeabi-v7a/x86_64 APK секретами environment release, проверяет
apksigner/manifest/единственность ABI и публикует APK, SHA-256, metadata и notes один раз без замены уже опубликованных assets. Локальная сборка
этого bundle пройдена с одноразовым тестовым ключом.
I7-07 остаётся внешним действием владельца: workflow и инструкция готовы,
но настоящий постоянный ключ, две зашифрованные офлайн-копии и GitHub Secrets невозможно
создать честно без выбранного владельцем ключа и доступа к репозиторию.
Gate 7
- Неверный checksum, другая подпись, прерванная загрузка и downgrade обрабатываются безопасно.
- После update/cancel/restart cache не содержит старых APK.
- Release APK воспроизводимо содержит указанный core revision.
- 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. Последний пункт
Gate остаётся внешним до первого фактически опубликованного GitHub Release.
Этап 8 — обязательная выпускная матрица
Цель: доказать работу на Android, а не только корректность схемы и JVM/Go-кода.
Устройства и сеть
R8-01Реальное слабое устройство API 26/28.R8-02Android 10/API 29 AVD:DnsResolver, Private DNS boundary и 66/66 tests.R8-03Современный Android API 36 AVD: 66/66 tests; физическое устройство остаётся в energy/OEM gate.R8-04Wi-Fi, cellular, IPv4, IPv6/NAT64, captive portal и смена сети.R8-05Always-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.R8-10100 connect/stop и 50 Wi-Fi/mobile transitions пройдены на API 29/36 AVD без deadlock, duplicate restart и утечек.
Протокол и честная граница automated/physical прогона зафиксированы в
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Измерить принятый managedparallelпротив контрольногоsequentialна уникальных именах. Test 17 уже доказал функциональную регрессиюsequentialпри hang; AVD 5×12 готов, физическая энергия/cache burst открыты.R8-16Сравнить текущийmixedstack с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 и физическая энергия открыты.R8-19Harness сохраняет System Trace, batterystats и 130 raw-файлов с SHA-256; manual CI загружает их artifact. Физические PowerMetric/ODPM входят в открытые R8-11…R8-18.R8-20Порог 5% применяется агрегатором и проверяется negative/positive self-test вverify-project.sh.
Точный AVD-протокол и граница оставшейся физической проверки находятся в
GATE8_RESULTS.md. Harness дополнительно прошёл сокращённую матрицу
5 × 16 на API 26; это только проверка minSdk-совместимости, не energy evidence. Ни один
production default по AVD-данным не изменён.
Security и release candidate
R8-21Secrets проверяются до export во внутреннем bounded-log, в redacted export, foreground notification и app-private temp/history после явного чтения clipboard; stale diagnostics/APK очищаются отдельными тестами. Уведомление принимает только закрытый enum состояний.R8-22Managed runtime удаляет все listener/UI/secret поляexperimental.clash_api, не меняя сохранённый JSON. Явный raw user JSON остаётся под контролем пользователя.R8-23Exact merged-manifest audit проверяет permission allowlist, все exported-компоненты, signature-permission AndroidX, FileProvider, backup/cleartext/process/VPN contract иDebug=false.R8-24Arm64 release проверяет APK/R8 mapping и exact native symbols; minified x86_64 release на API 36 прошёл 5 cold starts (median 407 ms) без process crash.R8-25Локальный minified RC прошёл clean install, same-key update, downgrade/signature rejection. Последняя галочка остаётся за APK, подписанным постоянным release key на реальном устройстве непосредственно перед публикацией.
Финальный gate
- Нет failed fixture, unit или instrumented test: exact/compat fixtures 6/6, JVM 85/85, API 36 instrumented 67/67; API 26/29 baseline также пройден.
- Нет core revision/ABI/signature mismatch. Exact core и arm64 ABI проверены; временная подпись прошла same-key/foreign-key/downgrade tests. Нужна последняя проверка APK, подписанного постоянным production key, на реальном arm64-устройстве.
- Нет app-owned WakeLock, alarm/job, скрытого polling или бесконечного reconnect. CI проверяет manifest и production sources; разрешён только lifecycle ticker видимой главной и одноразовый debounce смены сети.
- Managed idle не создаёт периодический сетевой трафик: 5/5 окон дали 0 UID RX+TX и 0 status/log clients; физическая energy-матрица остаётся отдельным R8 gate.
- Невыбранный трафик не проходит через TUN/libbox: 5 × 8 MiB дали 0 UID RX+TX Zapret KVN и median 0 TUN bytes (единственный интерфейсный шум — 96 bytes).
- Все известные ограничения синхронно перечислены в UI, README и генерируемых
release notes; это проверяет
scripts/verify-project.sh. - После прохождения gate опубликован первый production GitHub Release. Тестовые debug pre-release не закрывают этот пункт.
Публичный gate пока намеренно не закрыт: обязательны production signing key, реальный
arm64 RC и незавершённая физическая матрица из GATE8_RESULTS.md. GitHub-репозиторий
youtubediscord/ZapretKVN-android уже выбран; внешний CI остаётся закрыть зелёным прогоном
текущего commit.
После MVP — только по измерениям и запросам пользователей
Эти пункты не входят в текущий final gate и не реализуются «на всякий случай». Галочка означает не наличие кода, а прохождение указанного ниже отдельного activation gate.
F-01Whole-app block как третье состояние приложения. Активировать после первого release и явного продуктового запроса; реализовывать как узкоеpackage_name → rejectвнутри TUN, не смешивая с Android include/exclude boundary.F-02Clash 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/systemstack в GUI. AVD-разница ниже 5%; оставить upstreammixed, пока физические API 26/современный Android не покажут устойчивый выигрыш или несовместимость хотя бы одного режима.F-05MTU 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-06Patch owner/process lookup. Сначала P4 benchmark текущего core и отдельной экспериментальной сборки на реальных устройствах; требуются существенный повторяемый выигрыш, собственный patch hash и полный regression/release matrix.F-07FakeIP. Только явно экспериментальный режим с отдельной ADR: per-profile ranges/cache identity, очистка при stop/delete/network change и тесты, исключающие сохранение FakeIP вне TUN. Managed default остаётся без FakeIP.F-08Remote rule-set updater. Только после подтверждённого сценария, политики лицензий/version/SHA/rollback и явного ручного UX; не добавлять скрытый worker/polling и не объединять с APK updater.
Текущее состояние
- Архитектурный аудит завершён 22 июля 2026 года.
- Exact CLI: 6/6 fixtures.
- Compatibility CLI: 6/6 fixtures.
- Go packages compile/tests pass; дополнительный audit test временно встраивается в exact pinned fallback package и проверяет success/error/hang/RCODE.
- Markdown fences, локальные ссылки и SHA-256 fixtures проверены.
- Android/Gradle-проект создан.
- Debug и R8 release APK собраны.
- Runtime smoke пройден на эмуляторах API 26 и API 36.
- Повторный аудит I0-08–I0-14: exact origin/tag/SHA, рекурсивные fixtures, arm64 release и pinned CI actions.
- JVM unit tests: 141/141 во всех модулях; добавлены Auto DNS fallback, GitHub updater/signing, security и Always-on/Lockdown policy.
- Android instrumented tests: текущие 67/67 пройдены на API 36; базовые 66/66 — на API 26/29, security delta 3/3 — на API 26.
- Hard process recreation:
scripts/verify-process-recreation.shпройден на API 26/36; после смерти процесса ноль session/core/TUN/callback/client, после нового connect ровно один экземпляр. - Packaged
.srs: exact CLI RU/non-RU domain/IPv4/IPv6, manifest/license/SHA, atomic repair; 50 089 байт, cold install 14 мс на AVD API 36. - Routing lookup/cold-start CPU/RAM measurements выполнены на AVD API 26/36 и exact core benchmark.
- Test 18 собран локально из commit
e637391для arm64-v8a, armeabi-v7a и x86_64 и опубликован отдельным GitHub prerelease с SHA-256/metadata. - Test 20 опубликован из commit
352fb31с редактируемым DNS override; Auto DNS fallback в него ещё не входил. - Idle CPU/battery release-gate выполнен на физических устройствах.
Следующее действие: установить Test 21 и на том же WireGuard-профиле проверить Auto:
диагностика должна закончиться на DNS профиля, а при его реальной ошибке показать ровно
один переход к DNS Android и лишь затем к DoH. «Из JSON» и DNS Android проверить отдельно
как явные режимы без скрытой подмены. После успешного подключения повторить
смену Wi‑Fi/mobile и длительную сессию; затем остаются физическая
матрица этапа 8 (captive portal, IPv6-only/NAT64, камера/HTTPS subscription,
blocked-DNS/LKG/DoH, OEM per-app/routing и энергия) и production signing key по SIGNING.md.