Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
eef0d37
Client(iOS) - Move the demo's step derivations out of the views
alex-arguello Aug 11, 2026
676f412
Client(iOS) - Order each step behind the one before it
alex-arguello Aug 11, 2026
171940a
Disable the swiftformat rules that rewrite code, then format the tree
alex-arguello Aug 11, 2026
12ee549
Lint and analyse in CI, and run the demo's step sequences there
alex-arguello Aug 11, 2026
3aee13c
Client(iOS) - Let the activation step report the failure the session …
alex-arguello Aug 11, 2026
9bfeee6
Stop Sonar indexing the demo's tests as both source and test
alex-arguello Aug 11, 2026
0558f46
Keep the customer and the processor response out of the public log
alex-arguello Aug 11, 2026
c94ec79
Stop publishing the card reader's credentials to the log
alex-arguello Aug 11, 2026
91c8d68
Client(iOS) - Offer the activation code only where the SDK accepts it
alex-arguello Aug 11, 2026
f461585
Say what the code does, without arguing with the alternative
alex-arguello Aug 11, 2026
f65dad5
Client(iOS) - Derive the one control the screen offers, and let the v…
alex-arguello Aug 11, 2026
9b8ac65
Log the shape of the attestation and update calls, never their contents
alex-arguello Aug 11, 2026
bcd36e5
Client(iOS) - Let the enable step report the failure that follows an …
alex-arguello Aug 11, 2026
ef45b1d
Use the bound this suite already declared
alex-arguello Aug 11, 2026
ffbbed7
Client(iOS) - Let the enable step report a revoked attestation
alex-arguello Aug 11, 2026
e57a2c7
Measure the SDK in Sonar, not the sample app
alex-arguello Aug 11, 2026
e63324c
Keep the public initializers' documentation as documentation
alex-arguello Aug 11, 2026
ec0f30e
Cover the text-to-state boundary the screens depend on
alex-arguello Aug 11, 2026
aba6a86
Pin the lint tools CI gates on
alex-arguello Aug 11, 2026
4beee8b
Report coverage for the code Sonar measures, and nothing else
alex-arguello Aug 11, 2026
bd856d3
Verify the lint tools by content before running them
alex-arguello Aug 11, 2026
5d61160
Client(iOS) - Probe the card-not-present token endpoint from Configur…
alex-arguello Aug 11, 2026
4553419
Fail the coverage conversion on parsed lines, not on file count
alex-arguello Aug 11, 2026
7743961
Client(iOS) - Offer another activation code after one is refused
alex-arguello Aug 11, 2026
eb72bb5
Pin the analysis action to a commit
alex-arguello Aug 11, 2026
a6e2e12
Client(iOS) - Offer the full setup when the session errored
alex-arguello Aug 11, 2026
cde192f
Fail the conversion when an archive cannot be read
alex-arguello Aug 11, 2026
344b851
Read the coverage report in one xccov call
alex-arguello Aug 11, 2026
10cd8d8
Run lint and the two suites as parallel jobs
alex-arguello Aug 11, 2026
1ba2edb
Drop xcpretty from CI and read failures from the result bundle
alex-arguello Aug 11, 2026
a751408
Say which failure recovery is recovering from
alex-arguello Aug 11, 2026
c356886
Report what a change touched, and what is only formatting
alex-arguello Aug 11, 2026
fce6148
Run the full setup after any errored session, including a failed acti…
alex-arguello Aug 11, 2026
6b8cb0e
Post the change report to the pull request
alex-arguello Aug 11, 2026
3fa8cf2
Report what the change means, not what it measures
alex-arguello Aug 11, 2026
12e2f3b
Show a failed submit's reason on the step that failed
alex-arguello Aug 11, 2026
01ed4f8
Keep the write token out of the job that runs the branch's code
alex-arguello Aug 11, 2026
55d3b03
Report the API of files that entered or left the build
alex-arguello Aug 11, 2026
4793a72
Report which customer fields a charge carried, never their values
alex-arguello Aug 12, 2026
b24dbfe
Report the initiate request by size, not by its contents
alex-arguello Aug 12, 2026
6225029
Count an added or deleted production file as a behaviour change
alex-arguello Aug 12, 2026
9bc7af2
Classify a production file that was renamed as well as edited
alex-arguello Aug 12, 2026
9243bf6
Read the public surface by its container, not by the word public
alex-arguello Aug 12, 2026
51ec431
Give the token probes one owner across the tabs
alex-arguello Aug 12, 2026
ab7e283
Give each token function its own probe, and let the latest run win
alex-arguello Aug 12, 2026
9b99568
Count a moved statement as a change, because order is behaviour
alex-arguello Aug 12, 2026
68d422b
Take the probe button's enabled state from the step, not a local flag
alex-arguello Aug 12, 2026
4ea808c
Register a held run in the same call that numbers it
alex-arguello Aug 12, 2026
d7a8f33
Let the change-report comment fail out loud
alex-arguello Aug 12, 2026
fc436ec
Post the change report where it will be read
alex-arguello Aug 12, 2026
9fdb83c
Run the two token-holding jobs from the default branch
alex-arguello Aug 12, 2026
565338a
Take the pull request from the event, not from the artifact
alex-arguello Aug 12, 2026
a9a3432
Pin the workflow token and stop checkout leaving it behind
alex-arguello Aug 12, 2026
de919d8
Keep a settled probe answer while the next run is in flight
alex-arguello Aug 12, 2026
c5b3703
Delete only the reports this workflow posted
alex-arguello Aug 12, 2026
8e8347e
Decide where the analysis goes in the workflow, not in the branch
alex-arguello Aug 12, 2026
295e297
Run one report and one analysis per pull request at a time
alex-arguello Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/actions/ios-toolchain/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: iOS toolchain
description: Selects Xcode and a simulator, and resolves the package graph.

