diff --git a/docs/reviews/release-2026-10-05.md b/docs/reviews/release-2026-10-05.md new file mode 100644 index 0000000..259714f --- /dev/null +++ b/docs/reviews/release-2026-10-05.md @@ -0,0 +1,161 @@ +# Релизное тестирование Message487 — 2026-10-05 + +Проверен signed release-кандидат на двух выделенных эмуляторах по +[регламенту](../ru/release-testing.md). Базовый захват/доставка, независимые фильтры, +encrypted очередь и миграция подтверждены на API 26 и 35. В live release dry-run +найден HTTP 404; исправление в отдельной новой ветке fix/release-dry-run-repository-url. +Полная готовность релиза не подтверждена: исправленный workflow ещё требует live retest. + +## Кандидат и окружение + +- Commit: e0ae568763f9c34f7e6a7fec2f1866cb24386a99, main после PR #20. +- Package life.andre.message487, release, не debuggable; version 0.0.5/code 5. + Новая production версия для теста не создавалась. +- SHA-256 APK: 150d0aae9b85d6f41ad97d6363faaf8696d1147bfd12eee5a9821477f3749999. + APK прочитан обратно с обоих эмуляторов — hash совпал. +- Certificate SHA-256: 8a014a2a558a75b5f900ee0c33cd50f24b7432734912406699fc08866747f822. + Опубликованный baseline 0.0.4/code 4 имеет тот же ключ. +- minSdk 26, target/compile SDK 36, Build Tools 36.0.0; JDK 21, + Gradle 8.11.1, AGP 8.9.1, Kotlin 2.0.21. +- Кандидат собран штатным fastlane android release_artifacts с прежним локальным + ключом. Исправление Python-флоу не меняет проверенный код приложения. + +| Параметр | API 26 | API 35 | +| --- | --- | --- | +| Android / patch | 8.0.0 / 2018-04-05 | 15 / 2024-09-05 | +| Образ Google APIs | OSR1.180418.028.A3/10734686 | AE3A.240806.043/12960925 | +| Serial | emulator-5570 | emulator-5572 | +| ABI / страница | arm64-v8a / 4 KiB | arm64-v8a / 4 KiB | +| Экран / density | 1080×1920 / 420 dpi | 1080×1920 / 420 dpi | +| UI | EN, portrait/landscape, font 1.0/1.5, light/dark | EN, portrait/landscape, font 1.0/1.5, light/dark | +| Установка | ADB, signed APK | ADB, signed APK | + +Чистые AVD созданы специально для прогона. Личные телефоны и прежние AVD не +использовались. Samsung, foldable, carrier SMS, физический handover, 16 KiB и +естественные daily/weekly интервалы не проверены. + +HTTPS-фикстура слушала только loopback хоста. Отдельный test CA доверен в системном +trust store только этих AVD; на API 35 подготовлен Conscrypt APEX. Release APK и его +сетевые ограничения не менялись. Это laboratory system trust, не доверие user CA +на обычном телефоне. Для notification использован обычный probe APK с собственным UID. +Device requests сопоставлялись с ACK и локальной очередью; локальный n8n smoke +проверен отдельно. Telegram/downstream не проверялись. + +## Автоматические проверки + +- Штатный Android lane: 66 JVM/Robolectric/Compose тестов, debug/release lint, + debug/unsigned release сборки — PASS. +- Signed release lane: tests/lint/package/version/certificate validation — PASS. +- Python после фикса: 3 DevServer + 9 release automation tests, Black/isort — PASS; + добавлена регрессия repository metadata URL. +- Штатный n8n lane: 5 Node tests и smoke authorization/test/notification/SMS, + validation/error/invalid-ACK/timeout — PASS, n8n 2.38.1. Существующий контейнер не удалялся. +- [CI точного main кандидата](https://github.com/andre487/AndroidMessage487/actions/runs/37315255079) + — success; это не замена runtime. +- [Live read-only dry-run](https://github.com/andre487/AndroidMessage487/actions/runs/37320605546) + — FAIL до генерации changelog. 0.0.6 была только предполагаемым input: версия + проекта, release branch, PR, tag и Release им не создавались. + +## Матрица + +PASS «частично» относится только к описанной выполненной части. Остальное в +широком сценарии — NOT TESTED. JVM/исходники не заполняют runtime ячейку. +Полная четырёхдевайсная матрица этим прогоном не закрывается. + +| ID | API 26 | API 35 | Доказательства и ограничения | +| --- | --- | --- | --- | +| A01 | PASS | PASS | Commit/diff, version/SDK/toolchain и источник signed APK зафиксированы. | +| A02 | PASS | PASS | Штатные Android/Python/n8n lanes — host evidence, не runtime. | +| A03 | PASS | PASS | Release/package/code/certificate; installed APK hash совпал. | +| A04 | PASS, частично | PASS, частично | Manifest/permissions/backup/HTTP и обычный UID probe; полный license/F-Droid scan NOT TESTED. | +| A05 | PASS, частично | PASS, частично | Параметры двух чистых AVD записаны; телефоны/Samsung NOT TESTED. | +| A06 | N/A | N/A | Только созданные чистые AVD, без личных установок и сообщений. | +| A07 | PASS | PASS | Закрытая HTTPS ACK-фикстура; локальный n8n проверен отдельно. Device requests не шли в Telegram. | +| I01 | PASS, частично | PASS, частично | Чистая установка/холодный запуск: connection пустая, оба capture источника off, apps empty. Update traffic отдельно NOT TESTED. | +| I02 | PASS | PASS | Signed 0.0.4→кандидат: encrypted queue, installation ID, sources/packages, пауза и прежний адресат; после resume старая очередь и новые SMS/notification доставлены. API 35 настроен через UI. | +| I03 | PASS, частично | NOT TESTED | API 26: revoke/grant RECEIVE_SMS и штатный UI notification access revoke/ALLOW, no-capture и positive control. Initial Deny и полный permission UI API 35 NOT TESTED. | +| I04 | NOT TESTED | NOT TESTED | ADB/laboratory grants не доказывают restricted settings browser flow и Samsung Auto Blocker. | +| I05 | PASS, частично | PASS, частично | Навигация, UI switches/window, EN, portrait/landscape, font 1.0/1.5, light/dark. RU, narrow/foldable/themed icon NOT TESTED. | +| C01 | PASS, частично | PASS, частично | Обычный UID: text/BigText/Unicode, несколько notification IDs, запрос+ACK+queue ID. Lines-only/title-only NOT TESTED. | +| C02 | PASS, частично | PASS, частично | Ongoing/group summary и выключенные источники исключены. Empty-both/multiple ordinary apps NOT TESTED. | +| C03 | NOT TESTED | PASS, частично | На API 35 числовая synthetic-проба пришла как Sensitive notification content hidden; буквенные/Unicode пробы переданы полностью. Locked/work-profile/OEM NOT TESTED. | +| C04 | PASS | PASS | Single SMS, две SMS, Unicode и длинная 503-символьная SMS одним полным событием; source android и sender проверены. Сегментация emulator, не carrier. | +| C05 | PASS, частично | PASS, частично | Все четыре комбинации: фильтр on→1, off→2; окно 3s внутри/снаружи; UI независимых switches и окна. Exact boundary/zero/non-sliding/legacy preference migration — JVM, не runtime. | +| C06 | PASS, частично | PASS, частично | Off sources, pause capture, resume positive control; ручные test events не подавляются. Pause во время активного HTTP NOT TESTED. | +| Q01 | PASS | PASS | Signed HTTPS test/SMS/notification, Bearer/schema/source/sender/title, matching ACK; Journal без тела. Telegram не проверялся. | +| Q02 | PASS, частично | PASS, частично | Неправильный Bearer→401/BLOCKED; untrusted CA и hostname mismatch→NETWORK_ERROR/RETRY без HTTP; HTTP не сохраняется в release. Positive controls до/после; свои negative records удалены через UI. Redirect/пустой-token UI NOT TESTED. | +| Q03 | PASS, частично | PASS, частично | Wrong event_id→BLOCKED; manual retry после исправления fixture; generic malformed 2xx ACK off→HTTP_SUCCESS. Остальные ACK cases — JVM, не device. | +| Q04 | PASS, частично | PASS, частично | 500→RETRY, lost response→повтор с прежним ID/телом; затем acceptance. 408/425/429/timeout на device NOT TESTED. | +| Q05 | PASS | PASS | A→B: старый envelope остаётся на A, новый идёт в B; ID/JSON/URL проверены после retry. | +| Q06 | PASS, частично | PASS, частично | UI retry, detail event_id, accepted payload NULL. Burst/delete races NOT TESTED. | +| Q07 | PASS, частично | PASS, частично | Actual Keystore: encrypted pending, process kill/reopen и signed upgrade→доставка прежнего тела. Corruption/OOM NOT TESTED. | +| L01 | PASS, частично | PASS, частично | Два wifi/data offline→online цикла, pending и доставка; Home/reopen в soak. Physical handover/lock/OEM NOT TESTED. | +| L02 | PASS | PASS | Раздельные root kill и am force-stop, reopen сохраняет очередь; сеть/retry восстанавливают delivery. Захват во время stopped не заявляется. | +| L03 | PASS, частично | PASS, частично | Reboot с offline queue, unlock/reopen, payload сохранён и доставлен, новая notification. Locked boot/update schedule NOT TESTED. | +| L04 | PASS, частично | PASS, частично | Forced deep idle→pending сохранён→recovery; длительный foreground/background soak ниже. OEM/natural daily NOT TESTED. | +| L05 | PASS, частично | PASS, частично | Signed upgrade при encrypted pending и паузе, sources сохранены, старая/новая доставка. Upgrade во время активного HTTP NOT TESTED. | +| D01 | PASS, частично | PASS, частично | Actual logs/crash/ZIP исключают synthetic body/sender/token/URL; queued payload не содержит plaintext. Все system radio/response/device ID probes NOT TESTED. | +| D02 | PASS, частично | PASS, частично | Refresh, Prepare email, ZIP log/crash/environment, chooser Cancel; письмо не отправлено. Clear при pending/no-mail-client NOT TESTED. | +| D03 | PASS, частично | PASS, частично | am crash signed release: Java RemoteServiceException записана, application crash prompt. Не ANR/native/LMKD. | +| D04 | PASS, частично | PASS, частично | Ordinary UID SMS spoof/FileProvider/listener/job отвергнуты SecurityException. Private update-action forgery NOT TESTED. | +| U01 | PASS, частично | PASS, частично | Manual GitHub/F-Droid selection и metadata checks; actual F-Droid/browser installer NOT TESTED. | +| U02 | PASS, частично | PASS, частично | Оба live источника: No newer version для 0.0.5. Malicious metadata/APK — JVM; actual newer/download/consent NOT TESTED. | +| U03 | NOT TESTED | NOT TESTED | Нет более новой production версии; actual updater download/install и F-Droid client не запускались. Fixture release не публиковался. | +| U04 | NOT TESTED | NOT TESTED | Natural daily/weekly/reminder runtime не закрыт; host tests не заменяют device. | +| R01 | FAIL | FAIL | GitHub dry-run: repository URL с trailing slash дал 404. Local fix/regression/live canonical endpoint PASS; full fixed workflow NOT TESTED. | +| R02 | PASS, частично | PASS, частично | Host mock/integration head/base CI, merge/tag safeguards; будущий changelog/version release PR ещё не создан. | +| R03 | NOT TESTED | NOT TESTED | Новый release не публиковался; actual merged tag/assets и F-Droid publication NOT TESTED. | + +## Находка и retest + +**F01 — read-only dry-run блокируется HTTP 404.** В кандидате api("") строил +repos/andre487/AndroidMessage487/; живой GitHub возвращает 404. Endpoint без slash +возвращает metadata с allow_squash_merge=true. + +В отдельной новой ветке fix/release-dry-run-repository-url исправлен только +repository endpoint. Новый regression test фиксирует правильный read-only GET; +полный Python lane и вызов исправленного helper на живом GitHub PASS. +Это не полный workflow с RELEASE_BOT_TOKEN/OpenAI: нужна публикация исправления +и повтор R01. Исходный FAIL остаётся до live retest. + +Расхождения fixture не приняты за app bugs: Android 15 при прямой правке preferences +мог продолжать читать уже загруженное состояние listener. Controlled capture presets +повторены с реальным NotificationManager revoke/grant; migration API 35 повторена +через UI. Снимки SQLite учитывают WAL. Отсутствие записи только в main DB-файле не +считалось потерей очереди. Незавершённые попытки UI automation не заменяют конечные PASS. + +## Доказательства и восстановление + +Сырые материалы локальны: dist/release-test-2026-10-05/evidence — UTC +runtime-results.jsonl, requests.jsonl, build/CI logs, environment/APK identity, +screenshots/XML, SQLite/WAL, diagnostic ZIP, migration и negative probes. +Каталог закрыт (0700), чувствительные файлы 0600. Токены, TLS private keys, базы, +ZIP и raw logs не включаются в Git. Probe APK/source остаются в игнорируемом dist. + +Сеть, font/rotation/theme/battery восстанавливались после проб. +На каждом эмуляторе foreground/background soak длился 945 секунд (15 минут 45 секунд): +9/9 уникальных synthetic notification были приняты с matching ACK. Пробы чередовали +Home и возвращение в приложение, не использовали force-job для доказательства естественного +расписания. Это эмуляторное доказательство, не OEM/суточный тест. + +Refs, PRs и releases до/после dry-run совпали. Внешняя публикация не выполнялась. +Очистка завершена: pending queue 0 на обоих AVD перед остановкой; сеть, +battery/Doze, font/theme/rotation восстановлены. Probe удалён, оба созданных AVD +остановлены и удалены вместе с synthetic app data/Keystore/test CA. Три собственных +loopback HTTPS сервера остановлены, приватные TLS keys и временный Bearer token +удалены. ADB forwards/reverses не создавались. ADB после очистки пуст. + +Из существующего локального n8n через штатный REST API удалены только 14 executions +двух smoke-прогонов: отбор по двум временным окнам и synthetic marker либо явно +пустому Webhook body validation-пробы. Другие workflows/credentials/volumes не +менялись, n8n оставлен работать. Существующие AVD и остальные Docker контейнеры +сохранены. Остались signed APK/baseline и закрытые обезличенные raw evidence в dist; +Исправление и этот отчёт подготовлены в отдельной ветке fix/release-dry-run-repository-url. + +## Решение + +Ключевые проверки signed Android-кандидата на API 26 и 35 пройдены. +**РЕЛИЗ НЕ ПРОВЕРЕН** целиком: R01 исходного кандидата FAIL, full fixed GitHub +workflow ещё не retested. Остались телефоны/Samsung, официальный F-Droid +build/scanners/reproducibility, actual installer/update flow, natural daily/weekly +и перечисленные NOT TESTED. Не выпускать fixture-версию ради U03/R03/F-Droid. diff --git a/scripts/release_automation.py b/scripts/release_automation.py index de959b9..1352bde 100644 --- a/scripts/release_automation.py +++ b/scripts/release_automation.py @@ -54,7 +54,7 @@ def command(*args, input_text=None): def api(path, payload=None, method=None): repo = os.environ["GITHUB_REPOSITORY"] require(re.fullmatch(r"[\w.-]+/[\w.-]+", repo), "Invalid repository") - args = ["gh", "api", f"repos/{repo}/{path}"] + args = ["gh", "api", f"repos/{repo}" + (f"/{path}" if path else "")] args += ["--method", method or ("POST" if payload is not None else "GET")] if payload is not None: args += ["--input", "-"] diff --git a/scripts/tests/test_release_automation.py b/scripts/tests/test_release_automation.py index ee610b8..8d82aeb 100644 --- a/scripts/tests/test_release_automation.py +++ b/scripts/tests/test_release_automation.py @@ -81,6 +81,18 @@ def test_generator_sends_only_history_and_stats_with_strict_locale_schema(self): m.generate_notes('0.0.6', 'v0.0.5') request.assert_not_called() + def test_repository_metadata_uses_canonical_url_without_trailing_slash(self): + with ( + patch.dict(os.environ, {"GITHUB_REPOSITORY": "owner/repo"}), + patch.object( + m, "command", return_value='{"allow_squash_merge": true}' + ) as run, + ): + self.assertTrue(m.api("")["allow_squash_merge"]) + run.assert_called_once_with( + "gh", "api", "repos/owner/repo", "--method", "GET", input_text=None + ) + def test_api_mutations_use_explicit_http_method_and_structured_stdin(self): with ( patch.dict(os.environ, {"GITHUB_REPOSITORY": "owner/repo"}),