# How the app takes a release A platform release reaches the app as a reviewed vendor sync: the contract at the tag, one version number, an App Store archive from the tag, a ledger entry, and the completeness check. - Page: - Updated: 2026-09-27 - Verified against: `417c92f` on 2026-09-25 - From the Sri Guru Granth Sahib Ji — Knowledge Base wiki. Sri Guru Granth Sahib Ji is quoted verbatim, cited by Ang, and never altered. --- A platform release ends with a tag `vX.Y.Z` on `main` ([the delivery pipeline](https://docs.gurbanisoul.com/process/branching/)). It is **complete** only when the App Store binary reports the same number from the same platform commit. Between the two sits a small, fully mechanical flow in the app repository. ```mermaid sequenceDiagram accTitle: A platform release reaches the App Store as a vendor sync, a tag and one archive accDescr: At a platform tag, a pull request in gurbani-soul-ios vendors the contract and sets MARKETING_VERSION; its CI checks the vendor lock, the dataset pin, the versions, the gates, Swift parity and the app build. After the squash merge the app is tagged; make testflight, from a worktree at that tag, stages the database, runs the licence gate, archives, uploads and proves the build, and records it in the ledger, which check_release_complete.py reads. participant P as platform vX.Y.Z participant A as app PR participant CI as app CI participant M as make testflight participant L as the ledger P-->>A: vendor-sync at the tag A->>A: MARKETING_VERSION = X.Y.Z A->>CI: pull request CI->>CI: pins, versions, parity, build CI-->>A: green, merged, tagged A->>M: from the tag, CHANNEL=appstore M->>M: stage, gate, archive, upload, prove M->>L: record the build L-->>P: check_release_complete.py ``` ## 1. Vendor the release `make vendor-sync-platform REF=vX.Y.Z` re-vendors the platform's golden contract (`contract/_meta.json`, every `contract/golden_*.ndjson`) and the contributors roster at the tag, **at the same paths they have upstream**, and records in `vendor.lock.json` the source commit, the sha256 of every file and the platform's `APP_VERSION` at that ref. Vendored files are never edited in the app repository; CI rejects a local edit. ```python #!/usr/bin/env python3 """Vendored files from the platform and data repositories, pinned by commit and sha256. The app is built and tested against inputs other repositories own: the golden contract, the contributors roster and the brand tokens (platform) and the iOS database builder (sggs-data). They are committed here at the same paths they have upstream, so the app, its tests and its tooling read them unchanged — and vendor.lock.json records where each came from and its sha256. python3 scripts/vendor_sync.py check # every vendored file == its lock hash (CI) python3 scripts/vendor_sync.py check --remote # ... and == the file at source@commit python3 scripts/vendor_sync.py sync platform # re-vendor from a platform ref python3 scripts/vendor_sync.py sync data # re-vendor from dataset.lock.json's commit `sync platform` also records the platform's APP_VERSION at that ref: the one-version policy (scripts/release/check_versions.py) requires this app's MARKETING_VERSION to equal it, so a binary always ships with the contract of the release it carries the number of. Stdlib only. GH_TOKEN / GITHUB_TOKEN is sent when set (needed once the sources are private). """ from __future__ import annotations ``` *`gurbani-soul-ios/scripts/vendor_sync.py`, lines 1–20, read at `a386422`: * Source: [`scripts/vendor_sync.py`](https://github.com/Algorythmos-AI/gurbani-soul-ios/blob/main/scripts/vendor_sync.py) in gurbani-soul-ios. The dataset is a separate pin: `dataset.lock.json` there names the same sggs-data commit as this platform's, and `make vendor-sync-data` re-vendors the iOS database builder from that commit. ## 2. One number, enforced `MARKETING_VERSION` in `ios/App/project.yml` must equal the vendored platform version (`scripts/release/check_versions.py` there; [ADR-0004](https://docs.gurbanisoul.com/adr/0004-unified-semver/) here). `CURRENT_PROJECT_VERSION` stays at the floor `1`; every upload passes `BUILD=N` explicitly and the ledger keeps N monotonic within a marketing version, so a 20-minute archive never discovers Apple's rejection of a reused build number. ## 3. The pull request and the app's gates | Job | Proves | |---|---| | `ci · gates` | every vendored file equals its lock entry **and** its source at the pinned commit (`vendor_sync.py check --remote`); sggs-data publishes the pinned database; the version invariants; the iOS source, listing and archive gates (`ios/tests`) | | `iOS fidelity gate · parity` | both database profiles build; the Swift kit's parity suites pass against the vendored contract on the derived database | | `iOS fidelity gate · app` | the app builds and its unit and UI tests pass on a simulator | | `security · secrets` | no secret in the tree | PRs are squash-merged; the app then tags its own `vX.Y.Z`. ## 4. The archive, from the tag `make testflight TEAM_ID=… BUILD=N CHANNEL=appstore UPLOAD=1` is the **only** sanctioned way to build a candidate, run from a clean detached worktree at the tag. It refuses a dirty tree, waits for green CI on the commit, checks the version invariants and the ledger, then stages the database, runs the licence gate, generates a temporary Xcode project, archives, uploads, and proves what was built — the whole chain is [poster 12](https://docs.gurbanisoul.com/ios/db-pair-and-launch-integrity/). `CHANNEL=appstore` is declared up front: it makes the Nitnem scholar-review attestation a hard requirement, and only a build recorded with that channel may be submitted (`make appstore-preflight`). ## 5. The ledger `ios/testflight-builds.json` is the tracked, append-only source of record for every upload. Each row carries the version and build, the app's `source_commit`, the `platform_commit` whose contract the binary vendors, the `dataset_commit`, the database profile, the channel, the bundled `db_sha256`, the Xcode and SDK versions and the upload time. It is committed with the release. ```python #!/usr/bin/env python3 """ testflight_ledger.py — the source of record for every build uploaded to App Store Connect, and the gate that keeps a build number monotonic *before* a 20-minute archive+upload discovers Apple's rejection. The ledger is ios/testflight-builds.json (tracked). Apple enforces two rules that this tool mirrors: - a CFBundleVersion (build number) may not be reused within a marketing version, and - a marketing version already on TestFlight may not be undercut by a lower one. Commands check [--strict] Print `next: N` for the version. With --strict, exit 1 when is not greater than every build already recorded for , or when is lower than the highest version in the ledger. Without --strict a violation is a warning and the exit code stays 0 (so a local re-archive with UPLOAD=0 is never blocked). next Print only the next build number for (1 if the version is new). record [--force] Append a row from the archive script's candidate--.json. Refuses a duplicate (version, build). Refuses a candidate whose "uploaded" is false unless --force (the Transporter path, where the upload happens later). Exit codes: 0 ok · 1 gate/refusal · 2 usage/IO error. """ import argparse, json, sys ``` *`gurbani-soul-ios/ios/tools/testflight_ledger.py`, lines 1–28, read at `a386422`: * Source: [`ios/tools/testflight_ledger.py`](https://github.com/Algorythmos-AI/gurbani-soul-ios/blob/main/ios/tools/testflight_ledger.py) in gurbani-soul-ios. ## 6. Complete ```python #!/usr/bin/env python3 """check_release_complete.py — prove a release is COMPLETE across all three surfaces. The one-number policy (docs/engineering/delivery.md "Versioning convention") says web, API and the shipped iOS binary must all report the same X.Y.Z from the same released commit. This is the release-time, network-touching check that proves it — run as the final step of sggs-release, and by sggs-verify-prod --ios. For version X.Y.Z it asserts: 1. prod API /api/health.version == X.Y.Z and .commit == the commit tag vX.Y.Z points at 2. prod WEB /api/health.version == X.Y.Z and .commit == that same commit 3. the app's ledger (ios/testflight-builds.json in Algorythmos-AI/gurbani-soul-ios) has a channel="appstore" build of X.Y.Z built against that same commit: its platform_commit (the platform release whose contract the binary vendors) — or, for builds made before the app had its own repository, its source_commit — equals it (full 40-hex). Exit 0 if complete, 1 otherwise. Usage: check_release_complete.py X.Y.Z [--api URL] [--web URL] [--ledger PATH] """ import argparse, json, os, subprocess, sys, urllib.error, urllib.request ``` *`scripts/release/check_release_complete.py`, lines 1–20, read at `c3d9360`: * Source: [`scripts/release/check_release_complete.py`](https://github.com/Algorythmos-AI/sggs-platform/blob/integration/scripts/release/check_release_complete.py) in this repository. The check reads the ledger from the app repository's `main` and asserts that production's web and API report `X.Y.Z` from the commit the platform tag points at, and that an `appstore` build of `X.Y.Z` was built against that same commit. It is the last step of the release skill and of `make verify-prod --ios`. ## Hotfixes A one-sided fix is never shipped: a web or iOS fix bumps the **patch** everywhere, and the app re-archives at the new tag. The App Store has no binary rollback, so the app repository's hotfix runbook expires the bad TestFlight build, pauses a phased release and ships forward.