# The test jobs run in parallel and each needs the same four steps. A composite
# action keeps the Xcode selection and the simulator lookup written once, so the
# jobs cannot drift onto different runtimes and disagree about a failure.

runs:
using: composite
steps:
- name: Select latest Xcode
shell: bash
run: |
XCODE=$(ls /Applications | grep -E '^Xcode_[0-9]' | sort -V | tail -1)
sudo xcode-select -s "/Applications/$XCODE/Contents/Developer"
echo "Using $XCODE"
xcodebuild -version
swift --version

- name: Resolve dependencies
shell: bash
run: |
xcodebuild -resolvePackageDependencies \
-scheme PayabliSDK-Package \
-clonedSourcePackagesDirPath .build/checkouts

- name: Select simulator
shell: bash
run: |
SIMULATOR_ID=$(xcrun simctl list devices available -j \
| python3 -c "
import json, sys
d = json.load(sys.stdin)['devices']
iphones = [
v for k, vals in d.items()
if 'iOS' in k
for v in vals
if 'iPhone' in v['name'] and v['isAvailable']
]
if not iphones:
print('error: no available iPhone simulator found', file=sys.stderr)
sys.exit(1)
print(sorted(iphones, key=lambda x: x['name'])[-1]['udid'])
")
echo "Using simulator: $SIMULATOR_ID"
echo "SIMULATOR_ID=$SIMULATOR_ID" >> "$GITHUB_ENV"
42 changes: 42 additions & 0 deletions .github/actions/lint-tools/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Lint tools
description: Installs swiftformat and swiftlint, verified by checksum.

# Pinned by content, not by name. Both tools decide whether the lint job passes,
# and .swiftformat names the version its four disabled rules were checked
# against, so a change in either would alter the tree or redden every pull
# request without a commit here.
#
# A release tag is not immutable: a publisher can delete an asset and upload
# another under the same URL. The checksums below are what makes the bytes
# fixed, and they are verified before anything is unpacked or run. Raising a
# version means replacing its checksum in the same change, with `swiftformat .`
# re-run alongside it.

runs:
using: composite
steps:
- name: Install lint tools
shell: bash
env:
SWIFTFORMAT_VERSION: 0.62.1
SWIFTFORMAT_SHA256: 7cb1cb1fae04932047c7015441c543848e8e60e1572d808d080e0a1f1661114a
SWIFTLINT_VERSION: 0.65.0
SWIFTLINT_SHA256: d6cb0aa7a2f5f1ef306fc9e37bcb54dc9a26facc8f7784ac0c3dd3eccf5c6ba6
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/tools"

curl -sSfL -o "$RUNNER_TEMP/swiftformat.zip" \
"https://github.com/nicklockwood/SwiftFormat/releases/download/${SWIFTFORMAT_VERSION}/swiftformat.zip"
echo "${SWIFTFORMAT_SHA256} ${RUNNER_TEMP}/swiftformat.zip" | shasum -a 256 -c -

curl -sSfL -o "$RUNNER_TEMP/swiftlint.zip" \
"https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/portable_swiftlint.zip"
echo "${SWIFTLINT_SHA256} ${RUNNER_TEMP}/swiftlint.zip" | shasum -a 256 -c -

unzip -q -j -o "$RUNNER_TEMP/swiftformat.zip" -d "$RUNNER_TEMP/tools"
unzip -q -j -o "$RUNNER_TEMP/swiftlint.zip" -d "$RUNNER_TEMP/tools"
chmod +x "$RUNNER_TEMP/tools/swiftformat" "$RUNNER_TEMP/tools/swiftlint"
echo "$RUNNER_TEMP/tools" >> "$GITHUB_PATH"
"$RUNNER_TEMP/tools/swiftformat" --version
"$RUNNER_TEMP/tools/swiftlint" version
187 changes: 154 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,174 @@ concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

# Every job here runs the pull request's own code, so the token it inherits is
# pinned rather than left to the repository's default. That default is read-only
# today and is a setting, not a property of this file; without this line, raising
# it would silently hand a write to code the branch controls, which `checkout`
# leaves in `.git/config` for whatever runs next.
permissions:
contents: read

# Four jobs, run at once. The two suites are the whole cost of this workflow and
# neither reads anything the other writes, so run in sequence they only add up.
# They cannot share a build: both compile the SDK, but one is the package's own
# test action and the other consumes the package as a dependency of
# Example/PayabliDemo.xcodeproj, and Xcode compiles a dependency with
# -suppress-warnings. Same intermediates path, different flags, so pointing both
# at one -derivedDataPath makes them invalidate each other's objects rather than
# reuse them.
#
# Nothing here is granted a secret or a write. Posting the change report and
# running the analysis both need one, and both live in `pr-reports.yml`, which
# this run triggers when it finishes. That file is read from the default branch,
# so a pull request cannot edit the jobs that hold the tokens; every job below
# is read from the pull request's own revision, which is why none of them may
# hold one.

jobs:
Comment thread
alex-arguello marked this conversation as resolved.
test:
name: Build & Test
lint:
name: Lint
runs-on: macos-15

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Nothing here pushes, so the token is not left in `.git/config` for
# the branch's own scripts to find.
persist-credentials: false

- uses: ./.github/actions/lint-tools

- name: Select latest Xcode
# No `--config`: naming a config file makes SwiftLint ignore nested ones,
# and Tests/.swiftlint.yml is what relaxes the rules XCTest fixtures break.
- name: Lint
run: |
XCODE=$(ls /Applications | grep -E '^Xcode_[0-9]' | sort -V | tail -1)
sudo xcode-select -s "/Applications/$XCODE/Contents/Developer"
echo "Using $XCODE"
xcodebuild -version
swift --version
swiftlint
swiftformat --lint .

# Reports, never judges. A branch that runs `swiftformat .` puts hundreds of
# files in the diff, and the file count alone does not say which of them
# changed what the code does.
#
# continue-on-error, and the script exits 0 whatever it finds: a report that
# can redden a pull request is a gate, and this is not one.
#
# This job runs the pull request's own code — the local action and the script
# under Scripts/ — so it is read-only and holds no token. Posting the report
# needs one, and that happens in `pr-reports.yml`, which runs from the default
# branch where this branch cannot edit it.
changes:
name: Change report
runs-on: macos-15
if: github.event_name == 'pull_request'
continue-on-error: true
permissions:
contents: read

steps:
# The classification reads both sides of the change, so it needs history.
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- name: Install xcpretty
run: gem install xcpretty --no-document
- uses: ./.github/actions/lint-tools

- name: Resolve dependencies
- name: Classify the diff
run: |
xcodebuild -resolvePackageDependencies \
-scheme PayabliSDK-Package \
-clonedSourcePackagesDirPath .build/checkouts
./Scripts/classify-changes.sh \
Comment thread
alex-arguello marked this conversation as resolved.
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}" \
> report.md
cat report.md >> "$GITHUB_STEP_SUMMARY"

