-
-
Four GitLab Duo flows look after a public service once the code is written.
-
Why now: the US deadline for WCAG 2.1 AA moved, and 95.9% of top home pages still fail.
-
A copy of cloud.gov's website, before and after the review flow's four commits (!1). "Open a ticket" was 1.62:1.
-
Riverside Library !1: the same review flow, on another site, fixed all six common failures.
-
15:08 UTC: a resident can't see the print button. Every automated check had passed it (#4).
-
15:41 UTC: the fix is live on Google Cloud and checked, 33 minutes after the report (!6).
-
Review flow: one note for the approver, including what a machine cannot judge (!3).
-
Triage flow: a regression test proven to fail before the fix and pass after it (#4).
-
Conformance flow: only a person's recorded review can claim a level (v1.0.0 report).
-
Release v1.0.0, with its accessibility report and conformance report.
Inspiration
A curb cut is the small ramp at a street corner. It was made for wheelchairs, and it ended up helping everyone with a stroller, a suitcase or a bike. Online public services work the same way: a form that works with a screen reader and a keyboard works better for everybody.
The deadline for those services just moved. In April 2026 the US Department of Justice gave state and local governments another year to make their websites meet WCAG 2.1 AA: until April 26, 2027 for those serving 50,000 people or more, and April 26, 2028 for the rest. One of its reasons: "Advanced technology, such as generative AI, does not yet reliably automate the remediation of inaccessible content at scale" (Federal Register 2026-07663). Meanwhile, in February 2026, 95.9% of the top million home pages had detectable WCAG failures, and six kinds of error made up 96% of them (WebAIM Million 2026).
Most public services are checked once, at launch. Then the code keeps changing, and one small change at a time the service stops working for someone. The checks exist. What is missing is someone who acts on them every time, and who is honest about what still needs a person. Agents can do that work, as long as nothing they claim is taken on trust.
What it does
Curb Cut is four GitLab Duo Agent Platform flows that look after a public service once the code is written. The service is a bulky-item pickup request for Alder Creek, a fictional city, live on Google Cloud (Firebase Hosting): four pages and a five-step form.
| Flow | Starts when | What it does |
|---|---|---|
| Review | a merge request is created or marked ready, the flow is assigned as reviewer, or it is mentioned | runs the project's checks (axe-core on 14 screen states and a keyboard-only journey), traces each problem to its source line, fixes it in its own commit naming the WCAG criterion, re-runs the checks, and leaves one note for the approver with what a machine cannot judge |
| Incident | an accessibility job fails on main: the gate, the check after each deploy, or the daily check of the live site | lets only those failures through, finds the commit that caused the problem by testing before and after it, opens an incident in plain words, and proposes the fix as a merge request |
| Triage | someone opens an issue, or a person asks it to look again | reproduces the report in Playwright, measures what assistive technology is given (accessible names, descriptions, contrast), names the criterion, and proves a regression test both ways: it fails on today's code and passes once the fix is tried, then the fix is thrown away |
| Conformance | it is mentioned on a release issue | writes the release's accessibility conformance report, opens the merge request, and opens a checklist of the manual checks still needed |
Autonomy: supervised. The flows do the work; people approve the merge, the production deployment and every conformance claim. A script, not a model, sets every conformance level: a failing check can only lower a level, and only a person's recorded review can claim one.
Nothing in the flows is about Alder Creek. They read curb-cut.json at the root of the repository for what the service is, who uses it, its pages, and how it is built and served. A second site, Riverside Library (plain HTML, no build step, no framework), took the same review flow from the catalog with its own curb-cut.json. So did a copy of cloud.gov's website, the US government's cloud platform for federal agencies (Astro and the US Web Design System).
What happened (all of it is in the projects)
- One report, end to end, in 33 minutes. 15:08 UTC: a resident with low vision reported they could not see the print button (#4). Every automated check had passed it, because axe-core does not measure the contrast of icons. 15:24: the triage flow measured 1.27:1 against the panel, named WCAG 1.4.11 and proved a contrast test both ways. 15:31: GitLab's Developer flow opened !6 with the fix and the test, and once a person marked it ready, the review flow checked it. 15:36: a person merged it, and at 15:39 ran the deployment to Google Cloud. 15:41: the same checks passed on the live site. That test now runs on every change.
- Review. !1 added an access-notes field; the flow found and fixed two problems in two commits. !3 added a print button that was only an icon; assigned as reviewer, the flow gave it an accessible name.
- Incident. A map with no text alternative was pushed straight to main and the gate held it back. The flow confirmed the commit that caused it, opened #2 and proposed !4; a person merged it and ran the deployment.
- Conformance. The flow wrote the v1.0.0 report (!7) and the manual-checks checklist (#6). v1.0.0 shipped claiming nothing a person had not checked: 9 criteria not applicable, 41 not yet evaluated.
- Another site. Riverside Library's !1 added an events page written, on purpose, with all six of the most common failures in the WebAIM Million: low-contrast text, missing text alternatives, a missing form label, empty links, an empty button and no page language. The same review flow fixed all six in six commits, each naming its WCAG criterion, and the pipeline went from failed to passed in under six minutes. From the flow's checklist, a person made the event pictures decorative, then merged.
- A real site. On a copy of cloud.gov's public website, the merge request that turned the gate on (!1) failed it: 54 elements below WCAG 2.1 AA contrast on its ten pages, the same 54 that failed on cloud.gov itself on 7 October 2026, among them an "Open a ticket" heading at 1.62:1, close to invisible. The review flow traced all 54 to four colours in the site's theme and fixed one cause per commit; the pipeline was green eight minutes after the merge request opened. Before merging, a person scanned every page the build produces (54 failing elements before, none after, none new), corrected two slips in the flow's note, and left one trade-off for cloud.gov's designers: the darker accents also darken two product logo tiles.
How we built it
- Four custom flows in
.gitlab/duo/flows/(flow registry v1), with routers that end a flow early when it has nothing to do..gitlab/duo/agent-config.ymlruns them in the Playwright image, so the agents run the same checks as the pipeline. curb-cut.jsondescribes the project: the service and its users, the standard, the pages, the build and serve commands, and where the source is. Every page it lists is checked as soon as it is listed, and the flows read it before they act.- The checks: Playwright with axe-core on every page and form state, a keyboard-only journey, and unit tests.
scripts/a11y-worklist.mjsmaps each violation to the source lines behind it;scripts/acr.mjswrites the conformance report. - The pipeline: unit tests, build, the accessibility gate, SAST, secret detection and dependency scanning, a review app per merge request, a manual production deployment to Firebase Hosting on Google Cloud, a check of the live site after every deploy and every day, and tagged releases carrying the accessibility report, the conformance report and the built site as a package.
- No stored keys: the deploy job gets a short-lived GitLab OIDC token, and Google Cloud Workload Identity Federation accepts it only from this project's main branch (
deploy/google-cloud/).
Lifecycle stages
- plan: the triage flow turns a report into acceptance criteria, a proven regression test and labels
- create: the review and incident flows commit fixes; the conformance flow commits the release report
- verify: checks on every merge request, re-run by the flows before they push
- package: the built site as a generic package on each release
- secure: SAST, secret detection and dependency scanning
- release: review apps, production deployments a person approves, tagged releases
- configure: the flows, the project description (
curb-cut.json) and production access are configuration in the repository - monitor: the live site is checked after every deploy and every day; a failure starts the incident flow
- govern: claims only from recorded human review; every agent change is a commit in a merge request a person approves
Challenges we ran into
We kept a list of what went wrong, because each one changed a flow:
- The incident flow once acted on a merge request's pipeline. It now starts with a step that only lets through failed accessibility jobs on main.
- The triage flow's first regression test could never pass: an accessibility snapshot lists names, not descriptions. The flow now proves every test fails before the fix and passes after it.
- Once, a reply step made up an issue URL on another project and posted its answer there. Every flow now names this project by ID in every GitLab call and never passes a URL.
- Merge requests opened by an agent do not start other flows, so a person marks them ready, and that starts the review.
- A manual record went stale ("the site has no images") after the map was added. It was removed and the report regenerated.
- Trying the checks on a second site showed that the worklist could not point at the cause of a problem with a whole page, such as a missing page language. It now names the page's own file.
- Setting up the cloud.gov copy, one run of the checks checked no screen state at all and still reported no problems. The checks now fail when nothing was checked, and every flow reports that as an error, never as a pass.
Accomplishments that we're proud of
A real problem that every automated check had missed went from a resident's report to a verified fix in production in 33 minutes, with a person deciding at each gate. On a second site, the same flow fixed all six of the most common failures in one merge request, and on a copy of cloud.gov's website it fixed all 54 contrast failures on its ten pages in four commits. And the release report says exactly what was checked, how, and by whom.
What we learned
Agents are most useful when the checks are deterministic and the agent's job is to act on them, prove what it claims, and hand judgement back to people.
What's next
Share the flows in the public AI Catalog, regenerate the conformance report from checks recorded on its merge request, flag manual records when the pages they cover change, and add reflow and text-spacing checks as automated evidence.
Built With
- axe-core
- firebase
- gitlab
- gitlab-ci
- gitlab-duo
- google-cloud
- node.js
- playwright
- typescript
- vite
- wcag
Log in or sign up for Devpost to join the conversation.