ci: gate every compile job from one change filter - #648
Conversation
The iOS change filter becomes a general `changes` job publishing one output per gated area, `sdk_swift` and `sdk_kotlin`, and the Android compile gate moves out of its own workflow into a job that reads it. That job's steps are carried over unchanged, codegen and all. Living in a separate workflow left the Android gate with two problems. Its `paths` trigger meant it reported nothing at all on a pull request that touched other areas, so it could never satisfy a required status check; as a job in this workflow it reports skipped, which `ci-status` already counts as a pass. It also had no `merge_group` trigger, so the Kotlin shell was never compiled on a merge queue run. The Kotlin filter now also names `rust/crates/truapi`, which it did not before, for the same reason the Swift filter names it: the bindings are generated rather than committed, so a protocol change there leaves no `android/` diff to key on. Both filters name this workflow, so editing a filter exercises the jobs the filter gates.
Records where the compile gates are computed and which check is worth requiring, now that a filtered job reports skipped rather than not reporting.
|
@lore-bot-app review |
|
Reading the diff and checking what the record says. Back in a few minutes. |
There was a problem hiding this comment.
TL;DR
Folds the standalone ci-android workflow into ci.yml as android-bindings, gated by a shared changes job that emits sdk_swift and sdk_kotlin. 0 blocking, 3 minor.
Summary
The ios-changes job becomes changes and computes both path gates with one gate shell helper. ios-swift reads sdk_swift; a new android-bindings job reads sdk_kotlin and runs the same steps the deleted workflow did. Both feed ci-status, where skipped counts as pass. Two side effects follow from the workflow triggers: the old workflow had no merge_group trigger, so the merge queue never ran the Kotlin compile, and it now does. Because release.yml fires on a successful CI run, Android releases are now gated on this compile as well. CLAUDE.md is updated to match. All of this is from static reading; nothing was executed.
What the record says
- Issue #671 confirms
CI Statusis the only required check onmain, enforced by theno-direct-main-pushruleset, and that skipped jobs count as passes. Deletingci-androidtherefore leaves no dangling required check. - PR #551 untracked the generated Rust and iOS outputs and introduced
ci-android. That decision is why the filters must name source crates rather thanios/orandroid/diffs. - Issue #636 plans multi-host change detection in CI, and issue #677 wants a breaking core change to fail the Android host build in the same PR. The
changesjob is the natural attachment point for both. - Issue #674 settled that CI invokes each host build directly rather than composing Gradle builds, which is consistent with
gradle :truapi-host:assembleReleasehere. - The record has no discussion of the filter path lists themselves. Nothing covers the two omissions below.
Concerns
.github/workflows/ci.yml:239Thesdk_kotlinlist omits the root Gradle files.build.gradle.ktspins AGP 8.5.2 and Kotlin 1.9.24, andsettings.gradle.ktswires the module paths. A toolchain bump there skipsandroid-bindings. The Swift list namesPackage.swift$for exactly this case. Pre-existing in the deleted workflow'spaths, but this PR is where the list now lives and the comment claims it is complete. Dependabot only coversgithub-actions, so these bumps are manual, which makes them rarer but not safer..github/workflows/ci.yml:238Thesdk_swiftlist omitsrust/crates/uniffi-bindgen-cli/whilesdk_kotlinincludes it.ios-swiftrunsmake xcframework, which depends onmake uniffi, which runscargo run -p uniffi-bindgen-cli. A bindgen change that alters the emitted Swift would skip the compile gate.ios-bindingsstill generates, but generation passing is not the failure this gate exists to catch.android/truapi-host/README.md:440States "No CI job compiles this package." That is false after this change and was already false underci-android. CLAUDE.md requires docs to reflect the repo, and this PR rewrites the CLAUDE.md paragraph about the same gate.
Questions for the author
android-bindingsre-runsnpm ciandcodegen.shwith a nightly toolchain instead of addingneeds: codegenand downloading thecodegen-outputartifact asios-bindingsandios-swiftdo. Is the independence deliberate?- Changes under
android/truapi-provider/tripsdk_kotlin, but the job only builds:truapi-host.make provider-android-checkexists in the Makefile. Is compiling the provider module in this job in scope, or tracked elsewhere?
🤖 Reviewed by Lore (Parity knowledge base) · 30 agent turns · 424.9s · knowledge as of 2026-09-09
The Swift list did not name the bindings generator. `ios-swift` runs `make xcframework`, which depends on `uniffi`, which runs `cargo run -p uniffi-bindgen-cli`, so a generator change that alters the emitted Swift would have skipped the compile gate. The Kotlin list did not name the root Gradle files. `build.gradle.kts` pins AGP and Kotlin and `settings.gradle.kts` wires the module paths, so a toolchain bump would have skipped `android-bindings`. The Swift list already named `Package.swift` for exactly this case. Both were inherited from the workflows this consolidates, but the lists live here now, so they are fixed here. The comment above them said they name every crate the bindings come from; it now says what they actually have to name, which is every input the gated job reads.
|
Both correct, fixed in the latest commit. Verified each chain before changing anything. The bindgen gap. The root Gradle gap. Also rewrote the comment above the lists. It claimed they name every crate the bindings are Re-ran the filter cases, now 13 including the four new ones: That last one is worth noting: an imported host tree does not trigger the SDK gates, which |
Seven inputs both gated jobs read were matched by neither filter, so a change
to any of them skipped the gate that exists to catch it:
Cargo.toml, Cargo.lock pin the bindings generator and hold the codegen
profile, whose whole purpose is keeping the
metadata symbols bindgen reads
truapi-macros a path dependency of truapi, compiled with the
uniffi feature, and it emits type definitions
truapi-codegen emits the generated Rust the Kotlin job compiles
scripts/codegen.sh the Kotlin job runs it directly
package.json, lockfile the Kotlin job runs npm ci against them
Root Cargo.toml is anchored, so a crate's own manifest still matches through
its crate path. The npm entries are on the Kotlin list only: the Swift job
installs js/container's package, which that list already names.
The comment above the lists said they name every crate the bindings are
generated from. That is the weaker claim, and stating it is how these
survived. It now says what the lists have to name.
Folding the Android gate in puts it on the release path, since release.yml
gates on the CI run concluding successfully. So it gets a 30 minute timeout
like the Swift gate: unbounded, a hung Gradle fetch would hold a release for
the six hour default while reporting nothing. It also gets a pinned cache
key, so the rename does not orphan the warm cache and a future rename
cannot either.
The definition-of-done skill still said nothing compiles Kotlin. It does now.
|
Ran three independent adversarial audits over this before merging, since the approving The filters were missing seven inputs both gated jobs read. A change to any of them
The Root I also rewrote the comment above the lists. It claimed they name every crate the bindings The Android gate is now on the release path, because It also gets Confirmed clean, independently and with evidence: the folded job is byte-exact against Two things left as known and tracked rather than fixed here. Also updated the definition-of-done skill, which still claimed nothing compiles Kotlin. |
Closes #670.
Summary
ci.ymlcomputes every path gate in one place. Achangesjob publishessdk_swiftandsdk_kotlin, and each gated job reads the output it cares about.ci-statusaggregatesevery job in the workflow and counts a job skipped by its filter as a pass, which makes it
the single check worth requiring.
This is the gating spine the host imports build on: adding a host means adding one output
and one consumer, not another workflow with its own trigger.
Required checks: no change needed
Confirmed on the
no-direct-main-pushruleset, which requires exactly one check,CI Status. No per-workflow job is required, so renaming the change filter and removingthe separate Android workflow changes nothing that is enforced. Details in #671.
Why the Android compile gate lives here
In its own workflow with a
pathstrigger it reported nothing at all on a pull requesttouching other areas, so it could never have satisfied a required status check. It also had
no
merge_grouptrigger, so the Kotlin shell went uncompiled on merge queue runs. As agated job it reports skipped when it does not apply, and it runs on the merge queue with
everything else.
Its steps are carried over unchanged, including the codegen step the gitignored generated
modules require.
Filter behaviour
sdk_swiftsdk_kotlindocs/onlyplayground/onlyios/android/js/container/rust/crates/truapi-provider/rust/crates/uniffi-bindgen-cli/rust/crates/truapi/rust/crates/truapi-server/Makefile.github/workflows/ci.ymlThe Kotlin filter names
rust/crates/truapifor the same reason the Swift filter does:neither binding set is committed, so a protocol change there leaves no
android/diff tokey on. It did not name it before.
Both filters name this workflow, so this pull request exercises both gates.
Known cost
The Kotlin gate now also evaluates on
rust/crates/truapiandMakefilechanges, and onmerge queue runs, so it adds some merge queue latency. The old filter already covered
rust/crates/truapi-server/**, which most protocol changes touch, so the widening issmaller than the table suggests.
Verification
branch and both negative cases.
actionlintandshellcheckclean on the changed workflow. The findings that remainacross the workflows are all pre-existing on
main, including the intentionalif: falseon the end-to-end job.unchanged and only its trigger differs.
ci-statusneedsand its result list agree exactly: no job needed without beingchecked, none checked without being needed.
Not included
CLAUDE.mdonmainalready failsnpm run format:checkunder the markdown rules addedin #630, which reformatted no existing docs and which no workflow enforces. Fixing that
rewrites around 300 lines, so it belongs in its own change. The addition here is wrapped to
match the surrounding file.