Spec: F102 — Android Performance Release Gate
Status: Derived from the canonical F102 CUE contract: partial. See the generated traceability view and gap view; Markdown does not carry an editable completion claim.
1. Problem & CEO Job
Job: Trust the signal — but an Android release that regresses cold start time from 1.5s to 4s erases trust in the assistant’s reliability. The CEO’s $150 Android is the primary Lumi device; performance regressions that slip to production damage daily usage before they are noticed.
Without a performance gate, each Android release is a blind deploy:
- Baseline profiles can drift stale without detection
- Cold start time can regress without anyone noticing until users churn
- No mechanism exists to distinguish “emulator validation” from “physical device truth”
- The release manager has no checklist to verify performance before signing off
- A stale or already-consumed
versionCodecan reach Gradle or Play - A Play API failure can be mistaken for an empty release history
- An upload can be reported as published without proving the committed track state
- A dev-backend AAB can be uploaded under the production package or to a non-internal track
2. User Stories & Acceptance Criteria
L1 — Code Gate (static verification)
- As a reviewer, I want CI to verify the benchmark module and baseline
artifact exist, so that a misconfigured build doesn’t silently skip benchmarks.
- CI job fails if
android/benchmarks/build.gradle.ktsis missing - CI job fails if
android/benchmarks/baselines/benchmark-baseline.jsonis missing - CI job fails if
android/benchmarks/check_benchmark_regression.pyis missing
- CI job fails if
L2 — Emulator Gate (deterministic benchmark)
- As a release engineer, I want an emulator-based benchmark that measures
cold start time deterministically, so that client-side regressions are caught
before release without needing a physical device.
-
ColdStartBenchmark.startupColdNoCompilation— cold start, no AOT -
ColdStartBenchmark.startupColdWithBaselineProfile— cold start with Baseline Profile -
ColdStartBenchmark.startupWarm— warm start from recent tasks -
PulseJourneyBenchmark.pulseTabNavigation— Pulse tab navigation - Benchmarks use debug signing, not release credentials
- CI fails if any metric exceeds baseline + tolerance_pct
-
L3 — Physical Device Gate (human-operated)
- As a release manager, I want a documented procedure for physical-device
verification, so that emulator-vs-device differences are accounted for.
- Physical device template exists at
android/benchmarks/baselines/physical-device-template.json - Procedure documented in
docs/performance/android-release-gate.md - ⚠ Not automated — requires a human with a reference phone
- Physical device template exists at
Sentry Crash Budget
- As a release manager, I want to check the current release’s crash-free
rate before promoting, so that a crashy build does not reach users.
- Sentry crash-budget check documented but not automated — requires
SENTRY_AUTH_TOKEN/SENTRY_DSNwhich are not available on CI runners - When credentials are missing, becomes a hard manual gate
- Sentry crash-budget check documented but not automated — requires
Baseline Profile
- As a release engineer, I want baseline profiles regenerated on the
deploy/frontend-devpromotion path, so that new code paths are covered before the dev workspace moves forward.-
BaselineProfileGeneratorexists in:benchmarksmodule - The AndroidX Baseline Profile plugin is wired to
:appand:benchmarks -
android-baseline-profiles.ymlinvokes the canonical:app:generateProdReleaseBaselineProfiletask onpixel6api31aosp - CI validates the generated variant profile and uploads it with managed-device diagnostics
-
Google Play Publication Integrity
- As a release engineer, I want publication to fail closed against live
Play state, so that a stale code or API outage cannot authorize an upload.
- Production is bound to
com.lumi.ai, production Firebase, andapi.runlumi.app; development is bound tocom.locuno.lumi.dev, Firebase projectlumi-dev-5c0a0, andapi-dev.runlumi.app - The dev publisher can commit only to the
internaltrack and rejects every other track before Gradle - Both release scripts verify the built manifest package and
BuildConfig.BASE_URLbefore uploading the exact new AAB - One uploaded edit adapts to deferred-review, automatic-review, and first-release draft requirements without re-uploading the consumed bundle
- The checked-in
versionCodemust exceed the live Play maximum before Gradle - Failure to query every requested package blocks the release
- The committed track is read through a fresh edit and must contain the exact code
- CI runs focused mocked-session proof for preflight and readback behavior
- Production is bound to
3. Scope Boundaries
-
In:
- Android Macrobenchmark module (
:benchmarks) - Cold start, warm start, and Pulse journey benchmarks
- Baseline Profile generator and
deploy/frontend-devpromotion-path regeneration - CI gate for emulator-based regression detection
- Checked-in baseline artifact with explicit emulator/device distinction
- Release-verdict documentation
- Regression checker Python script with unit tests
- Static verification of benchmark module existence in CI
- Fail-closed Play version-code preflight and exact post-commit track readback
- Per-environment identity alignment across Gradle, Firebase, backend, track, and publishing
- Android Macrobenchmark module (
-
Out:
- Physical device automated benchmarks (requires a device farm — not available)
- Automated Sentry crash-budget check (credentials not available on runner)
- iOS performance gate (separate concern)
- Network latency benchmarks (not client-side)
- GPU/rendering benchmarks (not part of this gate)
- Companion web or backend performance regression gate
4. Layered Gate Diagram
flowchart TD
L1["L1 Code Gate (CI)"]
L2["L2 Emulator Gate (CI)"]
L3["L3 Physical Device Gate (human)"]
Sentry["Sentry crash-free check"]
Verdict["Release verdict"]
L1 -->|pass| L2
L1 -->|fail| Block["Block — fix benchmark wiring"]
L2 -->|pass| L3
L2 -->|fail| Block2["Block — fix regression"]
L3 -->|recorded| Sentry
L3 -->|missing| ManualPass["⚠ Allowed if emulator gate passes
— physical gate documented as open"]
Sentry -->|≥ 99.5%| Verdict
Sentry -->|< 99.5%| Block3["Block — fix crashes"]
Sentry -->|credentials missing| ManualGate["Manual sign-off required"]
5. Implementation
Files created
| File | Purpose |
|---|---|
android/benchmarks/build.gradle.kts | Benchmark module build config |
android/benchmarks/src/main/AndroidManifest.xml | Benchmark module manifest |
android/benchmarks/src/main/kotlin/.../ColdStartBenchmark.kt | Cold/warm start tests |
android/benchmarks/src/main/kotlin/.../PulseJourneyBenchmark.kt | Pulse navigation test |
android/benchmarks/src/main/kotlin/.../BaselineProfileGenerator.kt | Baseline profile generator |
android/benchmarks/baselines/benchmark-baseline.json | Checked-in emulator baselines |
android/benchmarks/baselines/physical-device-template.json | Physical device report template |
android/benchmarks/check_benchmark_regression.py | Regression gate checker |
android/benchmarks/tests/test_benchmark_regression.py | Tests for regression checker |
.github/workflows/android-benchmark-gate.yml | Benchmark CI workflow |
docs/performance/android-release-gate.md | Release-verdict documentation |
Files modified
| File | Change |
|---|---|
android/settings.gradle.kts | Added :benchmarks module |
android/gradle/libs.versions.toml | Added androidxBenchmark and benchmark libraries |
android/app/build.gradle.kts | Added benchmark build type and baselineProfile dependency |
.github/workflows/android-baseline-profiles.yml | Wired to :benchmarks module tasks |
.github/workflows/android-ci.yml | Added benchmark module verification step |
deploy/DIAGRAMS.md | Added benchmark gate to CI/CD pipeline diagram |
docs/MVP_SCOPE.md | Added F102 registry entry |
docs/POLISH.md | Updated F51 with benchmark gate note |
6. Device Distinction
| Device | Type | Gate layer | Trust level |
|---|---|---|---|
| pixel6api31aosp | Emulator (GMD) | L2 — CI | Regression detection only |
| Pixel 7 (reference) | Physical | L3 — Human | Release verdict target |
| User devices (any) | Physical | — | Actual production performance |
Emulator benchmarks use swiftshader_indirect GPU, no thermal throttling simulation,
and no real network. They detect client-side code regressions, not absolute
performance. Physical device values are expected to differ significantly.
7. Test Plan
| Test | Location | What it verifies |
|---|---|---|
test_parse_benchmark_results_* | android/benchmarks/tests/ | JSON parser handles both value_ms and median_ms formats |
test_check_regression_* | android/benchmarks/tests/ | Gate correctly passes/fails based on baseline + tolerance |
verify_benchmark_module CI job | .github/workflows/android-benchmark-gate.yml | Benchmark module and baseline files exist |
AndroidPlayReleasePolicyTests | scripts/tests/test_android_play_release_policy.py | Live-version query failure blocks, preflight precedes Gradle, and track readback requires the exact code |
8. Diagrams & Docs Touched
- docs/performance/android-release-gate.md — new
- deploy/DIAGRAMS.md — updated with benchmark gate
- docs/MVP_SCOPE.md — added F102 entry
- docs/POLISH.md — updated F51 performance columns
9. Implementation Audit
| Check | Status |
|---|---|
| Benchmark module compiles | ✅ Verified — :benchmarks:compileDevDebugKotlin BUILD SUCCESSFUL |
| Emulator benchmarks execute | ⚠ Not verified — requires self-hosted runner with KVM |
| Baseline profile generates | ⚠ Not verified — requires emulator run |
| Regression checker passes with sample data | ✅ Verified — 9/9 pytest passed |
| CI workflow wired correctly | ⚠ Not verified — needs GitHub Actions test |
10. Bug / Change Log
2026-07-10 — Initial implementation
Type: Feature
Summary:
- Created
:benchmarksmodule with Macrobenchmark cold/warm start and Pulse journey tests - Created Baseline Profile generator with CI regeneration
- Created CI benchmark gate workflow with regression checking
- Created release-verdict documentation with explicit device distinction
- Updated registry, CI diagrams, and POLISH
Notes:
- Physical-device and Sentry crash-budget checks are documented but not automated — they require credentials or hardware not available on CI. Issue #1649 must remain open if this evidence is missing.
- Git SHA: (to be filled at commit time)
2026-07-10 — r02: verify, polish, commit, rebase
Type: Polish + verification
Summary:
- Verified benchmark module compiles (
:benchmarks:compileDevDebugKotlin) - Verified Python regression checker (9/9 pytest)
- Removed unused
ManagedVirtualDeviceimport from app/build.gradle.kts - Updated implementation audit table
- Committed and rebased onto origin/main
2026-07-14 — Contract Graph migration
Type: Contract ownership and docs reconciliation
Summary:
- Added the canonical F102 CUE contract and structural proof markers for benchmark wiring, regression checking, and release-verdict evidence boundaries.
- Corrected stale unchecked claims: the physical-device template, procedure, and Sentry check are documented; their external results remain open and blocking.
2026-07-20 — Fail-closed Play publication identity
Type: Release safety
Summary:
- Added a live Play maximum check before Gradle for the production publisher; at that date the dev-backend release script was build-only. ADR-051 supersedes that boundary on 2026-08-02 with an isolated internal-only dev app.
- Changed all-package query failure from a misleading zero result to a hard failure.
- Added exact post-commit track readback and focused unit proof in Android CI.
- Kept upload, edit commit, review, approval, and store availability as distinct states.
2026-08-02 — Isolated dev internal publisher
Type: Release safety and Android distribution identity
Summary:
- Bound dev to package
com.locuno.lumi.dev, Firebase projectlumi-dev-5c0a0,api-dev.runlumi.app, and the Play internal track only. - Added pre-Gradle Firebase/package/version checks and post-build manifest/BuildConfig identity verification to both publishers.
- Required signed dev releases and exact committed-track API readback; package, backend, and dev track are not operator-overridable.
- Preserved production package
com.lumi.aiand ADR-047’s replacement-app boundary.