Skip to content

Phase 8: release hardening - #10

Merged
hupcus merged 11 commits into
mainfrom
phase-8-release
Sep 25, 2026
Merged

hupcus merged 11 commits into
mainfrom
phase-8-release

Conversation

@hupcus

@hupcus hupcus commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 8 of PLAN.md — the release build, an emulator matrix, a battery protocol and the distribution prep.

  • Signing (ADR-034): env-var signing (DETECT pattern). No variables → unsigned release (CI, gates); a wrong path, a missing password or an active configuration cache → the build fails. The upload keystore lives outside the repo, its password in the macOS Keychain. tools/build_release.sh builds the APK + AAB from a clean tree only, and fails unless both carry the pinned upload certificate (046f8cd0…669c0e88). It then runs the APK check and prints the commit and SHA-256.
  • No logcat output in release: R8 strips every android.util.Log call. tools/check_release_apk.py (in CI) gates on the Landroid/util/Log; descriptor in the dex bytes, flags a debuggable APK and debug-recording literals, and treats an unparseable dexdump as a tool error.
  • minSdk 29 (ADR-033): Android 9 delivers a scroll-only service's events only from the "active" window, which it hardly ever updates for such a service. The API 28 emulator measured 0 events in 18 of 18 runs.
  • Language outside activities (ADR-035): the delete toast and the notifications came out in English inside the Czech app → Context.inAppLanguage(), with Robolectric tests.
  • AAB without language splits: a phone in a third language switched to English would otherwise lack the English strings (lint AppBundleLocaleChanges).
  • Docs:
    • emulator matrix API 28/30/33/34/35/36;
    • battery protocol (emulator-relative);
    • README release section;
    • Play internal-testing upload checklist.

Measured (emulators, release build — docs/accuracy-testing.md)

  • API 30 / 33 / 35 / 36:
    • onboarding (en / cs), measuring;
    • test list MAPE: View 0.00–0.33 %, Column 3.85–4.99 %;
    • SAF export, share, delete;
    • edge-to-edge;
    • the POST_NOTIFICATIONS prompt.
  • API 30: PIN + reboot. Nothing runs before unlock; afterwards the service binds by itself and the data is kept.
  • API 36: revoking Usage access switches the detail screen to scroll time, no crash.
  • Battery, API 34, 10 min each:
    • scrolling: 3 463 events, ≈ 1.35 s CPU (0.39 ms per event), 0 wakelocks / alarms / jobs, 115 flushes;
    • idle: ≈ 0.4 s CPU, 0 writes;
    • time-in-app sync ≈ 12 ms per app open.
  • Screenshots were reviewed by subagents; the only defect found (the English toast) is fixed.

SPEC §54 Definition of Done

  • aplikace se sestaví bez chyb — clean-checkout gate below
  • target API 36
  • AccessibilityService lze aktivovat — emulators API 30–36, OnePlus (Phase 1)
  • přijímá TYPE_VIEW_SCROLLED
  • načítá package name
  • načítá scrollDeltaX/Y
  • počítá vzdálenost
  • umí fallback přes scrollX/Y
  • má outlier protection
  • má automatickou kalibraci
  • má ruční kalibraci platební kartou — implemented; with a real card → verification debt V2/V3
  • ukládá agregace do Room
  • ukazuje Today / Week / Month / Lifetime
  • ukazuje breakdown podle apps
  • má history chart
  • má exclusions
  • má daily goal
  • má CSV export
  • má delete data
  • má privacy screen
  • má Google Play compliant disclosure flow
  • nečte text z jiných aplikací — canRetrieveWindowContent=false, typeViewScrolled only (manifest policy + PolicyGuardTest)
  • nevyžaduje internet
  • nevyžaduje účet
  • nepoužívá QUERY_ALL_PACKAGES
  • prošel testem Chrome (Phase 1)
  • prošel testem Instagram — V1 (no account on the test phone)
  • prošel testem Reddit — V1 (not installed)
  • prošel testem horizontal scroll
  • prošel testem fling
  • vlastní testovací screen má MAPE < 5 % — View ≤ 0.33 %, Column ≤ 4.99 % (automatic calibration); with a card → V3
  • po restartu aplikace zůstávají data zachována — emulator API 30 / 34; OnePlus (ColorOS) → V4

Battery on real phones (SPEC §39: 1 h / 8 h / 24 h) → new debt V8; ColorOS usage-access revocation + restart → V9.

Gates

GitHub Actions still refuse to start (account billing, see handoff), so this was checked on a clean detached checkout of f23891d with the CI commands:

  • testDebugUnitTest lintDebug assembleDebug assembleRelease: 265 JVM tests pass; lint 0 errors, 6 old warnings (SetTextI18n in the debug test list);
  • check_manifest_policy.py (debug + release): ok;
  • check_release_apk.py on the unsigned release: clean;
  • python3 -m unittest discover -s tools: 44 pass;
  • tools/build_release.sh end to end from a clean worktree: signed APK + AAB, certificate pinned, APK clean. The password appears in neither the log nor any of 1 278 build files.

