Runs on: workflow_call. 1 jobs, 3 named steps.
Reusable. Decides which apps and Swift packages a run has to build, test and
upload, from apps.json and the diff (contracts section 4 and 5). Ubuntu only,
so it costs no macOS runner. Called by ios.yml and ios-release.yml.
Before the split lands there is no apps.json: scripts/release/apps.py then
describes today's single app (the root project). An app whose project does not
exist yet is skipped, with a line in the run summary.
- changes on
ubuntu-latest- Use another branch's tree (source_ref)
- Rules self-test
- Plan
Runs on: push, workflow_dispatch. 3 jobs, 23 named steps.
Packaging and distribution for the apps in apps.json (contracts section 5).
changes ubuntu: which apps this run touches, from apps.json and the diff
(ios-changes.yml). An app whose project does not exist yet is skipped.
package per affected app: pushes to dev (not docs/site-only), release tags,
and by hand. Unsigned: .xcarchive, .ipa (Payload/<App>.app zipped) and
a simulator .app, uploaded as artifacts. Needs no Apple account.
testflight per affected app with "testflight": true whose unsigned package
succeeded: signs, exports and uploads to THAT app's App Store Connect
record. The built bundle id must equal apps.json, and the record must
exist for it, or nothing is uploaded.
Triggers
push to dev NOT here since 10 Nov 2026: ios.yml's one macOS job builds, tests and, when
green, uploads the affected apps from the release commit website.yml tags
(briefs/2026-11-10__ci-on-one-mac). Pausing works the same way there.
tags team-v*, promptugal-v*, riskmandate-v*, fringe-v* release that app;
the old ios-v* means the app with bundle news.promptugal.reader
workflow_dispatch input "app": all, or one app; "upload" or "dry_run" to sign
Pausing (repository variables, Settings > Secrets and variables > Actions > Variables)
TESTFLIGHT_ON_PUSH=false pauses push uploads of every app
TESTFLIGHT_ON_PUSH_TEAM=false pauses one app (…_PROMPTUGAL, …_RISKMANDATE, …_FRINGE)
Tags and manual runs still upload. The same switches pause ios.yml's uploads.
Version: MARKETING_VERSION of every app = VERSION at the built commit (v0.4.7 -> 0.4.7), passed to
xcodebuild. Summary and TestFlight notes say "0.4.7 (build) · tag v0.4.7 · commit <sha>".
Build number: CFBundleVersion = run number of THIS workflow + the app's offset (ios.yml uses its own
run number, which is higher; to upload a version ios.yml already uploaded, set build_number), passed as
CURRENT_PROJECT_VERSION. Offset: variable IOS_BUILD_OFFSET_<APP>, else IOS_BUILD_OFFSET,
else 100 (above the manual builds; PrompTugal's last manual build is 6). Both jobs of one
app use the same number. MARKETING_VERSION comes from the app's own project.
Secrets: ASC_* and the certificate are shared. Profiles are per app:
IOS_PROFILE_<APP>_{APP,WIDGETS,INTENTS,SHARE}_BASE64, for example IOS_PROFILE_TEAM_APP_BASE64.
The app with bundle news.promptugal.reader also accepts the old IOS_PROFILE_{APP,WIDGETS,INTENTS}_BASE64.
Setup: briefs/2026-11-08__overnight-push/release-setup.md
- changes
- package on
macos-26- Use another branch's project (source_ref)
- Select Xcode
- Version and build number
- Archive and package (unsigned)
- Bundle id matches apps.json
- Size check
- Release notes
- Upload unsigned .ipa
- Upload simulator app
- Upload .xcarchive
- Upload build-info.json
- Upload logs
- testflight on
macos-26- Check signing secrets
- Use another branch's project (source_ref)
- Select Xcode
- Version and build number
- Install certificate, profiles and API key
- App Store Connect record exists?
- Archive, sign, export and upload
- Wait for processing, answer export compliance, set What to Test
- Upload signed .ipa
- Upload logs
- Remove keychain, profiles and key
Runs on: workflow_dispatch. 1 jobs, 4 named steps.
Submits a build that is already in App Store Connect (uploaded by
"iOS release") for App Store review. By hand only. Metadata, screenshots,
pricing and the privacy questions stay manual in App Store Connect.
Without "confirm" it only prints the plan.
- submit on
ubuntu-latest- Check secrets
- Wait for the build to be processed
- Export compliance
- Submit
Runs on: push, workflow_dispatch. 3 jobs, 2 named steps.
Swift writes a vault, sgit reads it back. Owned by the vault session.
ios.yml already runs `swift test` for Packages/SGitVault and builds the app, so this
workflow runs only when the write path changes (or by hand), to save macOS runners:
swift-writes macOS: the write tests export the vaults Swift wrote (SGITVAULT_ROUNDTRIP_OUT)
sgit-reads-swift Linux: sgit-ai 0.27 clones each, switches to the Swift branch,
verifies every signature and compares every file
reference-reader Linux: the fixtures, read by the Python reference reader
- swift-writes on
macos-15- Write tests, exporting what Swift wrote
- sgit-reads-swift on
ubuntu-latest- sgit clones, switches branch, verifies signatures, compares files
- reference-reader on
ubuntu-latest
Runs on: push, pull_request, workflow_dispatch. 7 jobs, 14 named steps.
CI is the compiler: nobody in the overnight push can build Swift on Linux.
Every job copies compiler and test errors into its job summary, so a session
can read a failure through the GitHub tools (actions_list, get_job_logs).
Only what a change touches is built (contracts section 5). The `changes` job
(ubuntu, ios-changes.yml) reads apps.json and the diff, then ONE macOS job does
all of it (briefs/2026-11-10__ci-on-one-mac):
mac per affected app: build once, unit tests on an iPhone and an iPad simulator
(dev, pull requests and workflow_dispatch; night/** pushes build only), the
Release build for a device (unsigned); then each affected package's tests
quick manual runs only: Debug build and iPhone tests, no Release build, no iPad
result one stable check name that is green when everything above is
An app whose project does not exist yet is skipped, with a line in the summary.
Before apps.json exists, the built-in config builds today's single app.
Pushes that change only briefs/, website/, root Markdown or VERSION don't run
it: macOS runners are shared by every session, and those files can't break
the app. (A .md file inside PrompTugal/ can, so those still run.)
- pick-runner on
ubuntu-latest
- gate on
ubuntu-latest
- changes
- check-apps on
ubuntu-latest- check_apps.py
- mac on
‹needs.pick-runner.outputs.runner›- Select Xcode
- Build the apps and packages
- Tests on the iPhone simulator
- Tests on the iPad simulator
- Find this push's release commit
- Release build (no release commit, so no upload)
- Sign, export and upload to TestFlight
- Remove keychain, profiles and key
- Upload build-info and release notes
- Upload signed .ipa files
- Delete the simulators (self-hosted only)
- Upload logs and test results (failures only)
- testflight-processing on
ubuntu-latest- Wait for processing, answer export compliance, set What to Test
- result on
ubuntu-latest
Runs on: workflow_dispatch. 5 jobs, 10 named steps.
Screenshots of every main screen on an iPhone and an iPad simulator, so the
team can see the app without a Mac. By hand only (workflow_dispatch): macOS runners
are shared, and branch CI (ios.yml) stays at a few jobs.
Per app: only the apps a change touches, and only apps whose project has a shared
"<Scheme>Screens" scheme (optional apps.json keys screensScheme, screensCount).
Artifact: screens-<sha>, files <iphone|ipad>-<destination id>.png, longest side
1600 px. The app with bundle news.promptugal.reader keeps that name (the editor
publishes it on the site from successful dev runs); the others get screens-<sha>-<app>.
- changes
- shoot on
macos-26- Select Xcode
- Pick and boot an ‹matrix.device› simulator
- Take the screenshots
- Export PNGs
- collect on
ubuntu-latest
- store on
macos-26- Select Xcode
- Create and boot the ‹matrix.size.kind› simulator
- Take the screenshots, once per locale
- Normalise, check sizes, convert to JPEG
- Delete the simulator
- store-collect on
ubuntu-latest- Lay out as <locale>/<display type>/<NN-id>.jpg
Runs on: workflow_dispatch. 1 jobs, 2 named steps.
Manual TestFlight / Beta App Review operations through the App Store Connect API
(scripts/testflight/tf.py). Every changing action only prints its plan unless
"confirm" is ticked. The API key comes from the repository secrets; nothing is printed.
status builds, processing, beta review state, groups, expiry
add-to-group build + group
remove-from-group build + group
set-test-info build; sets What to Test (scripts/testflight/what-to-test.txt)
and the Beta App Review details (scripts/testflight/review-info.json
+ review-notes.txt; contact and demo keys from secrets)
submit-beta-review build
expire build
- ops on
ubuntu-latest- Unit tests (no network)
- Run action
Runs on: workflow_dispatch. 1 jobs, 3 named steps.
The RiskMandate business card as an Apple Wallet pass (scripts/wallet/wallet_pass.py), at Dinis's word
(9 November 2026). Run by hand. Without "confirm" it only checks the Pass Type ID. With it, it creates the ID if
needed and a new pass certificate through the App Store Connect API, signs the pass, and uploads card.pkpass as an
artifact, and commits it to briefs/2026-11-09__wallet-pass/card.pkpass on dev ([skip ci]; a signed pass holds no
secret). The certificate's private key exists only on the runner and is deleted; nothing secret is printed.
- pass on
ubuntu-latest- Make the pass
- Upload the pass
- Commit the signed pass (no secret in it)
Runs on: push, pull_request, workflow_dispatch. 3 jobs, 11 named steps.
On every push to dev:
1. release bump /VERSION (v0.1.n -> v0.1.n+1), commit it, tag it, push both to dev
2. build test, build and validate the site from that release commit
3. deploy publish to GitHub Pages (ios.app.promptugal.news) and confirm the
live site serves the release; the deploy is not done until it does
On pull requests, only the build job runs, with no release and no deploy.
The release commit is pushed with GITHUB_TOKEN, which does not start another
workflow run, so there is no loop.
- release on
ubuntu-latest- Bump the release version
- Commit, tag and push
- build on
ubuntu-latest- Test the builder and the validator
- Latest iOS build
- Latest app screenshots
- Build
- Validate
- Show the version stamp
- Upload the Pages artifact
- deploy on
ubuntu-latest- Deploy to GitHub Pages
- Verify live