Skip to content

Resolve the pinned iOS XCFramework the way SPM does - #10

Merged
jsantelys-as merged 2 commits into
mainfrom
chore/ios-fixture-resolve-pinned-xcframework
Aug 12, 2026
Merged

Resolve the pinned iOS XCFramework the way SPM does#10
jsantelys-as merged 2 commits into
mainfrom
chore/ios-fixture-resolve-pinned-xcframework

Conversation

@jsantelys-as

@jsantelys-as jsantelys-as commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #9. The iOS contract fixture was validating the bridge against a binary that is not the one we ship. This also corrects an iOS note on #9 that the same problem caused.

The bug

Tests~/Native/iOS/run-tests.sh obtained the XCFramework with git archive <tag> AppstackSDK.xcframework from an ios-appstack-sdk checkout. That committed directory is vestigial after the distribution repo moved to binaryTarget(url:checksum:), and it does not track the tag at all:

4.4.0        xcframework-subtree=643dea158185c0977d1d6aa395e053c510cb7010
4.4.1        xcframework-subtree=643dea158185c0977d1d6aa395e053c510cb7010
4.5.0-rc0    xcframework-subtree=643dea158185c0977d1d6aa395e053c510cb7010
4.5.0        xcframework-subtree=643dea158185c0977d1d6aa395e053c510cb7010

Identical across all four tags. So the fixture compiled the bridge against 4.4.0-era bits while printing the version it was asked to pin, and would have kept doing that for every future tag. What SPM downloads — and what a Unity iOS build actually links — is the release zip named by binaryTarget(url:), which the fixture never touched.

This is not academic: the committed directory does not export setCustomerUserId, while the real 4.5.0 release artifact does.

Correction to the iOS note on #9

#9's description stated that the distributed 4.5.0 binary does not export setCustomerUserId and that #7's iOS blocker still stood. That was wrong and has been corrected on #9. It was based on the stale committed directory described above. Verified against the real artifact:

  • The binaryTarget URL from Package.swift at tag 4.5.0 downloads an archive whose SHA256 is 4745e8d48767daf034fdaa4e347c0a7f52e5589cba265af7bc3e7da12e0c57e9, matching the checksum the manifest declares, so it is the artifact SPM resolves.
  • setCustomerUserId is present in its public and private .swiftinterface files and in the framework binary under nm.

iOS 4.5.0 ships the setter, so #7's blocker is cleared and nothing in ios-appstack-sdk needs re-cutting. The one loose end there is the stale AppstackSDK.xcframework/ directory: it is what a reader would naturally inspect, and deleting it — already planned as part of the binaryTarget migration — would also shrink clones.

The fix

The runner now resolves the binary the way SPM does:

  1. reads the binaryTarget url and checksum from Package.swift at the tag;
  2. rejects a manifest whose URL does not point at the expected release;
  3. downloads that artifact and refuses to continue unless the SHA256 matches the declared checksum;
  4. compiles the production bridge against it and checks the C ABI symbols as before.

It deliberately no longer reads the committed directory, and DEVELOPMENT.md plus Tests~/Native/README.md now say why, so this does not get quietly reintroduced.

This requires network access. That matches the Android fixture, which already resolves from Maven Central, so the suite's requirements are unchanged in kind.

The caller-supplied-XCFramework escape hatch from #9 is untouched and still reports those inputs as unverified.

Verification

  • The bridge compiles against the real checksum-verified 4.5.0 artifact, with all 7 expected C symbols present: Verified iOS bridge against AppstackSDK 4.5.0 (checksum-verified release artifact) and all expected C symbols.
  • The checksum gate is not decorative: with a tampered digest the runner aborts with exit 8 before compiling.
  • Ran on this branch rebased onto current main.

The fixture read the AppstackSDK.xcframework directory committed in the
distribution repository via git archive <tag>. That directory is vestigial after
the move to binaryTarget(url:checksum:): its subtree hash is identical across
4.4.0, 4.4.1, 4.5.0-rc0 and 4.5.0, so it does not track the tag at all. The
fixture was therefore compiling the bridge against 4.4.0-era bits while
reporting whichever version was pinned, and would keep doing so for every
future tag.

Concretely, that hid a real API difference: the committed directory does not
export setCustomerUserId, while the 4.5.0 release artifact does.

The runner now reads the binaryTarget url and checksum from Package.swift at the
tag, downloads that artifact, and refuses to continue unless the SHA256 matches
the declared checksum. It also rejects a manifest whose URL does not point at
the expected release. This needs network access, matching the Android fixture,
which already resolves from Maven Central.

Verified: the bridge compiles against the real 4.5.0 artifact and all expected C
symbols are present; a tampered checksum aborts with exit 8.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 209ca56b-1d7d-417e-af55-f53cf70b7d72

📥 Commits

Reviewing files that changed from the base of the PR and between 1202bd4 and a4cfa08.

📒 Files selected for processing (1)
  • Tests~/Native/iOS/run-tests.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests~/Native/iOS/run-tests.sh

📝 Walkthrough

