[CI] Close eight merge-gate holes: required loop, path filters, pre-push parity, protoc, release tests, Kotlin smoke - #419
Open
justin13888 wants to merge 14 commits into
Conversation
`required` is the single aggregated status check branch protection guards, and it listed `rust-test` in `needs` while its verification loop skipped it — so a red Rust test job reported through to a mergeable pull request. The job's own comment has said it is blocking since `S-C59`; the aggregate never agreed. Add `needs.rust-test.result` to the loop and rewrite the header comment, which claimed the opposite, to state the rule the loop now enforces: every job in `needs` is verified.
The `rust` paths filter listed nine crate trees, but `check-rust` reads three more: `i18n-check` and `i18n-guard` run `xtask` over `locales/`, `build-check-wasm` runs `cargo check -p capsule-wasm`, and `build-rust` is a workspace build, so `capsule-core-ffi` is in it. A pull request touching only those paths skipped the whole Rust job, and the gates written to catch a catalog or FFI regression never ran on the change that caused it. Add `capsule-core-ffi/**`, `capsule-wasm/**` and `locales/**`. The `swift` filter has carried `locales/**` for the same reason.
Three jobs still installed `protoc` for a `prost-build` step that went with the `capsule.sync.v1` service in `S-D28` — the workspace has no `.proto`, no `prost`/`tonic` dependency and no build script that shells out to it, and `capsule-sdk/build.rs` says so in its own header. The `rust-cross` matrix in `ci.yml` and both `build-ios.yml` jobs (one of them on a self-hosted macOS runner) were paying a toolchain install, and a GitHub token, for nothing.
Deploying capsule with
|
| Latest commit: |
b3c2c75
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e8f8a4c5.capsule-22k.pages.dev |
| Branch Preview URL: | https://chore-close-ci-gate-holes-41.capsule-22k.pages.dev |
`test-web` ran `bun test --pass-with-no-tests`, so any change that stopped bun finding the suites — a moved directory, a changed default glob, a wrong `dir` — reported a green gate over zero assertions. Eight suites live under `capsule-web/src/**` and 59 tests pass today, so the flag protects nothing that exists and hides the one failure it was masking. Verified both ways: bare `bun test` over a directory with no suites exits 1, the same invocation with `--pass-with-no-tests` exits 0.
…overclaiming `CONTRIBUTING.md` said "the same checks run on `pre-push` and on every PR in CI", while `hk.pkl`'s pre-push covered two of `check-rust`'s fourteen steps. Eleven gates — among them the i18n catalog sync, the hardcoded-string guard, the dependency-boundary check and the licence allowlist — were reachable only by pushing and waiting for CI. Add the four that are seconds on a tree `lint-check-rust` has just warmed: `i18n-check`, `i18n-guard` and `architecture-check` share one `xtask` binary, and `license-check` is `cargo deny` with `cargo-deny` already a `[tools]` pin. Each glob is the set of files the task actually reads, so a push that touches none of them pays nothing. `openapi-check-kynos` stays out: it is `cargo run -p capsule-server --bin gen_openapi`, a codegen link of the server and the whole Kynos tree, and clippy leaves `.rmeta` rather than linkable rlibs — so it is a fresh multi-minute build on every push. A hook people disable is worse than one honest about its scope, which is what `CONTRIBUTING.md` now is: it names the subset pre-push runs, names the gates that stay in CI, and points at `mise run check-rust` and its siblings for the full local gate.
…onger exists `FfiWorkspace.create` and `createWithHardwareSigner` each grew an `FfiClientBuild` parameter with `S-D15`, so every manifest a foreign app authors reports that app's own `client_id/semver+commit`. `SoftwareSignerSmokeTest` was never updated: it passed three arguments to a four-parameter constructor and six to a seven-parameter one, which is a Kotlin compile error, so `./gradlew :core:test -Pcapsule.wireFfi` could not build the test source set at all. Mirror `SoftwareP256SignerSmokeTest` in the same directory — a `client` member identifying `capsule-core-kotlin` — and pass it at both call sites. The parameter lists were read off the regenerated binding (`mise run gen-bindings`, `target/bindings/kotlin/uniffi/capsule_core/capsule_core.kt:2002,:2021`); the Gradle/Android toolchain does not run on the reference dev host, so `build-android` in CI is the authority.
…ore/close-ci-gate-holes-416
The previous wording said pre-push runs "the test suites", which is the same shape of overclaim the sentence above it replaced: `hk.pkl`'s pre-push carries `test-rust` and `test-web`, not `test-kotlin`, `test-swift` or `test-docs`. Say which two, and put the other three in the list of what CI is left to do.
5 tasks
The `roadmap` check resolves every `ROADMAP.md` row against the manifests that declare what actually exists — the root `Cargo.toml`, `settings.gradle.kts`, `capsule-swift/Project.swift`, each `Package.swift`, `package.json` and `pyproject.toml`, `.gitmodules`, and the `legacy-review/` subtrees. The `docs-truth` paths filter listed none of them, so a pull request that adds or removes a package without touching Markdown skipped the job entirely, and the gate was advisory for precisely the change it exists to catch. Same defect class as the `rust` filter two commits back: a gate that does not run on the files it reads.
No task or CI job has ever built the workspace's tests under the release profile, so every `#[cfg(not(debug_assertions))]` test is dead: never compiled, never run, and free to rot. `capsule-i18n` carries the only one today (`format.rs:157`, pinning that a release build passes an unrenderable ICU construct through rather than tripping the `debug_assert!` a debug build trips), so `test-rust-release` is scoped to that crate — building the whole workspace a second time would double the test job for a handful of tests. Left out of `test-rust`'s run list on purpose, with the reason in the file. The first release run this task made possible immediately found why it cannot be wired in yet: `format.rs:144`'s `#[should_panic]` test is named for debug builds but is not `#[cfg(debug_assertions)]`, so under release the `debug_assert!` compiles out, the expected panic never happens, and the test fails. That one attribute is in a file this lane does not own and that #414 is editing concurrently, so it is filed as #429 rather than reached for here. Refs #428
`changes` was in `required`'s `needs` and in neither of its checks. Every filtered job's `if:` reads `needs.changes.outputs.*`, so when that job dies — a checkout hiccup, a `dorny/paths-filter` error — those outputs are empty, all eleven gates evaluate false and report `skipped`, and `if: always()` runs the loop anyway. A loop that only rejects `failure` and `cancelled` then passes, and `CI / required` certifies a merge having executed zero toolchain gates. Assert `needs.changes.result == 'success'` before the loop, not merely "not failure": `changes` carries no `if:`, so unlike `commit-lint` on push events it can never be legitimately skipped, and a skip there is exactly the symptom. The header comment claimed every `needs` entry was verified; now it is, and the comment says which rule covers which.
…hine `i18n-check` compares `locales/` against four generated trees, and it fails on drift whichever side moved — a hand-edited generated catalog is precisely what it exists to catch. It runs in exactly one place, `check-rust`, which exactly one job invokes, so the `rust` paths filter is the whole of its CI reachability. That filter gained `locales/**` two commits ago but none of the generated targets, which left the perverse arrangement of `hk.pkl`'s pre-push step catching a generated-catalog edit that CI would skip. Add the web messages, the Swift `.xcstrings` and the Android `strings.xml` set, so the CI gate is at least as wide as the local one.
…omment Two loose ends from the `docs-truth` filter widening. `hk.pkl`'s `check-docs-truth` step says it "Mirrors the `docs-truth` paths filter in ci.yml", which stopped being true the moment eight manifest globs went into ci.yml alone — so the same package-addition that now fires the CI job still slipped past pre-push. Both lists carry the eight. The ci.yml comment also described the `roadmap` check in the present tense, but neither that check nor the ROADMAP.md it reads exists at this commit; both arrive with `#422`. Say so, and say why the globs are correct to land first.
… ten entries Two remaining overclaims in the paragraph this branch rewrote. "Every toolchain's format/lint checks" was not true: `lint-check-ffi` runs in `check-rust` and in no hook, so it is the one lint pre-push never performs. Said plainly, and named in the list of what CI is left to do. The entrypoint list offered five commands as "exactly what CI runs" while CI has ten jobs with a local equivalent. `test-rust` was the glaring omission — this same branch made that job blocking, and it is not part of `check-rust` — along with `check-docs-truth`, `check-vision` and `check-md`. Listed as a block with what each covers, and `rust-cross` called out as the one job with no local entrypoint rather than silently absent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Eight holes in the merge gate, closed — the six #416 names, plus two the run found while this
branch was open. Each was a check that existed and could not fail, or a document that described
a check nobody was running.
requiredcould not see a red test job.ci.yml's aggregated status check listedrust-testinneedsand omitted it from the verification loop, so a failing Rust testsuite reported through to a mergeable pull request. The job's own comment has said it is
blocking since
S-C59; the aggregate disagreed. Fixed, and the loop is now asserted tocover every job in
needs.check-rustreads.i18n-checkandi18n-guardrunxtaskoverlocales/;build-check-wasmiscargo check -p capsule-wasm;build-rustis a workspace build, socapsule-core-ffiis in it. A pull request touchingonly those paths skipped the whole Rust job.
locales/**,capsule-wasm/**andcapsule-core-ffi/**added.protocfor a build step that retired withS-D28. No.proto,no
prost/tonic, no build script that shells out to it —capsule-sdk/build.rssays soin its own header. Removed from
ci.yml'srust-crossmatrix and from bothbuild-ios.ymljobs, one of which is a self-hosted macOS runner.
test-webpassed with no tests.bun test --pass-with-no-teststurned a discoveryregression into a green gate. Flag dropped.
CONTRIBUTING.mdsaid "the same checks run onpre-pushand on every PR in CI" whilehk.pklcovered two ofcheck-rust's fourteensteps. Four cheap boundary gates added to pre-push;
CONTRIBUTING.mdrewritten to describethe subset honestly and point at the per-toolchain entrypoints for the full gate.
SoftwareSignerSmokeTestdid not compile.FfiWorkspace.createandcreateWithHardwareSignereach grew anFfiClientBuildparameter withS-D15; the teststill passed three arguments to a four-parameter constructor and six to a seven-parameter
one. Both call sites fixed against the regenerated binding.
Two further gate holes were added to this lane after it started, both the same defect class:
docs-truthdid not fire for the manifests it reads. Theroadmapcheck resolves everyROADMAP.mdrow against the rootCargo.toml,settings.gradle.kts,capsule-swift/Project.swift, eachPackage.swift/package.json/pyproject.toml,.gitmodulesand thelegacy-review/subtrees. The filter listed none of them, so a pullrequest adding a package without touching Markdown skipped the job. Eight globs added.
#[cfg(not(debug_assertions))]test is dead — never compiled, never run (ci: no job runs cargo test --release, so every cfg(not(debug_assertions)) test is dead #428).
[tasks.test-rust-release]added. It isdeliberately not wired into
test-rustyet: see decision 9.Validation
Run inside the worktree, on
b3c2c754—git statusclean, HEAD ==origin/chore/close-ci-gate-holes-416.python3 -c 'import yaml; yaml.safe_load(...)'onci.ymlandbuild-ios.ymlci.ymlstill yields its 13 jobs,build-ios.ymlits 2python3assertion:required.needsminuschanges== the set ofneeds.*.resultin the looppython3 -c 'import tomllib; tomllib.load(open("mise.toml","rb"))'tasks.test-web.run == "bun test"hk validatehk.pkl is validpython3+ PyYAML dump of thedocs-truthfiltercargo nextest run -p capsule-i18n --cargo-profile releaseformat::tests::an_unrenderable_icu_construct_is_refused_in_debug_builds. Mechanism and fix in #429; this is exactly the dead-test class #428 describes, and it is why the new task is unwiredmise run architecture-checkcd capsule-web && bun install --frozen-lockfilef433d918; passes after merging the current base (99dd4bc8, "build(web): regenerate bun.lock…") into this branch — 385 installs, no changes. Not caused, and no longer outstandingmise run test-webcd capsule-web && bun test src/generated--pass-with-no-testsexits 0. This is the regression the flag was hidingmise run gen-bindingscreate(root, passphrase, tier, client: FfiClientBuild)atcapsule_core.kt:2002,createWithHardwareSigner(…, client: FfiClientBuild)at:2021; both match the edited call sitesmise run check-commitsmise run format-check-kotlin/lint-check-kotlinJAVA_HOME is not set and no 'java' command could be found; there is nojavapin in[tools], andcapsule-core-kotlin/build.gradle.ktsappliescom.android.library(compileSdk 36), which needs an Android SDK at configuration time.build-android.ymlin CI is the authority for item 6mise run check-rusti18n-check,i18n-guard,architecture-check,license-check) andopenapi-check-kynosmise run test-rustmise run check-docs-truthroadmapcheck the new filter globs serve arrives with PR #422; the globs are harmless before it and correct aftermise run check-mdmise run check-webhk run pre-push --plan --from-ref chore/merge-v1-head-397 --to-ref HEADcheck-commits,architecture-check,format-check-kotlin,lint-check-kotlin,lint-check-md,check-docs-truth;i18n-check,i18n-guardandlicense-checkcorrectly skiphk run pre-push --plan --all --glob <g>for five representative pusheshk run pre-push --from-ref origin/master --to-ref HEAD -S i18n-check -S i18n-guard -S architecture-check -S license-checkhkitself, not just throughmiseGlob selection proof for the four new pre-push steps (
hk run pre-push --plan --all --glob <pattern>, which simulates a push whose changed files are exactly that set):locales/**i18n-check,i18n-guard,architecture-check,lint-check-md,check-docs-truthcapsule-web/src/routes/**i18n-guard,architecture-check, the three web steps,check-docs-truth—i18n-checkcorrectly skipscapsule-android/src/androidMain/res/**i18n-check(13 generatedstrings.xml),check-docs-truth— nothing RustCargo.locklicense-checkonlycapsule-core/src/**architecture-check— both i18n gates correctly skipThe third row is why decision 7 below departs from the plan's coarser glob: a hand-edit to a
generated
strings.xmlis precisely the drifti18n-checkexists to catch, andcapsule-i18n/**alone would have skipped the step on it.CI is fully green on the final head
1518c082— run33580398701, all 16 jobssuccess,including
required, whose log readsAll required jobs passed or were skipped.Theper-item evidence below is drawn from that run plus the earlier
c8157c16run(
33577788190) and the Android runs33577788151/33580398714:Rust (tests)pass — item 1's newly blocking job is green, so making it blocking does notblock this branch.
Rust (fmt + clippy + build),Web,Docs,Docs truth,Markdown,Kotlin (ktlint + detekt),VisionandCommit lintall pass.Webpassing is the lockfile point below.All four
Rust crosslegs (android,linux-arm64,windows,apple) run without anyprotocinstall step — item 3 verified on real runners.Item 6 has a clean before/after against this PR's own base. On
f433d918the Android run33574327356fails at:core:compileDebugUnitTestKotlinwith exactlySoftwareSignerSmokeTest.kt:49:82 No value passed for parameter 'client'and:66:17 No value passed for parameter 'client'. Onc8157c16(run33577788151) and again on1518c082(run33580398714),:core:compileDebugUnitTestKotlin,:core:testDebugUnitTestand:core:testReleaseUnitTestall clean.
./gradlew :core:test -Pcapsule.wireFficompiles and passes, which is the issue'sown success criterion for this item.
Build & test Capsule.app against the Rust core(build-ios.yml) pass — the workflow whosetwo
protocsteps this change deletes builds the FFI xcframework and the app without them.Swift (… iPhone UI sweep)passed on1518c082after failing onc8157c16with noSwift-affecting change between the two commits — which settles the classification as flaky.
The earlier failure was
MockBridgeTests.swift:245: Caught error: BridgeTestTimeout(). Mechanism:waitForSubscriber(MockBridgeTests.swift:319-324) spins up to 1000Task.yield()s waitingfor an actor registration and throws if the cooperative pool has not scheduled it by then —
a load-dependent scheduling race with no wall-clock deadline. This branch changes no Swift, no
capsule-swift/**, and nothing in that job's steps; the same job passed on both base commits(runs
33574327583and33576515009). Worth its own issue, not filed here to avoidduplicating a lane that may already own it.
requiredfailed onc8157c16and passed on1518c082— the failing run is itself ademonstration of item 1's mechanism working: the loop printed
A required job did not pass (result: failure)off a job result it verifies, and stoppedbeing mergeable. The gate is no longer decorative.
The Android workflow is still red, and not because of this change. Its only
FAILEDtask is:android:compileDebugKotlinAndroid, with all 48 errors incapsule-android/**— files thisbranch does not touch (
Unresolved reference 'di' / 'initKoin' / 'ListViewModel' / 'DetailViewModel' / 'museumObject': #389's app half, over Kotlin-Multiplatform-templateleftovers). It is red on
origin/mastertoo.build-android.ymlis not inCI / required, andthis change deliberately does not add it — see "Will not" in the record.
No gate was substituted for another. Every failure above is classified inline.
Review repair — six findings closed on
b3c2c754Four commits on top of
1518c082, one concern each. Every mechanism was reproduced againstthe tree before the edit.
requiredhadchangesinneedsand in neither check. Ifdorny/paths-filteror its checkout dies, its outputs are empty, all eleven gates evaluate false and reportskipped,if: always()runs the loop, and the aggregate exits 0 having executed zero toolchain gates. My comment claiming "every job inneedsis verified" was false for exactly that job.8736edb9— assertneeds.changes.result == 'success'before the loop. Strict equality, not "not failure", becausechangescarries noif:(verified:changes.ifisNone, unlikecommit-lint'sgithub.event_name == 'pull_request'), so a skip there is only ever the symptom. Comment rewritten to say which rule covers which job.rustfilter hadlocales/**but none of the three generated i18n targets.i18n-checkruns in exactly one task (mise.toml:89, insidecheck-rust) which exactly one job invokes, so the filter is the whole of its CI reachability — leavinghk.pkl's pre-push step catching a generated-catalog edit that CI would skip, the inverse of the intended relationship.9dd69f98— addedcapsule-web/src/i18n/messages/**,capsule-swift/Generated/**,capsule-android/src/androidMain/res/values*/strings.xml. Chose the filter addition over a separate output + job, as suggested.git ls-files -- ':(glob)…'(git's:(glob)has picomatch's no-slash-crossing*) matches 13 / 2 / 13 tracked files respectively. Forcapsule-android/…/values-ja/strings.xml: the hk plan selectsi18n-check, and the same path is matched by the rust filter — both sides now firehk.pkl'scheck-docs-truthsays it "Mirrors thedocs-truthpaths filter in ci.yml"; the eight manifest globs went into ci.yml only.278fc157— all eight mirrored into the hk step; comment notes the mirroring claim is load-bearing.hk-not-in-ciis emptylint-check-ffiis incheck-rustand in no hook.b3c2c754— reworded, andlint-check-ffinamed in the CI-only list.grep -c lint-check-ffi hk.pkl→ 0test-rust— which this PR made blocking, and which is not part ofcheck-rust— was missing, along withcheck-docs-truth,check-vision,check-md.b3c2c754— all ten listed as a block with what each covers;rust-crosscalled out as the one job with no local entrypoint.mise tasks;mise run lint-check-mdexit 0docs-truthcomment described theroadmapcheck in the present tense; neither it norROADMAP.mdexists at this SHA.278fc157— comment is forward-looking and cites#422.ls ROADMAP.md→ absent;grep -c roadmap capsule-docs/scripts/docs-truth.mjs→ 0Validation commands for the repair, as requested:
python3 -c 'import yaml; yaml.safe_load(...)'onci.ymlandbuild-ios.ymlactionlint[tools]pin; the PyYAML parse plus therequired-loop parity assertion stand in, and neither is claimed as equivalenthk validatehk.pkl is validhk run pre-push --plan --all --glob 'capsule-android/src/androidMain/res/values-ja/strings.xml'check-commits,i18n-check,check-docs-truth; all eighteen other steps report "no files matched filters"mise run lint-check-mdmise run check-commitsb3c2c75433583780821, all 16 jobssuccess,requiredincluded. Its log shows the new guard evaluating on real data (if [ "success" != "success" ]) before the eleven-gate loop, thenAll required jobs passed or were skipped.One self-caught defect: the L4/L5 commit message initially contained a stray non-ASCII
character. Amended before the push, while the commit was still unpublished — no published
history was rewritten.
Risks and rollout
CI configuration and one test source file. No persisted data, no public interface, no
migration and nothing to observe at runtime — the backout for each item is named in
"Decisions taken" below.
The real risk is item 1 doing its job:
rust-testnow blocks. If the Rust test suite is redon a branch, that branch stops being mergeable, which is the intent but is a behaviour change
for anyone who had learned to ignore the job.
Item 2 makes a locale-only or wasm-only pull request pay the full Rust gate. That is the point for
rustandrust-test, and it has a side effect worth naming:rust,rust-testandrust-crossall key off the same
changes.outputs.rust, so a locale-only pull request now also runs thefour-leg
rust-crossmatrix, whichi18n-checkdoes not need. Givingrust-crossits own,narrower filter is the obvious follow-up; it is a filter-topology change rather than one of the six
holes, so it is left out of this change rather than smuggled in. Cost today is roughly ten runner-
minutes on catalog-only pull requests.
One correction to make before merging. While this branch was in flight the base branch
gained
99dd4bc8("build(web): regenerate bun.lock for the package.json the v1 head changed"),which fixes a stale
capsule-web/bun.lockthat was breakingbun install --frozen-lockfileinthe
webanddocsjobs. Not knowing that fix had landed, this lane filed #427 for thesame defect. #427 is redundant and should be closed — it was verified fixed by merging the
current base into this branch, after which
bun install --frozen-lockfilereports "385 installsacross 438 packages (no changes)". This lane's write permissions do not extend to closing
issues, so it is flagged here rather than closed.
That base merge (
6343dd93) is the only content on this branch that is not one of the sixitems; it touches
capsule-web/bun.lockonly, and it is there so the branch's own CI runreflects the state this will actually merge into.
Related Issues
Refs #416
Refs #428 (the release-profile gate hole; the task lands here, the wiring waits on #429)
Filed by this lane: #429 (blocks #428's wiring — see decision 9) and #427
(redundant, already fixed on the base — see "Risks and rollout"; it should be closed).
This also lands the harness half of #389:
capsule-core-kotlin's two staleFfiWorkspacecall sites are the only Kotlin ones in the tree (
grep -rn 'FfiWorkspace\.create' --include='*.kt'finds no others outside
target/), so #389's remaining scope is whatever the Android app itselfneeds, and #389 stays open.
Decisions taken
Decisions taken inside the manifest during delivery, in the same shape:
Unresolved review notes
Contributor Checklist
CONTRIBUTING.md.