# Only the report. Which pull request it belongs to is read from the
# triggering run's event by `pr-reports.yml`, because anything uploaded
# here is chosen by the branch, and that workflow holds the tokens.
- name: Upload the report
uses: actions/upload-artifact@v4
with:
name: change-report
path: report.md

test-sdk:
name: SDK tests
runs-on: macos-15

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Nothing here pushes, so the token is not left in `.git/config` for
# the branch's own scripts to find.
persist-credentials: false

- uses: ./.github/actions/ios-toolchain

- name: Test
- name: Test the SDK
run: |
SIMULATOR_ID=$(xcrun simctl list devices available -j \
| python3 -c "
import json, sys
d = json.load(sys.stdin)['devices']
iphones = [
v for k, vals in d.items()
if 'iOS' in k
for v in vals
if 'iPhone' in v['name'] and v['isAvailable']
]
if not iphones:
print('error: no available iPhone simulator found', file=sys.stderr)
sys.exit(1)
print(sorted(iphones, key=lambda x: x['name'])[-1]['udid'])
")
echo "Using simulator: $SIMULATOR_ID"
xcodebuild test \
-scheme PayabliSDK-Package \
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
-clonedSourcePackagesDirPath .build/checkouts \
CODE_SIGNING_ALLOWED=NO \
| xcpretty && exit ${PIPESTATUS[0]}
-enableCodeCoverage YES \
-resultBundlePath SDKTests.xcresult \
-quiet \
CODE_SIGNING_ALLOWED=NO

# -quiet names the failing tests and stops there, so the assertion comes
# back out of the result bundle.
- name: Report test failures
if: failure()
run: ./Scripts/print-test-failures.sh SDKTests.xcresult

# Sources only. sonar-project.properties measures that root, and an
# .xcresult also covers the test files themselves and the vendored
# card-reader source the suite touches.
- name: Convert coverage
run: |
./Scripts/xccov-to-sonarqube-generic.sh --include Sources/ \
SDKTests.xcresult > coverage.xml

# Only the coverage, for the same reason as the report above.
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.xml

test-demo:
name: Sample app step sequences
runs-on: macos-15

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Nothing here pushes, so the token is not left in `.git/config` for
# the branch's own scripts to find.
persist-credentials: false

- uses: ./.github/actions/ios-toolchain

# The sample app's step sequences, which decide what each screen offers
# next. A separate scheme, because this bundle has no host application:
# Secrets.swift is gitignored and is a member of the app target, so the app
# itself cannot be built here.
- name: Test the sample app's step sequences
run: |
xcodebuild test \
-project Example/PayabliDemo/PayabliDemo.xcodeproj \
-scheme PayabliDemoFlowTests \
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
-clonedSourcePackagesDirPath .build/checkouts \
-enableCodeCoverage YES \
-resultBundlePath DemoFlowTests.xcresult \
-quiet \
CODE_SIGNING_ALLOWED=NO

- name: Report test failures
if: failure()
run: ./Scripts/print-test-failures.sh DemoFlowTests.xcresult
Loading
Loading