mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-09 07:20:07 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 558802b552 | |||
| 7a37282d0b | |||
| 728ace6f81 |
@@ -77,13 +77,6 @@ jobs:
|
||||
# on either runner. Unit tests alone take ~16 min here. Upstream skips them the same way in
|
||||
# unit-tests-enterprise.yml. Cost: preview snapshot regressions are not caught in CI, so
|
||||
# re-record and check them locally when changing UI.
|
||||
env:
|
||||
# HealthChat: VoiceMessageCacheTests cannot write its URLFileProtection.complete cache file on
|
||||
# this runner's simulator (NSCocoaErrorDomain 513) — a runner-environment issue, not a code bug:
|
||||
# the suite passes locally and upstream, and neither recreating+booting the device nor clearing
|
||||
# state fixes it. Skip it here only, via -skip-testing. Drop this once the runner can write
|
||||
# data-protected files again.
|
||||
CI_SKIP_TESTS: VoiceMessageCacheTests
|
||||
run: swift run -q tools ci unit-tests --skip-previews
|
||||
|
||||
- name: Archive artifacts
|
||||
|
||||
@@ -55,16 +55,6 @@ struct RunTests: AsyncParsableCommand {
|
||||
"com.apple.CoreSimulator.SimRuntime.iOS-\(runtime.replacingOccurrences(of: ".", with: "-"))"
|
||||
}
|
||||
|
||||
/// Test identifiers to pass to `-skip-testing`, read from the `CI_SKIP_TESTS` environment variable
|
||||
/// (comma-separated, e.g. "VoiceMessageCacheTests"). Lets a workflow exclude a suite that only fails in
|
||||
/// its environment without touching the shared test.
|
||||
private var skippedTests: [String] {
|
||||
(ProcessInfo.processInfo.environment["CI_SKIP_TESTS"] ?? "")
|
||||
.split(separator: ",")
|
||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
.filter { !$0.isEmpty }
|
||||
}
|
||||
|
||||
func run() async throws {
|
||||
if let createName = createSimulatorName {
|
||||
guard let createType = createSimulatorType else {
|
||||
@@ -152,10 +142,6 @@ struct RunTests: AsyncParsableCommand {
|
||||
command += " -only-testing:\(scheme)/\(testName)"
|
||||
}
|
||||
|
||||
for skippedTest in skippedTests {
|
||||
command += " -skip-testing:\(scheme)/\(skippedTest)"
|
||||
}
|
||||
|
||||
command += " | \(formatter)"
|
||||
|
||||
try await CI.run(.path("/bin/zsh"), ["-cu", command])
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
| `ElementX/SupportingFiles/target.yml` | Entitlement arrays — regains the classic keychain group | 1 |
|
||||
| `ElementX/Sources/Screens/Authentication/StartScreen/AuthenticationStartScreenViewModel.swift` | Start screen logic — `.requiresBackup` redirect | 1 |
|
||||
| `UnitTests/Sources/AuthenticationStartScreenViewModelTests.swift` | Encodes the redirect behaviour | 1 |
|
||||
| `fastlane/Fastfile` | `release_testflight` + private build/signing lanes | 2 |
|
||||
| `fastlane/Fastfile` | `before_all` + `release_testflight` + private build/signing lanes | 2 |
|
||||
| `fastlane/Appfile`, `fastlane/.env.default`, `fastlane/Pluginfile` | fastlane configuration | 2 |
|
||||
| `Gemfile`, `Gemfile.lock` | Toolchain pinning | 2 |
|
||||
| `Gemfile`, `Gemfile.lock`, `Brewfile` | Toolchain pinning | 2 |
|
||||
| `HEALTHCHAT_CHANGES.md` | Release-notes source the workflow greps | 2 |
|
||||
| `.github/workflows/release-healthchat.yml` | Tag-triggered build + TestFlight upload | 2 |
|
||||
| `.gitignore` | Excludes `out/` and `provisioning_profiles/` | 2 |
|
||||
@@ -253,7 +253,7 @@ Build the branch and install it **without deleting the legacy app** — same bun
|
||||
- [ ] **Step 5: Observe**
|
||||
|
||||
Expected: the app finds the existing account and signs in without a manual login.
|
||||
Watch the log for `ClassicAppManagerError`. Two *different* lines end in `"skipping loadAccounts"` (`AuthenticationService.swift:52` and `:57`) and they mean opposite things — never match the bare substring. `"Already signed in to Element X before, skipping loadAccounts"` means Step 1's trap caught you and the run is **void, not failed**. `"Classic App not configured, skipping loadAccounts"` is the opposite: the `CLASSIC_APP_*` Info.plist keys are missing, `ClassicAppManager` never initialised, and migration is broken for every user — investigate, do not dismiss.
|
||||
Watch the log for `ClassicAppManagerError` and for `"skipping loadAccounts"` — the latter means Step 1's trap caught you and the run is **void, not failed**.
|
||||
|
||||
- [ ] **Step 6: Exercise the `.requiresBackup` path if you can reach it**
|
||||
|
||||
@@ -279,11 +279,9 @@ Leave the description template for the developer. Exactly one `pr-` label or `Da
|
||||
### Task 4: Add the toolchain files
|
||||
|
||||
**Files:**
|
||||
- Create: `Gemfile`, `fastlane/Pluginfile`, `fastlane/Appfile`
|
||||
- Create: `Gemfile`, `Brewfile`, `fastlane/Pluginfile`, `fastlane/Appfile`
|
||||
- Modify: `.gitignore`
|
||||
|
||||
**No `Brewfile`.** Nothing in this pipeline installs tools. The runner's toolchain is provisioned once by hand (Task 9 Step 5): `novaloop-mac-studio-001` is shared with the legacy release pipeline, so a per-run `brew` could upgrade `xcodegen` underneath a running release. Its only other purpose was installing `xcodes` for a version pin that no longer exists (Task 6).
|
||||
|
||||
- [ ] **Step 1: Branch**
|
||||
|
||||
```bash
|
||||
@@ -316,7 +314,15 @@ gem 'fastlane-plugin-xcodegen'
|
||||
|
||||
Only one plugin. The legacy also carries `fastlane-plugin-versioning`, but nothing in its Fastfile calls it — its version lanes are raw `File.read`/`gsub!`. Do not copy dead weight forward.
|
||||
|
||||
- [ ] **Step 4: Create `fastlane/Appfile`**
|
||||
- [ ] **Step 4: Create `Brewfile`**
|
||||
|
||||
```ruby
|
||||
brew "xcodesorg/made/xcodes"
|
||||
```
|
||||
|
||||
`xcodegen` is deliberately **absent**: it is installed on the runner once by hand, so `brew bundle` cannot churn it on every run. The legacy documents the same convention.
|
||||
|
||||
- [ ] **Step 5: Create `fastlane/Appfile`**
|
||||
|
||||
```ruby
|
||||
# For more information about the Appfile, see:
|
||||
@@ -328,7 +334,7 @@ team_id ENV["TEAM_ID"]
|
||||
|
||||
`APPLE_ID` is a machine-level shell variable on the runner, not a repo secret.
|
||||
|
||||
- [ ] **Step 5: Extend `.gitignore`**
|
||||
- [ ] **Step 6: Extend `.gitignore`**
|
||||
|
||||
Append:
|
||||
|
||||
@@ -340,17 +346,17 @@ provisioning_profiles/
|
||||
|
||||
`build` (`.gitignore:35`) and `fastlane/report.xml` (`:28`) are already covered upstream. `provisioning_profiles/` matters most: it holds downloaded signing material and must never be committed.
|
||||
|
||||
- [ ] **Step 6: Resolve gems and commit the lock**
|
||||
- [ ] **Step 7: Resolve gems and commit the lock**
|
||||
|
||||
Run: `bundle install`
|
||||
Expected: creates `Gemfile.lock`.
|
||||
|
||||
Commit the lock — the workflow's gem cache keys on `hashFiles('**/Gemfile.lock')`. Without it the key never changes, fastlane silently re-resolves, and a breaking fastlane release can change the pipeline between two runs of the same tag.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add Gemfile Gemfile.lock fastlane/Pluginfile fastlane/Appfile .gitignore
|
||||
git add Gemfile Gemfile.lock Brewfile fastlane/Pluginfile fastlane/Appfile .gitignore
|
||||
git commit -m "Add the fastlane toolchain for HealthChat releases"
|
||||
```
|
||||
|
||||
@@ -431,11 +437,16 @@ git commit -m "Add fastlane configuration for the HealthChat App Store build"
|
||||
```ruby
|
||||
default_platform :ios
|
||||
|
||||
# No Xcode version pin: novaloop-mac-studio-001 carries a single, globally-selected Xcode, so
|
||||
# whatever xcode-select points at is by definition the one we build with. Pinning bought nothing
|
||||
# and risked everything — a version the runner lacks sends xcodes into a multi-GB authenticated
|
||||
# download that no CI secret can satisfy, hanging the release. Change the runner's Xcode instead.
|
||||
platform :ios do
|
||||
#### Pre ####
|
||||
|
||||
before_all do
|
||||
# 26.4 is what novaloop-mac-studio-001 carries and what the legacy pipeline builds with.
|
||||
# Bumping this requires installing the new version on the runner FIRST — otherwise xcodes
|
||||
# attempts a multi-GB authenticated download, which no CI secret can satisfy, and hangs here.
|
||||
xcodes(version: "26.4", select_for_current_build_only: true)
|
||||
end
|
||||
|
||||
#### Public ####
|
||||
|
||||
desc "Builds the App Store ipa and uploads it to TestFlight (internal testers only)"
|
||||
@@ -592,10 +603,12 @@ Expected: lists `ios release_testflight` with its description. A Ruby syntax err
|
||||
|
||||
- [ ] **Step 3: Verify the version guard rejects a marker version**
|
||||
|
||||
Run: `brew bundle` first if you have never run it here — this step needs the `xcodes` CLI, for the reason below.
|
||||
|
||||
Run: `bundle exec fastlane release_testflight marketing_version:26.07.3-rc1`
|
||||
Expected: fails with `'marketing_version' must be 1-3 dot-separated integers`, before any build or signing work.
|
||||
|
||||
**On ordering.** With no `before_all`, the guard is the first thing the lane does — nothing runs ahead of it, so this step needs no Xcode selection and no tool beyond fastlane itself. That is the whole point: it costs seconds instead of the ~30 minutes of signing it exists to prevent. Do not reintroduce a `before_all` to pin the toolchain; see Task 9 Step 5 for why the runner's Xcode is a property of the machine.
|
||||
**On ordering.** fastlane's `before_all` always runs before the lane body, so `xcodes` selects the Xcode version *first* and the guard fires immediately after — not before. That still saves the ~30 minutes of signing the guard exists to prevent, which is the point, but it means this step needs the `xcodes` CLI present (it is in `Brewfile`). Without it the lane dies at line 10 with `Invalid default value for binary_path` — a missing-tool error, not a guard failure. Do not restructure the Fastfile to make the guard literally run first; `before_all` pinning the toolchain is the convention the legacy pipeline uses too.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
@@ -705,21 +718,12 @@ jobs:
|
||||
submodules: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Record toolchain
|
||||
# HealthChat: nothing pins Xcode (the runner carries a single globally-selected one), so this
|
||||
# log line is the only record of which compiler produced a given TestFlight build.
|
||||
run: xcodebuild -version
|
||||
|
||||
- name: Resolve marketing version
|
||||
id: version
|
||||
env:
|
||||
# Read via env, never `${{ }}` inside the script: the input is attacker-controlled text
|
||||
# that GitHub would splice into the shell before the regex below could reject it.
|
||||
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
VERSION="$INPUT_VERSION"
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
||||
fi
|
||||
@@ -772,6 +776,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-
|
||||
|
||||
- name: Brew bundle
|
||||
run: brew bundle
|
||||
|
||||
- name: Bundle install
|
||||
run: |
|
||||
bundle config path vendor/bundle
|
||||
@@ -869,13 +876,7 @@ Then restore the previous account: `gh auth switch --user joel-poyet_GALGH`.
|
||||
|
||||
`novaloop-mac-studio-001` is an **org-level** runner (`[self-hosted, macOS, ARM64]`, online). Confirm the org runner group's visibility includes element-x-ios — two `Default` groups exist, one `all` and one `selected`.
|
||||
|
||||
On the runner itself, confirm the toolchain is installed **by hand** — CI never provisions it. Measured today on `novaloop-mac-studio-001` (user `novaloop`, arm64, macOS 26.5.2):
|
||||
|
||||
- **Xcode:** one install, `/Applications/Xcode.app` (**26.6**, build `17F113`), globally selected via `xcode-select`; no versioned `Xcode_*.app` siblings. Nothing pins it — the build uses whatever `xcode-select` points at, so upgrading it silently changes the compiler every subsequent release is built with. Treat that upgrade as a release-affecting change.
|
||||
- **Present:** `xcodes`, `xcodegen`, `git-lfs`, `brew`, `swift`, rbenv `ruby`/`bundle`/`gem`.
|
||||
- **Missing:** `swiftlint`, `swiftformat`, `pkl`, `xcresultparser`, `xcbeautify`. The unit-test workflow asserts all five; this release lane requires none (its SwiftLint/SwiftFormat build phases are `if which …` guarded and degrade to a warning). The human installs them once: `brew install swiftlint swiftformat pkl xcbeautify a7ex/homebrew-formulae/xcresultparser` (`xcresultparser` is not in homebrew-core).
|
||||
- **No passwordless sudo.** Nothing in CI may call `sudo`.
|
||||
- Also confirm `APPLE_ID` is exported as a machine-level shell variable — the `Appfile` reads it and no secret carries it.
|
||||
On the runner itself, confirm: `xcodegen` on PATH (`brew install xcodegen`, deliberately not in `Brewfile`), Xcode **26.4** present and selectable via `xcodes`, Homebrew, Ruby + bundler, and `APPLE_ID` exported as a machine-level shell variable.
|
||||
|
||||
---
|
||||
|
||||
@@ -1083,36 +1084,20 @@ invisible to the other account.
|
||||
Runs on `novaloop-mac-studio-001` — an **org-level** runner (`[self-hosted,
|
||||
macOS, ARM64]`) shared with the legacy fork, not registered per-repo.
|
||||
|
||||
- **One Xcode, globally selected.** A single install at `/Applications/Xcode.app`
|
||||
(**26.6**, build `17F113`, today) — no versioned `Xcode_*.app` siblings.
|
||||
**Nothing pins it.** The Fastfile builds with whatever `xcode-select` points at, so
|
||||
**whatever the runner has is what ships**. Upgrading the runner's Xcode silently
|
||||
changes the compiler every subsequent release is built with — no tag, no diff, no
|
||||
review. Treat that upgrade as a release-affecting change.
|
||||
- **Do not reintroduce a version pin.** Pinning a version the runner lacks sends
|
||||
`xcodes` into a multi-GB authenticated download that no CI secret can satisfy, and
|
||||
the run hangs. Change the runner's Xcode instead.
|
||||
- **Toolchain installed once by hand**, never by CI: `xcodegen swiftlint swiftformat
|
||||
xcbeautify xcresultparser pkl` (`xcresultparser` is not in homebrew-core — only
|
||||
`a7ex/homebrew-formulae/xcresultparser` ships it). `git-lfs`, `xcodes`, `brew` and
|
||||
rbenv `ruby`/`bundle` are already present. There is deliberately **no `brew bundle`
|
||||
step and no `Brewfile`**: this machine is shared with CI, and a per-run
|
||||
`brew update` could upgrade `xcodegen` underneath a running release. CI asserts the
|
||||
tools are present and fails loudly naming the fix; it never provisions.
|
||||
- **No passwordless sudo.** Nothing in CI may call `sudo` — that is why upstream's
|
||||
`setup_github_actions_environment` cannot run here.
|
||||
- **Xcode 26.4**, selectable via `xcodes` (pinned in `fastlane/Fastfile:before_all`).
|
||||
**Bumping the pin requires installing the new Xcode on the runner first.**
|
||||
Otherwise `xcodes` starts a multi-GB authenticated download that no CI secret can
|
||||
satisfy, and the run hangs in `before_all`.
|
||||
- **xcodegen** on PATH — installed manually once via `brew install xcodegen`;
|
||||
deliberately **not** in `Brewfile`, so `brew bundle` will not churn it.
|
||||
- **Homebrew**, **Ruby** + **bundler**, **Git** on PATH.
|
||||
- **`APPLE_ID`** exported as a machine-level shell variable — the `Appfile` reads
|
||||
it and it is not carried in any secret.
|
||||
- Network access to `developer.apple.com`, `api.appstoreconnect.apple.com`,
|
||||
`github.com`, `rubygems.org`.
|
||||
|
||||
**A release's only Xcode provenance is its own log.** `release-healthchat.yml` runs
|
||||
`xcodebuild -version` (`Record toolchain`) before every build, so each run records the
|
||||
compiler that produced it. Nothing pins that version — upgrading the runner's Xcode
|
||||
silently changes the compiler every subsequent release is built with, and that log
|
||||
line is the only place it shows up. Do not reconstruct a past build's toolchain from
|
||||
the runner: it answers for today, not for the build you are asking about. Read the
|
||||
run's log.
|
||||
If `brew bundle` installs packages on every run, brew state has drifted from
|
||||
`Brewfile` — investigate rather than ignore.
|
||||
|
||||
## Renewing the Apple Distribution certificate (annual)
|
||||
|
||||
@@ -1203,19 +1188,13 @@ place, and these are what stop existing users being silently logged out.
|
||||
- **`hasSignedInBefore` voids migration testing.** It lives in that shared suite,
|
||||
survives app deletion, and short-circuits `loadAccounts()`. Any device that has
|
||||
run an element-x HealthChat build shows a plain login screen *while migration
|
||||
works perfectly*. Test on a device that has never run an element-x HealthChat
|
||||
build, or clear the `group.ch.healthchat` suite first;
|
||||
`"Already signed in to Element X before, skipping loadAccounts"` in the log
|
||||
means the run is **void, not failed**. A
|
||||
`"Classic App not configured, skipping loadAccounts"` line is the opposite —
|
||||
the `CLASSIC_APP_*` Info.plist keys are missing, `ClassicAppManager` never
|
||||
initialised, and migration is broken for every user. Investigate, do not
|
||||
dismiss.
|
||||
works perfectly*. Delete the app before testing; `"skipping loadAccounts"` in the
|
||||
log means the run is **void, not failed**.
|
||||
|
||||
## Known branch divergence on rebase
|
||||
|
||||
`fastlane/`, `.github/workflows/release-healthchat.yml`, `HEALTHCHAT_CHANGES.md`,
|
||||
`Gemfile` and this skill are **fork-only** — upstream element-x-ios has
|
||||
`Brewfile`, `Gemfile` and this skill are **fork-only** — upstream element-x-ios has
|
||||
no `fastlane/` directory at all. A rebase conflict in any of them means something
|
||||
upstream has started to overlap; keep `--ours` and investigate.
|
||||
|
||||
@@ -1237,9 +1216,9 @@ Run:
|
||||
```bash
|
||||
grep -n "MARKETING_VERSION" project.yml | head -1 # section "Version rules" cites :56
|
||||
grep -c "CLASSIC_APP" Variants/HealthChat/healthchat.yml # migration contract: must be 3
|
||||
grep -rn "xcodes(version:\|before_all" fastlane/Fastfile # runner section says nothing pins Xcode
|
||||
grep -n "xcodes(version:" fastlane/Fastfile # runner section cites the pin
|
||||
```
|
||||
Expected: `MARKETING_VERSION` at `project.yml:56`; three `CLASSIC_APP_*` entries in the variant; **no match** for the pin — a pin's return would contradict the runner section. A skill that misquotes the repo is worse than no skill.
|
||||
Expected: `MARKETING_VERSION` at `project.yml:56`; three `CLASSIC_APP_*` entries in the variant; the Fastfile pin matches the `26.4` the skill documents. A skill that misquotes the repo is worse than no skill.
|
||||
|
||||
- [ ] **Step 4: Verify the skill is discoverable**
|
||||
|
||||
|
||||
@@ -168,11 +168,12 @@ element-ios. Here that class of conflict does not exist — which is why this is
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `fastlane/Fastfile` | Public `release_testflight` + private helpers. **No `before_all`, no Xcode pin** — see runner notes. |
|
||||
| `fastlane/Fastfile` | `before_all` (Xcode pin) + public `release_testflight` + private helpers |
|
||||
| `fastlane/Appfile` | `apple_id ENV["APPLE_ID"]`, `team_id ENV["TEAM_ID"]` |
|
||||
| `fastlane/.env.default` | Project/target/bundle-id/profile configuration (full key list below) |
|
||||
| `fastlane/Pluginfile` | **`fastlane-plugin-xcodegen` only** |
|
||||
| `Gemfile` + `Gemfile.lock` | `fastlane` + plugins. No cocoapods, no slather. **Commit the lock** — the workflow's gem cache keys on `hashFiles('**/Gemfile.lock')`; without it the key is constant and fastlane silently re-resolves between two runs of the same tag. |
|
||||
| `Brewfile` | `xcodesorg/made/xcodes`. **Not** `xcodegen` — see runner notes. |
|
||||
| `HEALTHCHAT_CHANGES.md` | **New file** (D8). Seed with a `## Changes in 26.07.3 (<date>)` section matching the awk pattern. |
|
||||
| `.gitignore` | Add `out/` and **`provisioning_profiles/`** (signing material — security-relevant). `build` (`:35`) and `fastlane/report.xml` (`:28`) are already covered upstream. |
|
||||
|
||||
@@ -261,8 +262,7 @@ Changes from the legacy:
|
||||
- Drop the CocoaPods cache and `rm -rf Pods/MatrixSDK`.
|
||||
- Drop `MX_GIT_BRANCH` (legacy `:41`) — no `mx_git_branch` helper exists in the slim Fastfile.
|
||||
- Re-evaluate `fetch-depth: 0` (legacy `:48`); keep only if something needs history.
|
||||
- **Drop the `brew bundle` step** (legacy `:115`) — there is no `Brewfile`, and the runner is provisioned by
|
||||
hand. See runner notes.
|
||||
- Keep the explicit `brew bundle` step (legacy `:115`).
|
||||
- `bundle exec fastlane release_testflight`; artifacts `out/ElementX.ipa`, `out/ElementX.app.dSYM.zip`.
|
||||
- Harden the notes step with `[[ -f HEALTHCHAT_CHANGES.md ]]`. The legacy's "warning fallback" only fires on
|
||||
an *empty match*, not an absent file — under `set -euo pipefail` a missing file aborts the job.
|
||||
@@ -278,38 +278,16 @@ the real requirement.**
|
||||
`runs-on: [self-hosted, macOS]`. Confirm the org runner group's visibility includes element-x-ios (two
|
||||
`Default` groups exist, one `all`, one `selected`).
|
||||
|
||||
**No Xcode pin.** Measured on the runner today (user `novaloop`, arm64, macOS 26.5.2): exactly **one** Xcode,
|
||||
`/Applications/Xcode.app` — **26.6, build `17F113`**, `xcode-select`ed and the only version `xcodes` lists. No
|
||||
versioned `Xcode_*.app` siblings exist.
|
||||
**Xcode pin: use `"26.4"`.** The runner has it and the legacy pipeline proves it works.
|
||||
`ci_scripts/ci_common.sh:34` (`sudo xcode-select -s /Applications/Xcode_26.5.0.app`) is an **Xcode Cloud image
|
||||
path, not an `xcodes` version identifier**, and is not evidence the runner has 26.5. If element-x turns out to
|
||||
need 26.5, install it on the runner manually **first** — otherwise `xcodes` attempts a multi-GB authenticated
|
||||
download needing `XCODES_USERNAME`/`XCODES_PASSWORD`, which none of the five secrets supply, and hangs in
|
||||
`before_all`.
|
||||
|
||||
An earlier draft said to pin `"26.4"` because "the runner has it and the legacy pipeline proves it works".
|
||||
**Both halves were wrong.** The runner does not have 26.4 and never will; a pin naming an absent version does
|
||||
not fail fast — `xcodes` starts a multi-GB authenticated download needing `XCODES_USERNAME`/`XCODES_PASSWORD`,
|
||||
which none of the five secrets supply, so `before_all` hangs and takes the release with it. Since the runner
|
||||
carries a single globally-selected Xcode, whatever `xcode-select` points at *is* the build toolchain, and a pin
|
||||
could only ever disagree with reality. **Drop it entirely**; the Xcode version becomes a property of the
|
||||
machine, changed by provisioning it rather than by editing a tracked file. The reasoning about
|
||||
`ci_scripts/ci_common.sh:34` (`sudo xcode-select -s /Applications/Xcode_26.5.0.app`) still stands — it is an
|
||||
Xcode Cloud image path, not an `xcodes` version identifier, and says nothing about this runner.
|
||||
|
||||
The cost of dropping it: **whatever the runner has is what ships**, and no release run records which Xcode
|
||||
built it (`release-healthchat.yml` does not log `xcodebuild -version`; `unit-tests.yml` does). Upgrading the
|
||||
runner's Xcode is therefore a release-affecting change with no tag, diff or review. Accepted — the alternative
|
||||
pins a version that does not exist. Captured in the skill.
|
||||
|
||||
**The runner is provisioned by hand, never by CI.** Upstream's `setup_github_actions_environment`
|
||||
(`ci_scripts/ci_common.sh`) cannot run here: it calls `sudo xcode-select -s /Applications/Xcode_26.5.0.app` —
|
||||
there is **no passwordless sudo** and no such path — and `brew update && brew install …`, which on this
|
||||
*shared release machine* could upgrade `xcodegen` underneath a running release. CI must **verify and fail
|
||||
loudly**, naming the fix, instead.
|
||||
|
||||
Present today: `xcodes`, `xcodegen`, `git-lfs`, `brew`, `swift`, rbenv `ruby`/`bundle`/`gem`, and `APPLE_ID`
|
||||
exported as a machine-level shell variable (the `Appfile` reads it; it is not repo-carried).
|
||||
**Missing today** — the human installs these once, before the test workflow can pass: `swiftlint`,
|
||||
`swiftformat`, `pkl`, `xcresultparser`, `xcbeautify`. Only `xcresultparser` is outside homebrew-core
|
||||
(`a7ex/homebrew-formulae/xcresultparser`). The unit-test job asserts all of them. The release lane requires
|
||||
none: its SwiftLint/SwiftFormat build phases are `if which …` guarded and degrade to a build warning when the
|
||||
tool is absent (`ElementX/SupportingFiles/target.yml:202-214`), and nothing in the build reads `pkl`.
|
||||
Also required on the runner: `xcodegen` on PATH (installed manually once, deliberately **not** in `Brewfile`
|
||||
so `brew bundle` cannot churn it), Homebrew, Ruby + bundler, and `APPLE_ID` exported as a machine-level shell
|
||||
variable (the `Appfile` reads it; it is not repo-carried).
|
||||
|
||||
### Secrets to copy
|
||||
|
||||
@@ -365,7 +343,7 @@ per its own "keep current" rule this is a new fact about the repo, so it belongs
|
||||
| PR | Contents | Label |
|
||||
|---|---|---|
|
||||
| PR1 | Part 1 — `CLASSIC_APP_*`, entitlement, `.requiresBackup` edit, regenerated pbxproj | `pr-bugfix` |
|
||||
| PR2 | Part 2 — fastlane, workflow, Gemfile(.lock), .gitignore, HEALTHCHAT_CHANGES.md | `pr-build` |
|
||||
| PR2 | Part 2 — fastlane, workflow, Gemfile(.lock), Brewfile, .gitignore, HEALTHCHAT_CHANGES.md | `pr-build` |
|
||||
| PR3 | Part 3 — skill + AGENTS.md delegation | `pr-doc` |
|
||||
|
||||
AGENTS.md's 500-addition guidance argues for keeping these separate rather than combining PR2 and PR3.
|
||||
@@ -446,9 +424,6 @@ upload; review proves nothing extra about the pipeline.
|
||||
| **O4** | **Runner group visibility** — confirm `novaloop-mac-studio-001` is reachable from element-x-ios. |
|
||||
| **O5** | `Secrets/Secrets.swift` holds placeholders; `Secrets/Secrets.pkl:12-17` wants MAPLIBRE_API_KEY among others. Maps degrade without it. Not a release blocker — track separately. |
|
||||
| **O6** | Cosmetic Element strings ship in a HealthChat binary, e.g. `BGTaskSchedulerPermittedIdentifiers: io.element.elementx.background.refresh` (`ElementX/SupportingFiles/target.yml:102-104`). Working; cosmetic only. |
|
||||
| **O7** | **The legacy element-ios pipeline is probably broken right now — unverified, and worth verifying.** It pins `xcodes(version: "26.4")` and runs on this same runner, which carries only **26.6**. Its last green run was 2026-04-15, so the machine plausibly had 26.4 then and lost it to an upgrade since. If so, the next legacy release does not fail — it **hangs** in `before_all` on an authenticated multi-GB download, and only a timeout ends it. **This fork no longer depends on that pipeline** (D3's "the legacy pipeline is proven" means *was* proven, on a toolchain the machine no longer has), so this is not a blocker here. Confirm the pin against `Novaloop-AG/element-ios` and de-pin it there before its next release. Out of scope for these three PRs — raise it. |
|
||||
| **O8** | **Whatever Xcode the runner has is what ships.** The pin is gone (see Runner), so upgrading `/Applications/Xcode.app` silently changes the compiler for every subsequent release, with no tag, diff or review. `release-healthchat.yml` records it via the `Record toolchain` step (`xcodebuild -version`) before every build, so each run logs the compiler that produced it — but that log line is the only record, and it detects the change after the fact rather than preventing it. Treat a runner Xcode upgrade as a release-affecting change. |
|
||||
| **O9** | **A pull request can execute arbitrary code on the release machine.** `unit-tests.yml` runs on `[self-hosted, macOS]` under a `pull_request` trigger (`.github/workflows/unit-tests.yml:9,20`), so any PR's build scripts run on `novaloop-mac-studio-001` — the same persistent runner, keychain and signing identities the release pipeline uses. **Accepted because the repo is INTERNAL**, which narrows PR authors to org members. The trade-off is invisible to CI: zizmor reports it (`self-hosted-runner`, medium) **only at `--persona=auditor`**, and the default persona CI runs suppresses it — so no required check will ever warn. If org membership stops being a sufficient trust boundary: restrict the trigger to non-fork branches, or move back to GitHub-hosted `macos-26` with a raised `timeout-minutes`. |
|
||||
|
||||
**Closed:** the app-group collision question — container paths are disjoint, and the shared
|
||||
`group.ch.healthchat` UserDefaults suite has a verified-empty key intersection. Captured as a standing hazard
|
||||
|
||||
Reference in New Issue
Block a user