Google Play readiness — Cadence
Subject: the app repository at pinned commit 03a176e72ef0075eec86b8915cbe6e93042a3b9d, version 0.4.12+18.
Stream: Phase 2.1 (Google Play), plus the Play half of 2.6.
All policy retrievals: 2026-08-04 (UTC timestamps in every capture header).
Method: every Google page was rendered with utilities/chrome.py (Playwright headless Chromium) per AGENT_RULES R4 — never WebFetch, never requests.get(). Raw captures live in proof/02_store/captures/ with an index at MANIFEST_google_play.md in that folder. Google's help pages hide their requirement tables inside collapsed accordions that the rendered text drops, so every capture also has a <slug>_expanded.txt with the accordions flattened; quote from that file.
Repo facts were re-measured, not assumed: proof/02_store/repo_state.txt, repo_state2.txt, repo_state3.txt, merged_manifest.txt (each stamped by proof/run_and_record.sh [not published] with command, cwd, UTC time, Flutter version, git sha and tree state).
0. The one fact that frames everything
https://play.google.com/store/apps/details?id=dev.sergemio.cadence&hl=en&gl=FR returns HTTP 404 (capture gp_store_listing_cadence_probe.txt, retrieved 2026-08-04T10:46:37Z). Cadence has never been published on Google Play. Nothing on the store side exists yet: no account, no listing, no upload key, no declarations. Every "PRESENT" below is a property of the code and build, not of a Play Console that does not exist.
1. Account, identity, payments and the EU trader question
| # | Requirement | Status | Evidence | Source (retrieved 2026-08-04) |
|---|---|---|---|---|
| 1.1 | A Google Play developer account exists | MISSING | App is 404 on Play; release build type still carries the Flutter template's // TODO: Add your own signing config and points at the debug keystore (android/app/build.gradle.kts:32-34) — no artifact has ever been accepted by Play |
gp_store_listing_cadence_probe.txt |
| 1.2 | US$25 one-time registration fee, paid by credit or debit card (MasterCard, Visa, American Express) | MISSING | Not paid; no account | https://support.google.com/googleplay/android-developer/answer/6112435 — "There is a US$25 one-time registration fee" (gp_registration_fee_expanded.txt:41) |
| 1.3 | Account type chosen: personal or organisation | MISSING | Undecided. This is the single highest-leverage decision on the Play side — see §2 | https://support.google.com/googleplay/android-developer/answer/13634885 ; https://support.google.com/googleplay/android-developer/answer/13628312 |
| 1.4 | Personal account: developer name, legal name, legal address, contact email, contact phone, developer email | MISSING | — | answer/13628312, "Creating a developer account for personal use" |
| 1.5 | Organisation account: D-U-N-S number, organisation name, address, phone, website, contact name/email/phone, developer email, developer phone | MISSING | — | answer/13628312. "This process can take up to 30 days so you should plan ahead. You will not be able to create a developer account for an organization without one." |
| 1.6 | Identity verification before publishing (government ID and a credit card in the legal name for personal accounts) | MISSING | — | answer/6112435: "you may be asked for a valid government ID and a credit card, both under your legal name. If this information is determined to be invalid, your registration fee will not be refunded." |
| 1.7 | Google payments profile linked at account creation, holding the legal name and address | MISSING | — | answer/13628312, "Linked Google payments profile": "must be kept up to date to keep your developer account in good standing." |
| 1.8 | France is a supported location for developer registration | PRESENT (jurisdiction is eligible) | France row: developer registration ✔, merchant registration ✔, default currency EUR | https://support.google.com/googleplay/android-developer/answer/9306917 (gp_payments_profile_expanded.txt:319-322) |
| 1.9 | Merchant account + bank account + tax information | MISSING, and only needed if the app monetises | Repo has zero billing code (no billing, no IAP, no com.android.vending.BILLING) — see proof/01_findings/S13/network_grep_lib.txt §4 and the merged manifest permission list |
answer/13628312: "If you decide to monetize on Google Play, you will need to create a merchant account." Payments-profile flow: https://support.google.com/googleplay/android-developer/answer/7161426 |
| 1.10 | Bank verification (deposit challenge or uploaded bank documents), if monetising | MISSING/deferred | — | answer/13628312: "Verification can take up to 5 days, so we recommend starting the process early" |
| 1.11 | Store listing contact email (mandatory to publish) | MISSING | No support email, no website, no mailto: anywhere in the repo — proof/02_store/repo_state.txt §5 prints NO_PRIVACY_OR_SUPPORT_URL |
https://support.google.com/googleplay/android-developer/answer/13634081: "An email address is required to publish or update apps on Google Play." |
| 1.12 | EU trader disclosure (Digital Services Act) | MISSING, and it is not a separate form on Play | See the finding below | answer/13628312 |
1.12 in full — what a French seller actually has to provide on Google Play
Google Play has no separate trader/non-trader declaration in Play Console Help. Four independent site searches for it returned nothing (gp_search_trader_status, gp_search_trader_only, gp_search_dsa_trader2, gp_search_eea_consumer_law); the only hit is a community-forum thread, which is not an official source. Google's own "General conditions of access for Google Play in the EEA" (https://support.google.com/googleplay/android-developer/answer/14659200) lists the developer's EEA obligations as exactly four: accept the Play Console Terms of Service, pay the one-off US$25 fee, choose an organisational or personal account, verify developer identity information, plus the testing requirement if personal. No trader toggle.
Google satisfies DSA Article 30 traceability automatically, from the payments profile, by publishing the developer's identity rather than by asking for a declaration:
"Google will display your legal name, your country (as per your legal address), and developer email address on Google Play. If you decide to monetize on Google Play then Google will display your full address." — answer/13628312, "Creating a developer account for personal use"
For an organisation: "Google will display your legal name, legal address, developer email address, and developer phone number on Google Play."
Concrete consequence for a French seller. If Serge registers a personal account and ever charges for the app, his home address is published on the Cadence store listing. That is not avoidable by a setting; it is a direct consequence of monetising under a personal account. An organisation account (a French SAS/SARL, or a registered entreprise individuelle if Dun & Bradstreet will issue it a D-U-N-S) publishes the company address instead, and additionally publishes a developer phone number. This is the same trade-off Apple imposes, with one difference worth noting: Apple has an explicit DSA trader declaration screen (captured by the market stream at proof/03_market/captures/policy_apple_asc_dsa_trader.txt), Google does not.
Note the asymmetry with §2: the organisation account that protects the home address is also the account type that is exempt from the 12-tester/14-day gate. The two decisions are the same decision.
2. The new-personal-developer-account testing requirement
The exact current numbers, from the official source.
"If you have a newly created personal developer account, you must run a closed test for your app with a minimum of 12 testers who have been opted-in for at least the last 14 days continuously. When you meet these criteria, you can apply for production access on the Dashboard in Play Console." — https://support.google.com/googleplay/android-developer/answer/14151465, retrieved 2026-08-04T09:53:49Z, capture
proof/03_market/captures/policy_google_personal_testing_reqs.txt
| # | Condition | Detail | Source |
|---|---|---|---|
| 2.1 | Who it binds | Personal accounts created after November 13, 2023. Organisation accounts are not subject to it. | answer/14151465 |
| 2.2 | Tester count | 12 testers minimum, opted in at the moment you apply | answer/14151465 |
| 2.3 | Duration | 14 continuous days opted in. Google warns explicitly: "Emphasize to your testers that they need to remain opted in to your closed test for at least 14 days continuously." | answer/14151465 |
| 2.4 | What is locked until you pass | Production and Pre-registration are disabled in Play Console | answer/14151465 |
| 2.5 | After the 14 days | Apply for production access on the Dashboard; answer three sections of questions (about the closed test, about the app, about production readiness) | answer/14151465 |
| 2.6 | Google's review of that application | "This usually takes 7 days or less, but may occasionally take longer." | answer/14151465 |
| 2.7 | Failure mode | "If your app isn't ready to be published, you may be required to continue testing your app. Examples include not having 12 testers opted-in to your closed test, or your testers not being engaged with your app during your closed test." | answer/14151465 |
| # | Requirement | Status | Evidence |
|---|---|---|---|
| 2.0 | Closed test with 12 testers opted in for 14 continuous days, then production access granted | MISSING | No account, no closed test, no testers — app is 404 on Play |
What this does to the timeline. On a personal account the shortest possible path to production is: account creation and identity verification → build a signed AAB → complete every App content declaration → publish to closed testing → recruit 12 Google-account holders and keep them opted in → wait 14 days → apply → wait up to 7 days for Google's answer. The 14 days do not start until the closed test is live, and Google grades engagement, not just headcount — a dozen accounts that install and never open the app is the named failure case. On an organisation account, none of this applies and the app can go straight to production once the declarations are complete. The D-U-N-S lead time (up to 30 days) is the price of skipping it, and the two waits do not stack usefully: apply for the D-U-N-S first and the testing gate disappears; skip it and you buy the 14-day gate plus the published home address from §1.12.
3. App signing
| # | Requirement | Status | Evidence | Source (retrieved 2026-08-04) |
|---|---|---|---|---|
| 3.1 | Release artifact is not signed with a debug certificate | MISSING — this is a hard blocker | android/app/build.gradle.kts:31-35 sets signingConfig = signingConfigs.getByName("debug") under // TODO: Add your own signing config. Both artifacts on disk carry it: the APK (apksigner: CN=Android Debug, O=Android, C=US) and the AAB — openssl pkcs7 on META-INF/ANDROIDD.RSA inside build/app/outputs/bundle/release/app-release.aab prints subject=CN=Android Debug, O=Android, C=US (proof/02_store/repo_state3.txt §B) |
https://developer.android.com/studio/publish/app-signing — "Because the debug certificate is created by the build tools and is insecure by design, most app stores (including the Google Play Store) do not accept apps signed with a debug certificate for publishing." (and_app_signing_expanded.txt:766-768) |
| 3.2 | Upload keystore exists | MISSING | git ls-files for keystore|.jks|key.properties|.p12|.pepk → TRACKED_SIGNING_MATERIAL=none; on-disk check → ON_DISK_SIGNING_MATERIAL=none (proof/02_store/repo_state.txt §2) |
— |
| 3.3 | Upload key format: Java keystore (.jks/.keystore), RSA 2048 bits or more |
MISSING | No key | https://support.google.com/googleplay/android-developer/answer/9842756 — "Requirement: Must be an RSA key of 2048 bits or more." |
| 3.4 | Play App Signing enrolment | MISSING (and automatic on first upload) | — | answer/9842756, "For new apps": "Your app will be automatically enrolled in quantum-ready, hybrid signing with Google-generated keys." Google-generated app signing keys are RSA 4096-bit. Accepting the Play App Signing Terms of Service is one of the "Declarations" at app creation (https://support.google.com/googleplay/android-developer/answer/9859152). |
| 3.5 | Signing material never committed | PRESENT (the guard, not the key) | android/.gitignore:12-14 already lists key.properties, **/*.keystore, **/*.jks, under the comment "Remember to never publicly share your keystore." (proof/02_store/repo_state.txt §3) |
https://docs.flutter.dev/deployment/android — "keep the keystore file private; don't check it into public source control!" |
| 3.6 | Gradle wired to the release key | MISSING | Only the debug config is wired; there is no signingConfigs { create("release") ... } block in android/app/build.gradle.kts |
https://docs.flutter.dev/deployment/android, "Configure signing in Gradle" |
3.a The exact commands
Generate the upload keystore (macOS; verbatim from https://docs.flutter.dev/deployment/android, retrieved 2026-08-04T10:41:31Z, flutter_android_deployment_expanded.txt:789-790):
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
-storetype JKS -keysize 2048 -validity 10000 -alias upload
-storetype JKS is required on Java 9 and newer, where the default became PKCS12 — the toolchain baseline for this project is openjdk version "17.0.20" 2026-07-21 (proof/00_toolchain/java_version.txt), so keep the flag. If keytool is not on the path, run flutter doctor -v, take the path printed after "Java binary at:", and replace the trailing java with keytool (same source).
Then [project]/android/key.properties (never committed; already gitignored):
storePassword=<password-from-previous-step>
keyPassword=<password-from-previous-step>
keyAlias=upload
storeFile=upload-keystore.jks
Then android/app/build.gradle.kts — verbatim from the same page's Kotlin code blocks. Before the android block:
import java.util.Properties
import java.io.FileInputStream
plugins {
...
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
...
}
and inside android, replacing the debug line rather than sitting beside it:
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
Flutter's own documentation shows both signingConfig lines stacked, because it is illustrating a diff. Do not leave both: delete the getByName("debug") line. Two assignments to the same property is exactly the "additive patch" failure mode where the loose default survives and the tightening becomes invisible.
Proof the fix worked — the same command that produced the current failure:
apksigner verify --print-certs build/app/outputs/flutter-apk/app-release.apk
must stop printing CN=Android Debug and print the upload key's DN instead. For the bundle, unzip -p build/app/outputs/bundle/release/app-release.aab META-INF/ANDROIDD.RSA | openssl pkcs7 -inform DER -print_certs -noout must do the same.
3.b What happens to today's artifacts
Both are dead on arrival. build/app/outputs/flutter-apk/app-release.apk (53,629,091 bytes, sha256 f11a484d…) and build/app/outputs/bundle/release/app-release.aab (51,974,023 bytes, sha256 89ac5401f8a11bff81b5c29479b0ba036828d30f8b24342bbc0c681ca57f55bb) are both signed CN=Android Debug. Play will not accept either. They are useful only as a demonstration that the build pipeline works end to end; delete them and rebuild once the release signing config lands. There is no salvage path — a debug-signed artifact cannot be re-signed into a valid upload, because the upload key must be the one whose certificate Google registers on first upload.
4. Format and technical requirements
| # | Requirement | Status | Evidence | Source (retrieved 2026-08-04) |
|---|---|---|---|---|
| 4.1 | Android App Bundle (.aab), not APK |
PRESENT | build/app/outputs/bundle/release/app-release.aab exists, 51,974,023 bytes, with base/manifest/AndroidManifest.xml and BUNDLE-METADATA/ debug symbols for all three ABIs (proof/02_store/repo_state3.txt §A) |
https://support.google.com/googleplay/android-developer/answer/9859152 — "Google Play uses Android App Bundles to generate and deliver APKs"; https://support.google.com/googleplay/android-developer/answer/9859372 — "Starting August 2021, new apps are required to publish with the Android App Bundle on Google Play." |
| 4.2 | Target API level | PRESENT | targetSdkVersion=36 read out of the shipped AAB's protobuf manifest and confirmed in the packaged release manifest: android:targetSdkVersion="36" (proof/02_store/repo_state3.txt §D, proof/02_store/merged_manifest.txt) |
https://support.google.com/googleplay/android-developer/answer/11926878 — "Starting August 31, 2026: New apps and app updates must target Android 16 (API level 36) or higher to be submitted to Google Play" |
| 4.3 | Deadline exposure on target API | PRESENT — no exposure | Already at 36, the level required from 2026-08-31. Today is 2026-08-04, so the app clears the deadline 27 days before it lands, and no extension request (available to 2026-11-01) is needed. | answer/11926878 |
| 4.4 | minSdk | PRESENT | android:minSdkVersion="24" in the packaged release manifest |
— |
| 4.5 | 64-bit native code | PRESENT | ABIs inside the release APK: arm64-v8a, armeabi-v7a, x86_64 (proof/02_store/repo_state2.txt §D). arm64-v8a satisfies the requirement; armeabi-v7a is the permitted 32-bit companion. |
https://developer.android.com/google/play/requirements/64-bit |
| 4.6 | Base-module download-size limit 500MB | PRESENT | 51,974,023 bytes ≈ 50 MiB, an order of magnitude under | https://support.google.com/googleplay/android-developer/answer/9859372 — base module 500MB; total across all modules and install-time asset packs 4GB; overall ceiling 34 GB |
| 4.7 | Under the 200MB mobile-data warning threshold | PRESENT | Same figure | answer/9859372 — "If your app is above 200MB in size, users on a mobile data connection will see a non-blocking dialog when installing" |
| 4.8 | Package name is final and correct | MISSING — a decision, not a file | applicationId = "dev.sergemio.cadence" (android/app/build.gradle.kts:21), still under the Flutter template's // TODO: Specify your own unique Application ID comment (line 20). Confirmed in the packaged manifest: package="dev.sergemio.cadence". |
https://support.google.com/googleplay/android-developer/answer/9859152 — "Package names for app files are unique and permanent, so please name them carefully. Package names can't be deleted or re-used in the future." |
4.8 in full — the irreversible identifier
dev.sergemio.cadence bakes two unresolved things into a permanent string: a personal namespace (sergemio) and a product name that README.md:38 itself calls a placeholder. Once an AAB with this applicationId is accepted by Play, the name is spent forever — not renameable, not reusable, not deletable. If the product ends up called something else, or if the business ends up owned by a company rather than by Serge personally, the store listing will still be served from a package named after neither. Changing it later means a brand-new listing with zero installs, zero reviews and zero ratings.
This decision must be made before the first upload to any track, including internal testing.
5. Permissions and policy — the highest-risk section
The governing document is Google Play's Permissions and APIs that Access Sensitive Information policy. Note the URL: the old ID 13161072 now redirects to https://support.google.com/googleplay/android-developer/answer/16558241 (capture gp_exact_alarm_policy.txt, final URL recorded in its header). Cite the destination.
| # | Requirement | Status | Source (retrieved 2026-08-04) |
|---|---|---|---|
| 5.1a | USE_EXACT_ALARM is policy-eligible for this app, and SCHEDULE_EXACT_ALARM is correctly capped at maxSdkVersion="32" |
PRESENT | answer/16558241 |
| 5.1b | Exact-alarm declaration filed in Play Console | MISSING | answer/16558241, Key Considerations |
| 5.2a | USE_FULL_SCREEN_INTENT is policy-eligible for auto-grant (core function = setting an alarm) |
PRESENT | answer/16558241 ; answer/13392821 |
| 5.2b | Full-screen-intent declaration filed (https://goo.gle/play-permission-decl-form) |
MISSING | answer/13392821 |
| 5.2c | Runtime check and graceful degradation if the permission is not granted | MISSING | answer/13392821 |
| 5.3 | POST_NOTIFICATIONS requested at runtime and handled on refusal |
PRESENT | https://developer.android.com/develop/ui/compose/notifications/notification-permission |
| 5.4 | RECEIVE_BOOT_COMPLETED — normal permission, no declaration |
PRESENT | answer/16558241 (absent from the restricted list) |
| 5.5 | VIBRATE, WAKE_LOCK, MODIFY_AUDIO_SETTINGS — normal permissions, no declaration |
PRESENT | answer/16558241 (absent from the restricted list) |
| 5.6 | No foreground service, so no Android 14 FGS declaration and no demo video | N/A | answer/13392821 |
| 5.7 | No INTERNET permission |
PRESENT | proof/01_findings/S13/apk_permissions.txt; proof/02_store/merged_manifest.txt |
The detail behind each row follows.
5.1 USE_EXACT_ALARM — ELIGIBLE
The policy names the acceptable use cases exhaustively:
"Your app must use the
USE_EXACT_ALARMfunctionality only when your app's core, user facing functionality requires precisely-timed actions, such as: - The app is an alarm or timer app. - The app is a calendar app that shows event notifications." — answer/16558241, "Acceptable use cases for using the Exact Alarm Permission" (gp_exact_alarm_policy_expanded.txt:625-630)
Cadence is a timer app and nothing else: pubspec.yaml:2 describes it as "Cadence — Kitchen Timer. Pro multi-timer board for restaurant kitchens." The permission drives AlarmManager.setAlarmClock backstop alarms at each running timer's final deadline (lib/alarm_backstop.dart:1-15, :192-194). This is the first bullet, verbatim. The permission is policy-eligible.
Two conditions still attach:
- The policy summary adds that
USE_EXACT_ALARM"is a highly restricted permission… Apps that request this restricted permission are subject to review, and those that do not meet the acceptable use case criteria will be disallowed from publishing on Google Play." Eligible does not mean unreviewed. - Key Considerations, "Do": "Complete Play Console declaration to indicate app functionality." Status: MISSING — no declaration can exist without an account.
SCHEDULE_EXACT_ALARM android:maxSdkVersion="32" (android/app/src/main/AndroidManifest.xml, confirmed in the packaged manifest) is the correct API 31-32 fallback and is the permission Google explicitly points at for apps that don't qualify. Cadence carries both, correctly scoped. PRESENT.
5.2 USE_FULL_SCREEN_INTENT — ELIGIBLE, but the declaration is mandatory and the code does not handle refusal
"For apps targeting Android 14 (API target level 34) and above,
USE_FULL_SCREEN_INTENTis a special apps access permission. Apps will only be automatically granted to use theUSE_FULL_SCREEN_INTENTpermission if the core functionality of their app falls under one of the below categories that require high priority notifications: - setting an alarm - receiving phone or video calls" — answer/16558241, "Full-Screen Intent Permission" (gp_exact_alarm_policy_expanded.txt:662-670)
And the operational rule, with dates:
"If you use the
USE_FULL_SCREEN_INTENTpermission, you are required to complete the Play Console declaration starting May 31, 2024 to indicate if your app has a permitted core functionality and qualifies for automatic granting. Starting January 22, 2025, for apps targeting Android 14+, only apps that have calling or alarm functionalities will have this permission enabled by default. Otherwise, you must get user permission… For apps that did not complete the declaration or have not been approved for default enabling, developers will need to prompt users to grant permission on new installs and gracefully degrade the experience if denied." — https://support.google.com/googleplay/android-developer/answer/13392821, "Full-screen intent requirements for Android 14" (gp_restricted_permissions_decl_expanded.txt:120-129)
Cadence sets alarms — AndroidNotificationCategory.alarm with fullScreenIntent: true on a channel whose entire purpose is "Rings when a timer expires while the app is not on screen" (lib/alarm_backstop.dart:44-59). The core function is setting an alarm. The permission is policy-eligible for auto-grant.
But there is a real defect behind it. The declaration is required regardless of eligibility, and Google's fallback path — prompt the user, degrade gracefully — is not implemented. lib/alarm_backstop.dart:77-87 requests exactly two permissions:
final notif = await android?.requestNotificationsPermission();
final exact = await android?.requestExactAlarmsPermission();
There is no canUseFullScreenIntent() check and no request path; proof/02_store/repo_state.txt §6 shows the complete set of permission calls in lib/ and android/app/src/main/kotlin/ — three lines, none of them full-screen intent. If Google declines the declaration, or if the declaration is simply never filed, the full-screen intent silently downgrades to a heads-up notification on Android 14+. In a kitchen the alarm still makes noise, but it stops seizing the screen — which is the behaviour the whole backstop exists to produce when the tablet is showing something else. The app would neither know nor tell the operator, unlike the exact-alarm path, which does degrade loudly (:209-216 flips _exactOk and raises a critical banner). Status: MISSING (declaration), and a code gap worth a finding in the engineering stream.
5.3 POST_NOTIFICATIONS — not a Play-restricted permission; correctly handled in code
POST_NOTIFICATIONS does not appear anywhere in the Permissions and APIs that Access Sensitive Information policy (grep over gp_exact_alarm_policy_expanded.txt matches only Exact Alarm and Full-Screen Intent, plus the entries for VPN, Age Signals and the rest — no notification entry). It is an ordinary Android 13+ runtime permission requiring a user prompt, not a Play declaration.
Android's own guidance is that the app must request it at runtime and behave sensibly if refused (https://developer.android.com/develop/ui/compose/notifications/notification-permission). Cadence does: alarm_backstop.dart:79 calls requestNotificationsPermission(), and :83-87 raises a critical operator banner when it comes back false — "permission notifications REFUSEE" — with the comment "without it the safety net can ring but shows nothing — say it loudly". PRESENT. No store action required.
5.4 RECEIVE_BOOT_COMPLETED — not restricted, no declaration
Also absent from the sensitive-permissions policy. It is a normal-protection permission granted at install. It is merged in for flutter_local_notifications' ScheduledNotificationBootReceiver, which re-registers pending backstop alarms after a reboot (android/app/src/main/AndroidManifest.xml, receiver block). PRESENT. No store action required.
5.5 The rest of the manifest
VIBRATE, WAKE_LOCK, MODIFY_AUDIO_SETTINGS are normal permissions and appear in no Play restricted-permission policy. PRESENT.
No foreground service. The packaged release manifest declares no FOREGROUND_SERVICE* permission and no android:foregroundServiceType (proof/02_store/merged_manifest.txt — the full uses-permission list is VIBRATE, WAKE_LOCK, MODIFY_AUDIO_SETTINGS, POST_NOTIFICATIONS, USE_EXACT_ALARM, SCHEDULE_EXACT_ALARM≤32, USE_FULL_SCREEN_INTENT, RECEIVE_BOOT_COMPLETED, plus the plugin-generated dev.sergemio.cadence.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION). The Android 14 foreground-service declaration on the App content page therefore does not apply. N/A — and this is a good outcome: the FGS declaration would have required a demo video per service type (answer/13392821).
No INTERNET permission. INTERNET_PRESENT = False in the shipped APK's binary manifest (proof/01_findings/S13/apk_permissions.txt), and it is absent from the packaged release manifest. This single fact carries most of §6.
6. Data safety form
Every app must complete it, including apps that collect nothing.
"Even developers with apps that do not collect any user data must complete this form and provide a link to their privacy policy. In this case, the completed form and privacy policy can indicate that no user data is collected or shared." — https://support.google.com/googleplay/android-developer/answer/10787469
Apps active only on the internal testing track are exempt; closed, open and production tracks all require it (same source). Since a personal account must run a closed test (§2), the form is required before the 14-day clock can even start.
Reconciliation note (asked for explicitly). findings/S13_data.md does not exist as of 2026-08-04 — findings/ contains S1_engine.md, S2_persistence.md, S5_error_handling.md, S10_product.md, S11_LICENSES_draft.md and nothing else. S13's raw probes do exist at proof/01_findings/S13/ (probe_data.txt, network_grep_lib.txt, apk_permissions.txt, device_info_reads.txt, transitive_http_reachability.txt, no_config_export_import.txt), and the answers below are derived from those probes plus the manifest. Every row must be re-checked against findings/S13_data.md when S13 lands.
| # | Form question | Required answer | Evidence |
|---|---|---|---|
| 6.1 | Does your app collect or share any of the required user data types? | No | "Collect" is defined as "transmitting data from your app off a user's device". The app has no INTERNET permission (proof/01_findings/S13/apk_permissions.txt) and no network symbol anywhere in lib/ — the grep for package:http|HttpClient|WebSocket|Socket(|package:dio|Uri.parse|… exits 1, no match (network_grep_lib.txt §1). Transmission is not merely absent, it is impossible. |
| 6.2 | Is any data collected by third-party libraries or SDKs? | No | No analytics, crash-reporting or ad SDK: the grep for firebase\|crashlytics\|sentry\|analytics\|amplitude\|mixpanel\|posthog\|bugsnag\|appcenter\|datadog\|google_mobile_ads\|facebook across pubspec.yaml and pubspec.lock exits 1 (network_grep_lib.txt §4) |
| 6.3 | Is data shared with third parties? | No | Nothing leaves the device automatically. The journal export (lib/journal.dart:224-229) writes a text file to a temp/external directory and hands it to the system share sheet — an on-device transfer to another app, which the policy would normally require disclosing, except under the named exception: "User-initiated action… Transferring user data to a third party based on a specific user-initiated action, where the user reasonably expects the data to be shared". The operator taps Export and picks the destination. Declare No, and state the export in the privacy policy so the two documents agree. |
| 6.4 | Data types collected (Location / Personal info / Financial / Health / Messages / Photos & videos / Audio / Files & docs / Calendar / Contacts / App activity / Web browsing / App info & performance / Device or other IDs) | None selected | Everything the app stores is local: 11 SharedPreferences keys and one file, <appdir>/cadence-journal.txt (proof/01_findings/S13/probe_data.txt). Note the content is sensitive in substance even though it is never collected — a real probe row reads "phrase":"Table 12 pour Mme Dupont", a customer's name typed by a cook. It stays on the tablet. |
| 6.5 | Purposes (App functionality / Analytics / Developer communications / Advertising / Fraud prevention / Personalization / Account management) | N/A — no data types selected | — |
| 6.6 | Required vs optional per data type | N/A | — |
| 6.7 | Is data encrypted in transit? | N/A — nothing is in transit | Same evidence as 6.1 |
| 6.8 | Can users request data deletion? | N/A, but answer honestly if the console forces a value: the app has no accounts and no server; the user deletes data by clearing app storage or uninstalling | No account system anywhere in lib/; Play's account-deletion requirement (https://support.google.com/googleplay/android-developer/answer/13327111) binds apps that let users create an account — Cadence does not |
| 6.9 | Privacy policy URL (blocking prerequisite for the form) | see §9.1 — counted there, not twice | proof/02_store/repo_state.txt §5 → NO_PRIVACY_OR_SUPPORT_URL |
| 6.10 | Optional badges (Families commitment, Independent Security Review/MASA, UPI) | skip all three | answer/10787469 |
The rows above are the answers. The one counted checklist item is the form itself:
| # | Requirement | Status | Source |
|---|---|---|---|
| 6.0 | Data safety form completed and submitted | MISSING | https://support.google.com/googleplay/android-developer/answer/10787469 |
7. Content rating, target audience, and the other App content declarations
Everything here lives on the App content page (Play Console → Policy and programs → App content) and is described at https://support.google.com/googleplay/android-developer/answer/9859455.
| # | Declaration | Status | Required answer for Cadence | Source |
|---|---|---|---|---|
| 7.1 | Content rating questionnaire (IARC) | MISSING | Complete it. Category will be an app (not a game); a kitchen timer has no rateable content, which yields the lowest rating from every authority. | https://support.google.com/googleplay/android-developer/answer/9859655 — "To prevent your apps from being listed as 'Unrated'… 'Unrated' apps may be removed from Google Play." The questionnaire also requires an email address for IARC correspondence. |
| 7.2 | Content rating retake after content changes | N/A (applies to later updates only) | Required for "All app updates where there has been a change to your content or features that would affect the responses" | answer/9859655 |
| 7.3 | Target audience and content | MISSING | Declare adult age groups only — "not designed for children". This is the answer that keeps the app out of Google Play's Families policy requirements entirely. A professional kitchen tool has no plausible child audience. | answer/9859655, "Understanding your target audience selection"; https://support.google.com/googleplay/android-developer/answer/9867159 |
| 7.4 | Ads declaration | MISSING | No. The app contains no ads of any kind: no ad SDK in pubspec.yaml/pubspec.lock (network_grep_lib.txt §4), no cross-promotion, no house ads. |
answer/9859455, "Ads": "You must declare whether or not your app contains ads… If you misrepresent the presence of ads… may result in your app(s) being suspended." |
| 7.5 | News and Magazine apps | MISSING | No. | answer/9859455, "News and Magazine apps"; https://support.google.com/googleplay/android-developer/answer/10523915 |
| 7.6 | COVID-19 contact tracing and status apps | MISSING | No — select none of the statements. | answer/9859455, "COVID-19 contact tracing and status apps" |
| 7.7 | Health apps declaration | MISSING | No health features. A kitchen timer is not a health app; it offers no fitness, medical, mental-health or health-research feature. | https://support.google.com/googleplay/android-developer/answer/14738291; category list at https://support.google.com/googleplay/android-developer/answer/13996367 |
| 7.8 | Government apps | N/A | Not a government app | https://support.google.com/googleplay/android-developer/answer/9514050 |
| 7.9 | Financial features declaration | N/A | No financial features, no billing code | Play Financial Services policy |
| 7.10 | Sign-in details for reviewers | N/A | The app has no login, no gate, no account — the reviewer and the pre-launch crawler reach the full board on first launch | answer/9859455, "Sign-in details" applies only where access is "restricted based on login credentials, sign in details, memberships, location, or other forms of authentication" |
| 7.11 | Permissions declaration form | MISSING | Required for the exact-alarm and full-screen-intent declarations in §5. Not required for SMS/Call Log — the app requests neither. | answer/9859455, "Permissions declaration form"; the FSI form is linked from the policy as https://goo.gle/play-permission-decl-form |
| 7.12 | Data safety | counted at §6.0 | §6 | answer/10787469 |
| 7.13 | Privacy policy URL | counted at §9.1 | §9 | answer/9859455 |
| 7.14 | Developer Program Policies + US export laws acknowledgements, Play App Signing ToS | MISSING | Accepted in the "Declarations" step at app creation | https://support.google.com/googleplay/android-developer/answer/9859152 |
8. Store listing assets
Requirements below are quoted from https://support.google.com/googleplay/android-developer/answer/9866151, retrieved 2026-08-04T10:34:01Z. The pixel figures are inside collapsed accordions; they are in gp_graphic_assets_expanded.txt, not in the plain rendered text.
| # | Asset | Exact requirement | Status | Evidence |
|---|---|---|---|---|
| 8.1 | App name / title | 30 characters or less | MISSING | Product name unresolved — README.md:38 calls "Cadence" a placeholder. android:label="Cadence" today. |
| 8.2 | App icon | 32-bit PNG (with alpha), 512px × 512px, maximum file size 1024KB | MISSING | Nothing near 512×512 exists. Largest launcher icon is mipmap-xxxhdpi/ic_launcher.png at 192×192; largest adaptive foreground is drawable-xxxhdpi/ic_launcher_foreground.png at 432×432 (proof/02_store/repo_state2.txt §C). The store icon is a separate, higher-resolution asset — "The app icon does not replace your app's launcher icon but should be a higher-fidelity, higher-resolution version". |
| 8.3 | Short description | 80 character limit | MISSING | No store copy exists in the repo (NO_STORE_ASSET_FOLDER, proof/02_store/repo_state.txt §9) |
| 8.4 | Full description | 4,000 characters | MISSING | Same |
| 8.5 | Feature graphic | JPEG or 24-bit PNG (no alpha), 1024px × 500px | MISSING | Same. Required to publish the listing. Note the guidance to keep the focal point centred and to avoid pure white, black or dark grey, which blend into Play's background. |
| 8.6 | Screenshots — the floor | "You must provide a minimum of two screenshots across different device types to publish your store listing: JPEG or 24-bit PNG (no alpha); minimum dimension 320px; maximum dimension 3840px; the maximum dimension can't be more than twice the minimum dimension." | MISSING | No screenshots anywhere in the repo |
| 8.7 | Screenshots — large screens (tablets, Chromebooks) | "you can add a minimum of 4 screenshots to demonstrate your in-app experience. Upload screenshots between 1,080 and 7,680px. Use a 16:9 aspect ratio for landscape and a 9:16 aspect ratio for portrait." Up to 8 per device type. | MISSING — and this is the set that actually matters for Cadence | The product is a tablet board for a kitchen, not a phone app: the audit's own product stream reasons throughout in tablets, including the repo's "petite tablette" reference resolution of 800 × 540 (findings/S10_product.md:459) and "116-296 taps… per tablet" (:80) |
| 8.8 | Screenshots — promotional eligibility | "For apps, you must provide at least four screenshots with minimum 1080px resolution. These should be 16:9 for landscape (minimum 1920×1080px) and 9:16 for portrait (minimum 1080×1920px)." | MISSING | Highly recommended, not mandatory — but it is the gate for appearing in Play's large-format recommendation surfaces |
| 8.9 | Preview video | Optional. One YouTube URL, public or unlisted, monetisation off, not age-restricted, embeddable, no playlist/channel URLs, no timecode parameters. | MISSING (optional) | — |
| 8.10 | TV banner | 1280px × 720px | N/A | Not an Android TV app — no leanback intent filter in the manifest |
| 8.11 | Wear OS / Automotive / XR screenshots | Device-specific | N/A | Not distributed to those form factors |
| 8.12 | Category and tags | Choose one | MISSING | Not chosen. "Tools" or "Food & Drink" are the plausible candidates; this is a marketing call, not a compliance one. |
| 8.13 | Alt text on every graphic asset | 140 characters or less, no "photo of"/"image of" | MISSING | Recommended, and it is free accessibility |
8.a What tablet-first changes
Play's hard floor is only "two screenshots across different device types" — no specific device type is individually mandatory. But a kitchen board photographed on a phone would misrepresent the product to the exact buyer being targeted, and Cadence's dense multi-tile board is the thing worth showing. The right set is at least four landscape 16:9 large-screen screenshots at 1920×1080 or better (satisfying 8.7 and 8.8 at once), taken on a tablet-shaped viewport, plus a small phone set only if the app is genuinely to be sold for phone use. Everything must be captured from the real app: "Screenshots must demonstrate the actual in-app or in-game experience"; no device frames, no marketing overlay taking more than 20% of the image, and — a rule the audit's own French UI makes easy to trip — the notification bar must be cleaned of service-provider names and notifications before capture.
9. Privacy policy URL
| # | Requirement | Status | Source |
|---|---|---|---|
| 9.1 | An active, public URL hosting a privacy policy, entered on the App content page | MISSING | https://support.google.com/googleplay/android-developer/answer/9859455 |
| 9.2 | Required even with zero data collection, because the Data safety form cannot be submitted without it | MISSING | https://support.google.com/googleplay/android-developer/answer/10787469 |
| 9.3 | Linked within the app as well as on the store listing, for apps requesting sensitive permissions | MISSING | answer/9859455: "For apps that request access to sensitive permissions or data (as defined in the User data policy): You must link to a privacy policy on your app's store listing page and within your app." Cadence declares two permissions that Google files under Permissions and APIs that Access Sensitive Information (USE_EXACT_ALARM, USE_FULL_SCREEN_INTENT — answer/16558241), so ship the in-app link. It costs one row in a settings sheet and removes the argument. |
| 9.4 | Content must "comprehensively disclose how your app collects, uses, and shares user data… including the types of parties with whom it's shared" | MISSING | answer/9859455, "Privacy policy" |
What the policy must actually contain, given this app's data. Another agent is drafting it; this is the specification it has to meet, derived from the probes rather than from a template:
- Statement of no collection and no transmission, and the reason: the app declares no
INTERNETpermission, contains no networking code, and includes no analytics, crash-reporting or advertising SDK. - What is stored locally, named exactly: the 11
SharedPreferenceskeys observed in a real session — timer definitions (cadence-timers-v1), run state (cadence-run-v1), zone/clone/sound/language/volume settings — and the on-disk journalcadence-journal.txt(proof/01_findings/S13/probe_data.txt). - The free-text warning: timer names and spoken announcement phrases are typed by staff and can contain third-party personal data. The probe's own seed row is
"phrase":"Table 12 pour Mme Dupont". The policy must say that this text is stored in clear on the device, is included in any exported journal, and that the operator controls what is typed. - The export path: how the journal leaves the device (user-initiated share sheet only, to a destination the operator picks), and that once shared it is outside the app's control.
- Permissions and why each is held: exact alarms and full-screen intent for the alarm backstop, notifications to display it, wake lock to keep the board alive, vibration, and audio-settings modification to drive the alarm stream.
- Retention and deletion: data persists until app storage is cleared or the app is uninstalled; there is no server-side copy and therefore no deletion request to make.
- Controller identity and contact, matching the Play developer identity in §1.12 — the same legal name and the same contact email that is entered as the store-listing contact.
- French and English, since the app ships a French UI and targets French kitchens (
cadence-lang: frin the probe), and the store listing will be French.
10. Release tracks and the first release
| # | Item | Detail | Source |
|---|---|---|---|
| 10.1 | Internal testing | Up to 100 testers, available within minutes of upload, can start before the app is fully set up. Apps active only on this track are exempt from the Data safety form. First-time publishes show "a temporary name and store listing information for up to 48 hours". | https://support.google.com/googleplay/android-developer/answer/9845334 ; answer/10787469 |
| 10.2 | Closed testing | Controlled tester list; the track that satisfies the §2 gate. Testers need a Google Account or Google Workspace account. Test-user feedback does not affect the public rating. | answer/9845334 |
| 10.3 | Open testing | Publicly discoverable and joinable; "Open testing is available when you have production access" — so it is behind the personal-account gate, not a way around it. | https://support.google.com/googleplay/android-developer/answer/14151465 |
| 10.4 | Production | Disabled on a new personal account until the closed-test application is approved | answer/14151465 |
| 10.5 | Staged rollout | Not available for a first release: "Staged rollouts can only be used for app updates, not when publishing an app for the first time." | https://support.google.com/googleplay/android-developer/answer/6346149 |
| 10.6 | Pre-launch report | Automatic, "subject to capacity within our device lab", generated on app-bundle upload and when a production release is saved. Google installs the app on real lab devices and crawls it for several minutes, testing stability, Android compatibility, performance and accessibility. | https://support.google.com/googleplay/android-developer/answer/9842757 |
| 10.7 | Review time | "For certain developer accounts, we'll take more time to thoroughly review your app… review times of up to seven days or longer in exceptional cases." Changes are not sent for review automatically — they queue under "Changes not yet sent for review" until submitted. | https://support.google.com/googleplay/android-developer/answer/9859751 |
10.a What the first release should actually use
Upload to internal testing first, immediately after the release signing config lands and before any store copy is written. Three reasons, each backed above: it is the only track exempt from the Data safety form, so it unblocks while §6 and §9 are still being written; it triggers the pre-launch report, which crawls the app on real Google devices — and for an app whose entire coverage baseline shows main.dart, ui/home.dart, ui/tile.dart, ui/header.dart, ui/theme.dart and ui/logo.dart at 0.00% line coverage, a free automated crawl on real hardware is the cheapest UI evidence available anywhere in this project; and it costs nothing irreversible except the package name, which must be settled first anyway.
Then closed testing with the 12 real testers — and they should be real cooks on real tablets, not friends, because Google grades engagement and because the pre-launch crawler cannot tell you whether a timer board survives a dinner service.
Do not plan a staged rollout for launch. It does not exist for a first publish.
Score
Counted rows are the numbered checklist rows carrying a bolded status. The §6 answer table (6.1-6.10) is not counted — those are the answers the form needs, and the form itself is the single counted row 6.0. Rows 7.12 and 7.13 point at 6.0 and 9.1 rather than double-counting them.
| Count | |
|---|---|
| PRESENT | 15 |
| MISSING | 45 |
| N/A (justified, no action) | 7 |
PRESENT (15): 1.8, 3.5, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 5.1a, 5.2a, 5.3, 5.4, 5.5, 5.7.
MISSING (45): 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.9, 1.10, 1.11, 1.12, 2.0, 3.1, 3.2, 3.3, 3.4, 3.6, 4.8, 5.1b, 5.2b, 5.2c, 6.0, 7.1, 7.3, 7.4, 7.5, 7.6, 7.7, 7.11, 7.14, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 8.12, 8.13, 9.1, 9.2, 9.3, 9.4.
N/A (7): 5.6, 7.2, 7.8, 7.9, 7.10, 8.10, 8.11.
The shape of that ratio is worth naming: the engineering is nearly compliant and the paperwork does not exist. Target API, 64-bit, bundle format, size, permission eligibility and runtime permission handling are all already correct — several of them by deliberate design rather than luck. Everything missing is either an account that has not been opened, a document that has not been written, or an image that has not been drawn. The one exception, and the one true engineering blocker, is signing.
Ordered action list
Owner key: S = Serge (repo, build, signing key), H = the project owner (business identity, money, legal), E = either. ⚠ marks an action that is irreversible or effectively irreversible once taken.
Round 0 — decisions that must be made before anything is uploaded
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 1 | ⚠ Decide the account type: personal or organisation. Organisation means a D-U-N-S number (free, up to 30 days to obtain), the company address published instead of a home address, and exemption from the 12-tester/14-day gate. Personal means US$25 and a 2-3 week testing gate, plus Serge's home address published the moment the app is monetised. If there is any intention to charge restaurants, this is not a close call. | H | 2, 3, 12 | ⚠ D-U-N-S must be obtained before the organisation account is created; Google will not create one without it |
| 2 | ⚠ Settle the product name and the package name together. dev.sergemio.cadence is permanent once uploaded to any track and encodes both a personal namespace and a name README.md:38 calls a placeholder. Decide the ≤30-character store title at the same time — that half is changeable, the package name is not. |
H (name), S (edit) | 8, 9, 19 | ⚠ Yes — the package name can never be changed, deleted or reused |
| 3 | If organisation: apply for the D-U-N-S number now. Up to 30 days. Nothing else in this list is blocked by it, so start it and continue in parallel. | H | 12 | No |
Round 1 — unblock the build (fully parallel with Round 0)
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 4 | Generate the upload keystore with the keytool command in §3.a. RSA, 2048 bits minimum, -validity 10000. |
S | 5, 6, 7 | ⚠ Practically — the certificate registered on first upload is the one Play will expect forever, though Google can reset a lost upload key on request |
| 5 | Create android/key.properties per §3.a. Already gitignored (android/.gitignore:12); verify with git status --porcelain that it does not appear. |
S | 6 | No |
| 6 | Wire signingConfigs.create("release") into android/app/build.gradle.kts and DELETE the signingConfig = signingConfigs.getByName("debug") line. Not comment it out, not leave it above the new one — delete it. |
S | 7 | No |
| 7 | Rebuild and prove it. flutter build appbundle --release, then apksigner verify --print-certs on the APK and openssl pkcs7 on the AAB's META-INF/*.RSA must both stop printing CN=Android Debug. Delete the two debug-signed artifacts currently on disk. |
S | 19 | No |
| 8 | Store the keystore and its passwords somewhere that survives a lost laptop, off the repo. Losing the upload key is recoverable through Google; losing it with no backup and no Play account is not. | S | — | No |
Round 2 — content that takes real time to produce (parallel with Round 1)
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 9 | Write and host the privacy policy to the eight-point specification in §9, in French and English, at a stable public URL. Another agent is drafting the text; someone must own the hosting. | H | 14, 15, 21 | No |
| 10 | Add an in-app privacy-policy link (§9.3). One row in the settings sheet. | S | 21 | No |
| 11 | Produce the store assets (§8): 512×512 32-bit PNG icon with alpha ≤1024KB; 1024×500 feature graphic, JPEG or 24-bit PNG with no alpha; at least four landscape 16:9 large-screen screenshots at 1920×1080 or better, captured from the real app on a tablet-shaped viewport with a clean status bar; short description ≤80 characters; full description ≤4000; alt text for every image. | E | 20 | No |
Round 3 — open the account
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 12 | Create the Play Console developer account, pay the US$25 fee, link the Google payments profile, and pass identity verification (government ID + card in the legal name for personal; D-U-N-S + organisation documents for organisation). | H | everything below | ⚠ The account type is chosen here |
| 13 | ⚠ Create the app record: default language, app-or-game, free-or-paid, store-listing contact email, and accept the Developer Program Policies, US export laws and Play App Signing Terms of Service declarations. Play App Signing enrolment happens automatically on first upload with Google-generated keys. | H | 19 | ⚠ Yes — the package name is claimed on first upload, and the Google-held app signing key is not something you swap later |
Round 4 — the declarations (all parallel; none can start before 12)
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 14 | Data safety form — answers in §6. Requires the privacy policy URL from action 9. | H | 22 | No |
| 15 | Privacy policy URL entered on the App content page. | H | 14, 22 | No |
| 16 | Content rating questionnaire (IARC) — an app, not a game; no rateable content. | H | 22 | No |
| 17 | Target audience and content — adult only, "not designed for children". | H | 22 | No |
| 18 | The one-word declarations: Ads = No. News and Magazine = No. COVID-19 contact tracing/status = none. Health apps = no health features. Government apps, Financial features, Sign-in details = not applicable. | H | 22 | No |
| 19 | The two permission declarations, and they are the ones that can get the app rejected: (a) the exact-alarm declaration, stating that the app's core user-facing functionality is a timer app — this is the first bullet of Google's own acceptable-use list, quoted verbatim in §5.1; (b) the full-screen-intent declaration at https://goo.gle/play-permission-decl-form, stating that the core function is setting an alarm. Both are eligible. Neither is automatic. |
H, with wording from S | 22 | No |
| 20 | Complete the main store listing: title, short and full descriptions, icon, feature graphic, screenshots, category and tags. | E | 22 | No |
Round 5 — release
| # | Action | Owner | Blocks | Irreversible |
|---|---|---|---|---|
| 21 | Add canUseFullScreenIntent() handling in lib/alarm_backstop.dart: check the grant, and if it is absent, raise the same critical operator banner the exact-alarm path already raises at :211-216. Google requires apps that are not auto-granted to "gracefully degrade the experience if denied", and today the app neither checks nor tells anyone. This does not block submission; it blocks the app being honest with a kitchen. |
S | — | No |
| 22 | Upload the signed AAB to internal testing. Exempt from the Data safety form, triggers the free pre-launch report on real Google lab devices, available to testers within minutes. Fix whatever the crawl finds. | S | 23 | ⚠ First upload claims the package name permanently |
| 23 | If personal account: publish to closed testing, recruit 12 real testers on real tablets, keep them opted in and engaged for 14 continuous days, then apply for production access and allow up to 7 more days for Google's answer. If organisation account: skip straight to 24. | H | 24 | No |
| 24 | Publish to production. No staged rollout — it does not exist for a first release. | H | — | No |
The critical path
Organisation account: 3 → 12 → 13 → (14…20 in parallel with 4…11) → 22 → 24. The long pole is the D-U-N-S, up to 30 days, and it can be started today.
Personal account: 12 → 13 → (14…20) → 22 → 23 → 24. The long pole is 14 days of closed testing plus up to 7 days of review, and it cannot start until every declaration in Round 4 is finished, because a closed-testing track still requires the Data safety form.