Summary by CodeRabbit

  • Documentation

    • Clarified that iOS validation uses the checksummed release artifact referenced by the Swift package tag.
    • Updated test runner guidance to explain artifact downloads, checksum verification, and local XCFramework limitations.
  • Bug Fixes

    • Improved SDK validation by verifying the release artifact’s SHA-256 checksum before testing.
    • Replaced reliance on the stale committed XCFramework directory.

Walkthrough

The iOS validation flow now reads the tagged Package.swift, downloads its declared binary artifact, verifies its SHA-256 checksum, and extracts it. Documentation identifies this artifact as authoritative and distinguishes it from unverified local XCFramework inputs.

Changes

iOS SDK validation

Layer / File(s) Summary
Release artifact validation
Tests~/Native/iOS/run-tests.sh
The tagged SDK path validates the binary target URL and checksum from Package.swift, downloads the release archive with retries and timeouts, verifies it, extracts it, and records checksum verification.
Validation guidance
DEVELOPMENT.md, Tests~/Native/README.md
The documentation identifies the release artifact as authoritative, excludes the stale committed XCFramework, and describes local inputs as unverified.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant run-tests.sh
  participant Package.swift
  participant Release artifact
  run-tests.sh->>Package.swift: Read binaryTarget URL and checksum
  run-tests.sh->>Release artifact: Download declared archive
  run-tests.sh->>Release artifact: Verify SHA-256 checksum
  run-tests.sh->>run-tests.sh: Extract verified XCFramework
Loading

Possibly related PRs

Poem

A rabbit checks the tag with care,
A trusted artifact waits there.
The checksum guards each hop,
The stale bundle does not stop.
Package.swift leads the test to run.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the stale XCFramework issue and the SPM-compatible resolution implemented by the pull request.
Title check ✅ Passed The title concisely and accurately describes the primary change to resolve the pinned iOS XCFramework as SPM does.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ios-fixture-resolve-pinned-xcframework
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch chore/ios-fixture-resolve-pinned-xcframework

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests`~/Native/iOS/run-tests.sh:
- Around line 48-51: Update the BINARY_URL validation in run-tests.sh to require
the production GitHub release URL prefix, including the expected SDK version,
rather than only matching the /download/${EXPECTED_SDK_VERSION}/ path. Keep
rejecting non-matching URLs before the artifact download proceeds.
- Around line 53-57: Update the curl invocation in the run-tests download block
to include explicit --connect-timeout and --max-time limits while preserving the
existing failure handling and archive output behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27a7f5ea-ea04-4386-a208-eaa87f07ca47

📥 Commits

Reviewing files that changed from the base of the PR and between 5567fce and 1202bd4.

📒 Files selected for processing (3)
  • DEVELOPMENT.md
  • Tests~/Native/README.md
  • Tests~/Native/iOS/run-tests.sh

Comment thread Tests~/Native/iOS/run-tests.sh Outdated
Comment thread Tests~/Native/iOS/run-tests.sh
Addresses review on #10.

The URL check matched /download/<version>/ anywhere in the string, so a manifest
declaring https://evil.example.com/download/4.5.0/... passed it. The checksum
does not cover that gap: it comes from the same manifest, so it proves integrity
against whatever the manifest claims rather than provenance. The check now
anchors the full release origin, which is the same origin
Editor/AppstackIOSPostProcessBuild.cs pins, making this a contract assertion
rather than only hardening: the fixture now confirms the tag resolves the
artifact a Unity build would resolve. The failure message prints both the
expected prefix and the declared URL.

The download is also bounded with --connect-timeout and --max-time, plus a small
--retry, since this PR is what makes the fixture depend on the network: a stall
should fail here instead of hanging until the outer CI timeout, and a transient
blip should not fail the run.

Verified: happy path still passes against the checksum-verified 4.5.0 artifact;
a non-matching origin aborts with exit 6 before any download; the lookalike URL
that the previous check accepted is now rejected.
@jsantelys-as

jsantelys-as commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Both review comments are fixed in a4cfa08.

Origin anchoring. The previous check matched /download/<version>/ anywhere in the string, so https://evil.example.com/download/4.5.0/AppstackSDK.xcframework.zip passed it. The checksum does not close that gap: it is read from the same manifest as the URL, so it proves integrity against whatever that manifest claims rather than provenance. The check now anchors the full release origin, which is the same origin Editor/AppstackIOSPostProcessBuild.cs pins — making it a contract assertion that the tag resolves the artifact a Unity build would actually resolve. The failure prints both the expected prefix and the declared URL. Verified: the lookalike URL the previous check accepted is now rejected.

Download limits. Added --connect-timeout 15 --max-time 300, and also --retry 3 --retry-delay 2. This PR is what introduces the network dependency, so both failure modes it creates are handled here: a stall fails fast instead of hanging until the outer CI timeout, and a transient blip does not turn a deterministic contract fixture into a flaky one.

Verification after both changes:

  • happy path passes: Verified iOS bridge against AppstackSDK 4.5.0 (checksum-verified release artifact) and all expected C symbols.
  • a non-matching origin aborts with exit 6 before any download
  • a tampered digest still aborts with exit 8

@jsantelys-as
jsantelys-as merged commit 56c5ff0 into main Aug 12, 2026
1 check passed
@jsantelys-as
jsantelys-as deleted the chore/ios-fixture-resolve-pinned-xcframework branch August 12, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant