fix(ios): move the published fallback forward and stop it rotting silently - #812
Conversation
…ently The default branch fell back to the 0.7.0 xcframework while 0.16.0 was the newest release. The Swift bindings are generated from whatever commit you are on and UniFFI checks the pairing at initialization, so anything resolving this package paired bindings from this tree with an August binary and got apiChecksumMismatch for the whole library rather than one degraded callback. Two things kept that invisible. The manifest follow-up existed only on the workflow_call path. A dispatch declared no manifest branch at all, so the manual publish that shipped 0.16.0 skipped it without comment. The input now exists on dispatch too, still empty by default, because a dispatch is also how a pre-release is cut and the default branch should not fall back to one. Every job sets TRUAPI_USE_LOCAL_BINARY=1, so nothing ever resolved the published asset and a stale URL or checksum could not fail anything. release-guard now compares the version the manifest names against the newest published release. It runs unconditionally, because releases move this and pull requests do not, so there is no path to filter on. The manifest names 0.16.0. Its checksum is the sha256 of the published asset, confirmed by hashing the 0.7.0 asset and getting exactly the checksum this file already carried.
|
CI Status: 18 required jobs green, 14 passed and 4 skipped by path filter. All job results
Commit |
|
@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.
Evidence gathered. Writing the review now.
TL;DR
Points the SwiftPM fallback at the 0.16.0 xcframework, lets a manual release-ios dispatch open the manifest PR, and adds a CI step that fails when the manifest is not the newest stable iOS release. 1 blocking, 4 minor.
Summary
Package.swift now names the 0.16.0 asset and checksum instead of 0.7.0. release-ios.yml gains a manifest_branch input on workflow_dispatch, empty by default, so a manual publish can open the Package.swift follow-up PR. ci.yml's release-guard job gets an unconditional step that reads the version out of publishedBinaryURL, lists @parity/ios-host@X.Y.Z releases with gh, and fails when the manifest does not name the highest one.
What the record says
- Issue #723 is the root of this: main's fallback binary can never match bindings generated from HEAD, because UniFFI checks the pairing at initialization. The recorded resolution there is to drop the fallback on main. PR #725 instead kept it for remote consumers and made the staged local xcframework win when present. This PR continues the #725 line.
- 0.16.0 was cut by #758, a normal
release:commit that should have passedmanifest_branch: main. Issue #769 records that a later push to main cancelled release-commit CI and silently skipped publishes at that time. That explains why 0.16.0 went out through a dispatch, and #809 has since fixed the cancellation. So the dispatch gap this PR closes was reached through a bug that is already fixed. - PR #747 set up the three guards:
release-guardandchangeset-guardblock per PR, while "default branch manifest versus what is published" lives in the dailyregistry-drift.ymlthat maintains one issue. This PR puts a check of the second shape into the first slot. - Issue #671 records that
CI Statusis the only required check and that skipped jobs count as passes precisely so unrelated PRs are not stalled. - Owners per Lore: pgherveou, decrypto21 (opened #758), TarikGul, valentinfernandez1.
Concerns
-
Blocking. Every PR and the merge queue go red between an iOS release and its manifest PR merging.
.github/workflows/ci.yml:794runs onpull_request,push, andmerge_groupwith no condition. Oncerelease-iospublishes0.17.0, main still names0.16.0, so this step fails on every branch untilrelease/ios-host-0.17.0merges. That PR needs ahost-sdk-teamreview (release-ios.yml:259) and only gets CI through an explicit dispatch (release-ios.yml:266). During the window nothing can merge, and becauserelease.ymlgates on green CI, no other package can publish either. This is the situation #671 was set up to avoid. Options, cheapest first: move the check intoregistry-drift.ymlnext to the npm comparison it mirrors, or enable auto-merge on the manifest PR, or fail only on the manifest PR and on pushes to the default branch. -
Release branches fail permanently.
.github/workflows/ci.yml:816-819takes the global maximum across all@parity/ios-hostreleases.release.yml:416passes the release branch asmanifest_branch, so a release cut fromrelease/v0.3.2(a CI trigger branch) gets a manifest naming its own version, and that branch's CI then fails forever because main's release is higher. -
A stable-version dispatch with no manifest branch replays the 0.16.0 incident, louder.
.github/workflows/release-ios.yml:42-46leaves the input optional and nothing ties "empty branch" to "pre-release version". Dispatching0.17.0with the default publishes a stable release, opens no PR, and concern 1 fires on every branch until someone hand-editsPackage.swift. One guard in the workflow closes it: a version without a pre-release suffix requiresmanifest_branch, one with a suffix forbids it. -
Docs now stale.
docs/RELEASE_PROCESS.md:194-195says a dispatched run "passes nomanifest_branch". It can now. CLAUDE.md requires docs to move with the code. -
Silent pass past 200 releases.
.github/workflows/ci.yml:816lists 200 releases across six packages plus the CLI. Once 200 releases of other packages postdate the newest iOS one,newestis empty and the step exits 0 with a notice.gh api --paginateon the releases endpoint removes the ceiling.
Not verified here: the 0.16.0 checksum at Package.swift:38. The sandbox blocked the download. The commit message says it was hashed against the asset.
Questions for the author
- Who consumes main's fallback? A consumer pinning
exact("0.16.0")gets the tag's ownPackage.swift, so main's value never reaches them. Abranch: "main"consumer hits #723 again the first time a UniFFI surface changes after 0.16.0, so the error text atci.yml:826("every use of TrUAPIHost fails to initialize" until the URL is updated) promises more than the update delivers. Is the intent to keep the fallback only for trees whose surface still matches the newest release, or is #723's "drop it on main" still the plan and this is a stopgap? - Given #809 already fixed the cancellation that pushed 0.16.0 onto the dispatch path, is the per-PR blocking check still worth the freeze window, or does the daily drift issue cover the remaining risk?
🤖 Reviewed by Lore (Parity knowledge base) · 34 agent turns · 244.1s · knowledge as of 2026-09-16
Checking it in ci.yml deadlocked the repository. That job runs on every pull request, push and merge queue entry with no condition, so between an iOS release publishing and its manifest follow-up merging, every branch went red. The follow-up needs a review and gets CI only through an explicit dispatch, and release.yml gates on green CI, so nothing could merge and nothing could publish. Taking the global maximum across releases also broke release branches permanently, since a branch whose manifest names its own version always sees a higher one on the default branch. The check now runs in registry-drift.yml, beside the npm comparison it mirrors. That workflow is scheduled, checks out the default branch and reports by opening an issue, so drift is visible without gating a merge, and a release branch is never compared against the default branch's releases. release-ios.yml now requires the version and the manifest branch to agree. A stable release has to name a branch for the follow-up, and a pre-release must not, since a pre-release exists for app-side testing of an unmerged change and must never become what the default branch falls back to.
|
All three fixed in the latest commit. The first was a deadlock I introduced and Blocking, the merge window. Moved out of Release branches failing permanently. The same move fixes it. The comparison Stable dispatch with no manifest branch. The |
…pm step The comparison ran inside the step that queries npm, which made that step also query GitHub. The test harness stubs every external command to fail closed, so four of its cases broke, and they were right to: the step's contract had quietly grown. It is its own step now, writing what it finds to a file the npm step folds into the same report, so one issue still covers both kinds of drift and the npm step is byte for byte what it was. Five cases cover it: a fallback naming the newest release, one naming an older release, a pre-release never counting as newest, a manifest naming no release at all, and a recorded finding reaching the report.
|
@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.
Since last review
- Resolved: the per-PR blocking check is gone from
ci.yml; the comparison now lives inregistry-drift.yml, which checks out the default branch only, so the merge freeze and the permanent release-branch failure are both gone. The stable-dispatch-without-manifest-branch gap is closed by the new guard step inrelease-ios.yml. Five script tests cover the new step. - Remaining: the docs still describe the old behaviour (concern 4), and the 200-release ceiling on
gh release listis unchanged (concern 5, now a missed report rather than a false pass). The 0.16.0 checksum is still unverified here because the sandbox blocks the download. I also could not run the script tests for the same reason. - New: the guard rejects pre-release versions on the
workflow_callpath thatrelease.ymlstill permits, the drift issue body points iOS drift at an exceptions file that cannot except it, and there are two leftovers from the earlier revision.
TL;DR
Moves the SwiftPM fallback to 0.16.0, makes release-ios refuse a stable dispatch without a manifest branch, and reports a stale fallback through the daily drift issue. 0 blocking, 5 minor.
Summary
Package.swift names the 0.16.0 asset and checksum. release-ios.yml gains manifest_branch on dispatch and a first step that requires a stable version to name a branch and a pre-release not to. registry-drift.yml gains a step that reads the version out of publishedBinaryURL, finds the highest stable @parity/ios-host release, and writes a drift line that the npm step folds into the single daily issue. release-consistency.test.mjs exercises the new step through the same fixture as the npm comparison.
What the record says
- #723 is the root cause and records "drop the
publishedBinaryURLfallback on main entirely" as the resolution. #725 kept the fallback for remote consumers and made a staged local xcframework win. This PR stays on the #725 line and adds monitoring instead of removal. - #747 placed "default branch versus what is published" in the daily
registry-drift.ymlwith one maintained issue and an explicit exceptions file. The revised PR now follows that split. #776 is the live instance of that issue, so iOS drift lines will land in an issue whose body was written for npm. - 0.16.0 was cut by #758. #769 records the CI cancellation that pushed it onto the dispatch path, fixed since by #809. The record has nothing on whether a pre-release has ever gone through
release.ymlrather than a dispatch. - Owners per Lore: TarikGul, Imod7, filvecchiato.
Concerns
-
Pre-release through
release.ymlnow fails after the release commit has merged..github/workflows/release.yml:100accepts0.17.0-beta.1as an iOS version and:416always passes the head branch asmanifest_branch. The new guard at.github/workflows/release-ios.yml:77-82then exits 1 on theworkflow_callpath, after therelease:commit is already on the branch.docs/RELEASE_PROCESS.md:190-195says pre-releases are cut by dispatch, so either tighten therelease.ymlregex to stable-only for iOS or let the guard skip the manifest step for a pre-release instead of failing. -
iOS drift has no exception path, and the issue body sends the reader to the wrong file. The exceptions file is consulted only inside the npm loop at
.github/workflows/registry-drift.yml:75-89; the iOS line is appended afterwards at:106-110. The body at:144still says to document exceptions inregistry-drift-exceptions.json. Between a release and its manifest PR merging, which needs ahost-sdk-teamreview and a dispatched CI run perrelease-ios.yml:292-299, the issue will report iOS drift with no way to acknowledge it. -
Docs are stale in four places.
docs/RELEASE_PROCESS.md:194-195says a dispatched run "passes nomanifest_branch".docs/RELEASE_PROCESS.md:303,README.md:403, andCLAUDE.md:137describe the drift workflow as npm-only. CLAUDE.md requires docs to move with the code. -
Silent miss past 200 releases.
.github/workflows/registry-drift.yml:57lists 200 releases across every package plus the CLI. Once 200 other releases postdate the newest iOS one,newestis empty and the step reports no drift.gh api --paginate repos/$GH_REPO/releasesremoves the ceiling. -
Leftovers from the earlier revision.
.github/workflows/ci.yml:786is a lone added blank line, soci.ymlis in the diff for no reason.scripts/lib/release-consistency.test.mjs:10hasexistsSync,}appended out of order; prettier only covers ts, tsx, and md, so nothing in CI will fix it. The comment at.github/workflows/release-ios.yml:37-41narrates what the dispatch used to do, which CLAUDE.md asks to keep out of code comments.
Questions for the author
- #723's recorded resolution is to remove the fallback on main. This PR keeps it and adds a daily check. Is the fallback now intended to stay, and if so, who resolves main's
Package.swiftwithout a staged xcframework? A consumer pinningexact("0.16.0")never reads it, and abranch: "main"consumer still hits the pairing mismatch the moment a UniFFI surface changes after 0.16.0. - Was the 0.16.0 checksum at
Package.swift:38hashed from the release asset? The commit message says the method was validated against 0.7.0. I could not download the asset here to confirm.
🤖 Reviewed by Lore (Parity knowledge base) · 37 agent turns · 200.8s · knowledge as of 2026-09-16 · re-review
…ease release.yml accepts a pre-release as an iOS version and always passes the head branch, so requiring the two to disagree failed the call after the release commit had already merged. Whether the manifest follows is now a decision the step makes: a pre-release leaves it alone whichever path asked, and a stable release still has to name a branch. The release listing is paginated. A fixed limit of 200 spans every package, so once that many other releases postdate the newest iOS one the comparison would have found nothing and reported no drift. Also drops a stray blank line that put ci.yml in the diff for nothing, restores the import ordering in the test file, and removes a comment narrating what the dispatch input used to do.
|
All three fixed. Pre-release through The 200 ceiling. Replaced with Leftovers. The stray blank line is gone, so 49 tests still pass. |
filvecchiato
left a comment
There was a problem hiding this comment.
approving to unblock, a small nit
Under set -euo pipefail a grep that matches nothing returns 1, the assignment inherits it, and the step dies before the emptiness test below it can run. It died silently too, with no error annotation, and because it runs ahead of the npm comparison the whole day's drift report went with it. Covered by a case that stubs a release list with no iOS entry, which fails against the previous version and passes now.
|
Fixed in feb0a12, and your reproduction is exactly right.
Covered by a case that stubs a release list with no iOS entry. It fails against Worth noting this is the same shape as the earlier finding on this PR: a check |
The default branch fell back to the 0.7.0 xcframework while 0.16.0 was the
newest release. Bindings are generated from whatever commit you are on and
UniFFI checks the pairing at initialization, so anything resolving this package
got
apiChecksumMismatchfor the whole library.It went unnoticed because the manifest follow-up exists only on the
workflow_callpath.workflow_dispatchdeclared nomanifest_branchand thestep is gated on it, so the manual publish that shipped 0.16.0 skipped it in
silence. The input exists on dispatch now, empty by default, since a dispatch is
also how a pre-release is cut.
And because every job sets
TRUAPI_USE_LOCAL_BINARY=1, nothing ever resolved thepublished asset, so a stale URL or checksum could not fail anything.
release-guardnow compares the version the manifest names against the newestpublished release.
This does not decide whether the default branch should carry a fallback at all.
Closes #723.