-
-
The Previewer: reviews your app the way the iOS and Android stores will, before you submit.
-
Plan: a release issue assigned to The Previewer's service account starts the review.
-
Secure + Verify: per-store readiness, band and cited blockers. Seeded case: image_picker with no purpose strings.
-
Create: the fix MR removes the unused plugin instead of writing purpose strings for a missing feature.
-
Govern: one human checklist, then a resume mention; the flow reads its own state and opens the release MR.
-
Release: the release MR with committed reviewer judgement. Merging it is the human approval (Supervised).
-
Verify + Package + Release: the merge pipeline tests, scans, builds and publishes the GitLab Release.
-
Release v2.1.2: iOS store 93, Android store 87, evidence for every finding.
-
Monitor: the outcome email sent by the notify:release job.
-
Configure: the live app on Google Cloud Run, with the account-deletion page both stores require.
-
Pitch: the last mile of shipping is store review.
-
Pitch: eight DevSecOps stages, one flow.
-
Pitch: the six-step loop, with one human approval.
-
Pitch: readiness with a band; precision and recall 0.971 on 42 seeded cases.
-
Pitch: one custom flow, a deterministic engine, CI/CD as the gate.
Inspiration
Shipping a mobile app does not end at the merge. The last mile is store review: you submit, wait a day or more, read a rejection, guess the fix and resubmit. The rules are spread across the iOS store's review guidelines, the Android store's policies and its Data safety form, and most rejections are things a careful reviewer could have caught before the first submission: a missing permission explanation, no privacy manifest, an account that can be created but not deleted, a debug-signed build.
What it does
The Previewer rehearses iOS store and Android store review for a Flutter app, inside GitLab, before you submit.
- Assign a "Release vX.Y.Z" issue to The Previewer's service account.
- A deterministic engine scans the app (Info.plist, privacy manifest, Android manifest, Gradle config, store listing, Data safety answers, the Dart UI) against 20 cited rules. Two reviewer agents, one per store, add judgement findings a static check cannot make, each with evidence and a confidence.
- A fixer agent opens one merge request per auto-fixable finding.
- The Previewer posts one checklist for what only a human can provide (a demo account, policy URLs, an export-compliance answer). You answer, then comment
resume. - It opens a release MR with a per-store readiness score (0 to 100, with a confidence range; any blocker caps it at 40) and a ready-to-submit package for each store. Merging that MR is your approval (Supervised autonomy). The merge pipeline publishes a GitLab Release and emails the outcome.
Nothing is ever uploaded to a store. If a store does reject the app, @The Previewer rejection: <message> hands it to an advocate agent that decides fix or appeal and drafts the appeal with evidence from the repo.
How we built it
- GitLab Duo Agent Platform: one custom flow with five agents (a governor, an iOS store reviewer, an Android store reviewer, a fixer and a rejection advocate), triggered by Assign and Mention.
- A Python engine the agents call (scan, score, merge, handoff, validate-inputs, package, benchmark, notify), so readiness scores never come from a model's guess.
- A Rejection Corpus of 42 seeded cases, each labelled with the rule that should catch it. CI recomputes precision and recall on every run (0.971 / 0.971 today) and fails below 0.90 / 0.85.
- GitLab CI/CD as the gate: engine and Flutter tests, the scan, the benchmark, web and Android builds, input checks on the release MR, per-store packages, the GitLab Release, a release email, and a keyless deploy to Google Cloud Run through Workload Identity Federation.
- Path B: a fork of the MIT-licensed Flutter + Firebase time tracker starter_architecture_flutter_firebase. All automation and the deployment were added from Oct 5, 2026.
What it found on a real app
The first run on the starter app found, all real: a target SDK below the Android store's current requirement, release builds signed with the debug key, no INTERNET permission in release builds (sign-in would fail in production), no iOS privacy manifest, template app names, and an account deletion that left the user's data behind. Every one was fixed through merge requests. A recorded run (v2.1.2) then caught a seeded camera plugin with no permission text, and the reviewer removed the unused plugin instead of writing text for a feature the app doesn't have. The latest release, v2.2.0, is a redesign shaped by the reviewers' own warnings, and it passes both stores with no blockers (iOS store 95, Android store 94).
Challenges we ran into
- A flow session cannot wait days for a human, so resume is a second run that reads its own state from the issue thread and the last pipeline's findings.
- Flow sessions cannot see masked CI/CD variables, so the agent defers those checks to the release MR pipeline, which checks them strictly.
- Keeping scores honest: the model may add findings, but only the engine computes a score, and a reviewer finding without evidence is rejected.
Accomplishments that we're proud of
- A full loop on gitlab.com, from issue to fix MRs to handoff to resume to release MR to GitLab Release, with one human checklist and one approval.
- A score you can audit: every finding cites a guideline, a file and a line, and the detector's own precision and recall are printed beside it.
What we learned
Agents are most useful when a deterministic core owns the facts and the agents own the judgement. That split is what makes Supervised autonomy trustworthy enough to approve with a single merge.
What's next
More rule packs (accessibility, in-app purchase rules), Arabic and RTL screenshot checks, and support for more mobile stacks beyond Flutter.
Log in or sign up for Devpost to join the conversation.