Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions docs/reviews/release-2026-10-05.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion scripts/release_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "-"]
Expand Down
12 changes: 12 additions & 0 deletions scripts/tests/test_release_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}),
Expand Down
Loading