Review

  • /topshit (cumulative diff): pinned the upload certificate for the APK and the AAB; the script accepts only a clean tree and prints the commit; build-tools are resolved in one place.
  • Stage 2 security (Fable):
    • MEDIUM: configuration-cache guard — fixed;
    • MEDIUM: the APK check could pass on an empty dexdump — fixed with the byte gate and a tool error;
    • MEDIUM: certificate pin — fixed by /topshit;
    • LOW: a Gradle daemon keeps the password in its environment — accepted and documented (only the same user can read it, and that user can read the Keychain item anyway);
    • areas without findings: manifest/permissions, CI workflow, ProGuard rule, inAppLanguage, device_accuracy.py.

🤖 Generated with Claude Code

hupcus and others added 10 commits September 24, 2026 18:29
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ck in CI

Release signing reads SIGNING_* environment variables only (DETECT pattern); without them the
release stays unsigned, a wrong path or a missing password fails the build. tools/build_release.sh
takes the password from the macOS Keychain and builds the APK + AAB without the configuration
cache. tools/check_release_apk.py reads the R8 dex through mapping.txt: not debuggable, no
ScrollMeter class calls android.util.Log, no debug recording literals. CI builds assembleRelease
and runs the check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…vice (ADR-033)

Measured on the API 28 emulator: with only ScrollMeter bound, no scroll event of our own test list
arrived (18/18 runs) and Settings got through only sometimes. AOSP 9 forwards TYPE_VIEW_SCROLLED
only from the active window, which moves only on TYPE_WINDOW_STATE_CHANGED — and apps send that
only when a bound service asks for it. Android 10 adds it to every non-cache service's relevant
types (without delivering it), so API 29+ works within the hard rules. UsageAccessChecker drops
its pre-29 branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…DR-033, ADR-034)

With minSdk 29 R8 merged androidx.profileinstaller.ProfileInstaller$2 (which logs) into
CalibrationRepository$Companion, so a check that attributes Log calls to classes cannot tell
ours from library code. The release now declares android.util.Log side-effect free (isLoggable
false) and check_release_apk.py requires no Log invocation anywhere in the dex. device_accuracy.py
no longer re-reads a stale UI dump when uiautomator fails. ADR-033 (minSdk 29), ADR-034 (release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ne's (ADR-035)

Android 13+ applies the per-app language to activity contexts only. The erase toast (application
scope) and AndroidNotificationPoster built their text from the application context, so a Czech
app on an English phone said "All measured data is deleted." (API 33 and 36 emulators) and
would have notified in English. Context.inAppLanguage() applies LocaleManager's app locales
through createConfigurationContext; Robolectric tests pin both paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dumps only our package

The result lines above "Vynulovat" wrap as numbers grow and move the button (API 30 emulator:
runs silently added up). `dumpsys activity top` timed out on a frozen cached app (API 35), so the
View hosts are read from `dumpsys activity <package>`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, gate Log on dex bytes

Review findings on the Phase 8 diff (/topshit + Stage 2 security review):
- build_release.sh fails unless both the APK (apksigner) and the AAB
  (keytool -printcert -jarfile) carry the upload certificate from ADR-034,
  refuses a working tree with changes and prints the commit it built.
- app/build.gradle.kts refuses a signed build while the configuration
  cache is active, before any password is read: the cache would store
  the environment variables' values in plain text under .gradle/.
- check_release_apk.py gates on the Log descriptor in the dex bytes
  (independent of dexdump's format), treats a dump without method code
  as a tool error (exit 2) and picks the newest installed build-tools,
  so CI no longer computes it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…upload checklist

- accuracy-testing.md: emulator matrix API 28-36 (release build), why
  Android 9 is not supported, harness notes, battery protocol on the
  API 34 emulator (scroll, idle, time-in-app sync) with its limits.
- README: status through Phase 8, Android 10+, release build and the
  upload certificate fingerprint.
- CLAUDE.md: the emulator AVD and tools/build_release.sh.
- play-listing.md: internal testing upload checklist for Honza.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An app bundle's language splits follow the phone's languages, so a
phone in a third language switched to English for this app (Android 13
per-app language) would lack the English strings. Lint
AppBundleLocaleChanges flagged it; the bundle's BundleConfig now has the
LANGUAGE split dimension negated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hupcus
hupcus merged commit 5020ed9 into main Sep 25, 2026
1 check failed
@hupcus
hupcus deleted the phase-8-release branch September 25, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant