Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c494713
Fix Core Data migration startup crashes
takemiyamakoto Jul 26, 2026
d7648e2
Fix production iOS release identity
takemiyamakoto Jul 26, 2026
b851725
Guard the production iOS archive identity
takemiyamakoto Jul 26, 2026
1d04582
Bind the iOS build number to release settings
takemiyamakoto Jul 26, 2026
749dbdd
Match WalletConnect storage to signed app groups
takemiyamakoto Jul 26, 2026
5ebcf5e
Keep TODO debt baseline current
takemiyamakoto Jul 26, 2026
b11cdc5
Update the Core Data release test invariant
takemiyamakoto Jul 26, 2026
1424b59
Clarify the Core Data release cohort
takemiyamakoto Jul 26, 2026
bcc3997
Keep custom-only chains available after migration
takemiyamakoto Jul 26, 2026
7a819cb
Isolate Debug builds from production wallet data
takemiyamakoto Jul 26, 2026
d689535
Gate and sign the migration crash hotfix release
takemiyamakoto Jul 26, 2026
e6cb0e9
Scope TestFlight provisioning to the app target
takemiyamakoto Jul 26, 2026
afbcde6
Scope App Store signing to the release target
takemiyamakoto Jul 26, 2026
f56b7b8
Accept standard Xcode archive application paths
takemiyamakoto Jul 26, 2026
5469d34
docs: track TestFlight publication evidence
takemiyamakoto Jul 26, 2026
02e7620
build: bump TestFlight build for in-place update
takemiyamakoto Jul 26, 2026
cdb8474
fix: make wallet observation activation lossless
takemiyamakoto Jul 26, 2026
ceb0ed8
build: advance TestFlight hotfix to 2026.7.28
takemiyamakoto Jul 26, 2026
3f00f46
ci: pin workflow actions for release safety
takemiyamakoto Jul 26, 2026
9c89b26
fix: prohibit empty meta account codecs
takemiyamakoto Jul 26, 2026
838840b
test: isolate account export operations
takemiyamakoto Jul 26, 2026
f7e44e6
ci: use pinned Codecov uploader with OIDC
takemiyamakoto Jul 26, 2026
10164c0
ci: generate executable iOS coverage data
takemiyamakoto Jul 26, 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
2 changes: 1 addition & 1 deletion .github/workflows/branch-flow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Branch flow audit self-test
run: bash scripts/test-branch-flow-audit.sh
Expand Down
46 changes: 43 additions & 3 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ on:
jobs:
build:
runs-on: macOS-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Branch flow audit
run: |
Expand All @@ -30,6 +35,16 @@ jobs:
bash ./scripts/test-todo-debt-audit.sh
bash ./scripts/audit-todo-debt.sh

- name: TestFlight publication evidence
run: |
bash ./scripts/test-testflight-publication-readiness-audit.sh
bash ./scripts/audit-testflight-publication-readiness.sh

- name: Codecov executable coverage contract
run: |
bash ./scripts/test-codecov-coverage-contract.sh
bash ./scripts/audit-codecov-coverage-contract.sh

- name: Iroha mobile SDK release asset contract
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -53,7 +68,7 @@ jobs:

- name: Upload shared-features delta report
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: shared-features-delta-report
path: build/reports/shared-features-delta-report.json
Expand Down Expand Up @@ -145,13 +160,16 @@ jobs:
esac
echo "Using destination: $DESTINATION"
LOG_FILE="$PWD/build/codecov-xcodebuild.log"
RESULT_BUNDLE="$PWD/build/CodecovTests.xcresult"
mkdir -p "$PWD/build"
if ! xcodebuild \
-workspace fearless.xcworkspace \
-scheme fearless.tests \
-clonedSourcePackagesDirPath "$SP_DIR" \
-disableAutomaticPackageResolution \
-destination "$DESTINATION" \
-enableCodeCoverage YES \
-resultBundlePath "$RESULT_BUNDLE" \
build test | tee "$LOG_FILE" | xcpretty --test; then
echo "Build/Test failed. Key errors from $LOG_FILE:" >&2
if command -v rg >/dev/null 2>&1; then
Expand All @@ -161,9 +179,31 @@ jobs:
fi
exit 1
fi
COVERAGE_PROFILE="$(
find "$HOME/Library/Developer/Xcode/DerivedData" \
-type f -name '*.profdata' -print -quit
)"
if [[ -z "$COVERAGE_PROFILE" ]]; then
echo "Tests passed but Xcode produced no coverage profile." >&2
exit 1
fi
xcrun llvm-profdata show "$COVERAGE_PROFILE" >/dev/null || {
echo "Xcode produced an unreadable coverage profile: $COVERAGE_PROFILE" >&2
exit 1
}
test -d "$RESULT_BUNDLE" || {
echo "Xcode did not produce the required result bundle." >&2
exit 1
}

- name: Upload coverage to Codecov
run: bash <(curl -s https://codecov.io/bash) -J 'fearless'
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
fail_ci_if_error: true
plugins: xcode
swift_project: fearless
use_oidc: true
version: v11.3.1

jenkins-pr-merge:
name: continuous-integration/jenkins/pr-merge
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/ios-release-safety.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: iOS Release Safety

on:
pull_request:
push:
branches:
- master
- "release/**"
- "hotfix/**"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ios-release-safety-${{ github.ref }}
cancel-in-progress: true

jobs:
release-contracts:
name: release-contracts
runs-on: macos-15
timeout-minutes: 20
steps:
- name: Check out exact commit
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1

- name: Require clean exact checkout
run: |
set -euo pipefail
test -z "$(git status --porcelain=v1 --untracked-files=all)"
git rev-parse --verify HEAD

- name: Run release gate contract suites
run: |
set -euo pipefail
bash scripts/test-ios-release-identity-audit.sh
bash scripts/test-ios-release-archive-signing-contract.sh
bash scripts/test-ios-signed-release-artifact-audit.sh
bash scripts/test-coredata-release-gate.sh
bash scripts/test-coredata-simulator-rehearsal.sh
bash scripts/storage/test-user-storage-compatibility-model-audit.sh

- name: Reject silent x86 Release omissions
run: |
set -euo pipefail
if rg -n 'Skipping Release simulator tests|Release simulator tests were skipped' scripts/test-matrix.sh; then
echo "Release matrix still contains a green skip path." >&2
exit 1
fi

required-context:
name: ios-release-safety
runs-on: ubuntu-latest
needs: release-contracts
if: always()
steps:
- name: Require release contract success
env:
RESULT: ${{ needs.release-contracts.result }}
run: |
set -euo pipefail
[[ "$RESULT" == "success" ]] || {
echo "iOS release safety contracts did not pass: $RESULT" >&2
exit 1
}
111 changes: 104 additions & 7 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def ghStatusFallback(String context, String state, String description) {

// Ensure SPM and shared-features patches are applied before the main pipeline.
// This resolves Web3 API drift (Data.bytes) and IrohaCrypto modulemap issues prior to archive.
node('mac-fearless') {
try {
node('mac-fearless') {
stage('Bootstrap CI deps') {
checkout scm
sh label: 'Bootstrap Pods/SPM/LFS + apply shared-features fixes', script: 'bash scripts/ci/bootstrap.sh'
Expand Down Expand Up @@ -78,17 +79,113 @@ node('mac-fearless') {
scripts/test-matrix.sh fearless.tests "$DESTINATION"
'''
ghNotifySafe context: 'jenkins/ios-tests', status: 'SUCCESS', description: 'All tests passed'
ghNotifySafe context: 'continuous-integration/jenkins/pr-merge', status: 'SUCCESS', description: 'Jenkins PR merge build passed'
ghStatusFallback('jenkins/ios-tests', 'success', 'All tests passed')
ghStatusFallback('continuous-integration/jenkins/pr-merge', 'success', 'Jenkins PR merge build passed')
} catch (e) {
ghNotifySafe context: 'jenkins/ios-tests', status: 'FAILURE', description: 'Unit tests failed'
ghNotifySafe context: 'continuous-integration/jenkins/pr-merge', status: 'FAILURE', description: 'Jenkins PR merge build failed'
ghStatusFallback('jenkins/ios-tests', 'failure', 'Unit tests failed')
ghStatusFallback('continuous-integration/jenkins/pr-merge', 'failure', 'Jenkins PR merge build failed')
throw e
}
}
}

appPipeline.runPipeline('fearless')
ghNotifySafe context: 'jenkins/ios-release-safety', status: 'PENDING', description: 'Running release safety gates'
ghStatusFallback('jenkins/ios-release-safety', 'pending', 'Running release safety gates')
stage('iOS Release Safety Contracts') {
sh label: 'Run fail-closed release gate contract tests', script: '''
set -euo pipefail
bash scripts/test-ios-release-identity-audit.sh
bash scripts/test-ios-signed-release-artifact-audit.sh
bash scripts/test-coredata-release-gate.sh
bash scripts/test-coredata-simulator-rehearsal.sh
bash scripts/storage/test-user-storage-compatibility-model-audit.sh
'''
}

// Jenkins multibranch PR jobs expose BRANCH_NAME as PR-<number>. Bind the
// gate to both direct release branches and the repository's allowed hotfix /
// release promotion paths so a PR cannot bypass the exact Release evidence.
def directReleaseBranch = (
env.BRANCH_NAME == 'master' ||
env.BRANCH_NAME?.startsWith('release/') ||
env.BRANCH_NAME?.startsWith('hotfix/')
)
def releasePullRequest = (
env.CHANGE_ID &&
(
(
env.CHANGE_TARGET == 'develop' &&
env.CHANGE_BRANCH?.startsWith('hotfix/')
) ||
(
env.CHANGE_TARGET == 'master' &&
(
env.CHANGE_BRANCH == 'develop' ||
env.CHANGE_BRANCH?.startsWith('release/') ||
env.CHANGE_BRANCH?.startsWith('hotfix/')
)
)
)
)
def releaseBranch = directReleaseBranch || releasePullRequest
if (releaseBranch) {
stage('Exact Core Data Release Gate') {
sh label: 'Require arm64 and exact 412-test Release inventory', script: '''
set -euo pipefail
[[ "$(uname -m)" == "arm64" ]] || {
echo "Exact Release gate requires dispatch to an arm64 macOS agent." >&2
exit 78
}
IOS_EXPECTED_BUILD_NUMBER=2026.7.28 \
IOS_RELEASE_SOURCE_PACKAGES_DIR="$PWD/SourcePackages" \
bash scripts/ci/audit-ios-release-identity.sh
bash scripts/storage/audit-user-storage-compatibility-models.sh
bash scripts/ci/run-coredata-release-gate.sh --stage core
'''
}
}

if (env.IOS_RELEASE_CANDIDATE == '1') {
stage('Copied-phone Release Evidence') {
sh label: 'Require immutable copied-phone fixture stage', script: '''
set -euo pipefail
: "${FEARLESS_SUBSTRATE_PHONE_STORE_FIXTURE:?release candidate requires the copied-phone fixture}"
: "${FEARLESS_CORE_DATA_SIMULATOR_UDID:?release candidate requires an explicit Simulator UDID}"
bash scripts/ci/run-coredata-release-gate.sh \
--stage copied-phone \
--simulator-udid "$FEARLESS_CORE_DATA_SIMULATOR_UDID" \
--fixture "$FEARLESS_SUBSTRATE_PHONE_STORE_FIXTURE"
'''
}
}
}

appPipeline.runPipeline('fearless')

// A required context must not turn green until every custom gate and the
// shared application pipeline have completed. The fallback needs a node
// because it uses curl through the shell.
ghNotifySafe context: 'jenkins/ios-release-safety', status: 'SUCCESS', description: 'All iOS release safety gates passed'
ghNotifySafe context: 'continuous-integration/jenkins/pr-merge', status: 'SUCCESS', description: 'Jenkins PR merge build passed'
node('mac-fearless') {
stage('Finalize Required GitHub Statuses') {
ghStatusFallback('jenkins/ios-release-safety', 'success', 'All iOS release safety gates passed')
ghStatusFallback('continuous-integration/jenkins/pr-merge', 'success', 'Jenkins PR merge build passed')
}
}
} catch (e) {
// Post plugin failures first. If a macOS executor cannot be reacquired for
// the API fallback, the required contexts remain failed or pending, never
// stale green.
ghNotifySafe context: 'jenkins/ios-release-safety', status: 'FAILURE', description: 'iOS release safety or pipeline failed'
ghNotifySafe context: 'continuous-integration/jenkins/pr-merge', status: 'FAILURE', description: 'Jenkins PR merge build failed'
try {
node('mac-fearless') {
stage('Report Required GitHub Status Failure') {
ghStatusFallback('jenkins/ios-release-safety', 'failure', 'iOS release safety or pipeline failed')
ghStatusFallback('continuous-integration/jenkins/pr-merge', 'failure', 'Jenkins PR merge build failed')
}
}
} catch (statusError) {
echo "Could not run GitHub status API fallback: ${statusError.message}"
}
throw e
}
80 changes: 80 additions & 0 deletions config/testflight-publication-readiness.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"schemaVersion": 1,
"platform": "ios",
"distributionChannel": "testflight-external-public-link",
"assessedAtUTC": "2026-07-26T13:26:11Z",
"evidenceBoundary": {
"contract": "tracked-sanitized-publication-snapshot",
"observationMethod": "authenticated-app-store-connect-and-unauthenticated-public-link-in-safari",
"rawAttestationsTracked": false,
"rawAttestationsLocation": "ignored-build-output",
"currentStateRevalidationRequired": true
},
"artifact": {
"sourceCommit": "f56b7b896344cfded39456217097747ef9efeacf",
"bundleIdentifier": "jp.co.soramitsu.fearlesswallet",
"marketingVersion": "4.2.0",
"buildNumber": "2026.7.26",
"preUploadArchiveTreeSHA256": "301f19a4f1232fb05719d85b825ac081196fa5fb6988b58011b7569a4bd9e6fa",
"executableSHA256": "cf0318792fc361bbd8704b21b6e1a08c4d81897f23492e4a807eab7f22403568",
"signedArchiveAuditReceiptSHA256": "1dbf38740b38403dde9cc7d4b1710cd28fdd5dc6f6f25990a7102020f11d1abc",
"preservedDataArchiveRehearsalResultSHA256": "418d22cdd9e673ddfa1f47a49c666df11a740aac6737f6ef2b0328fa1b6a8641"
},
"upload": {
"status": "success",
"uploadedAtUTC": "2026-07-26T13:16:00Z",
"postUploadArchiveTreeSHA256": "a483a372fe8fd19761d3b0324427c4cf55a4553b4ed7433b34b78517f292cea7",
"postUploadArchiveInfoPlistSHA256": "e82ff42b4b47713d1094c420dd7d1abfbfdfe7e7b80c972909936ad64e1e4756",
"postUploadAttestationSHA256": "6ede1d8fb981be149a82d69313be0ebc1838291e27d0ecf1c1576c95350b1af9",
"xcodeMutation": "Distributions-only",
"reconstructedPreUploadArchiveTreeMatched": true,
"embeddedAppSignatureVerified": true
},
"testFlight": {
"processingStatus": "testing",
"externalTestingActive": true,
"externalGroup": {
"name": "Public Beta Test",
"id": "9410949d-b468-40e5-b2f4-24055e65d270",
"testerCount": 122,
"buildCount": 2,
"containsBuild": true,
"publicLinkTesterCount": 120,
"publicLinkTesterLimit": 500
},
"publicLink": {
"enabled": true,
"shareableToAnyoneWithLink": true,
"url": "https://testflight.apple.com/join/012KzFyD",
"urlSHA256": "d6599b1b9c0f7e77d9dd80d9185da67c1d864be5df9a7bbf360f245b099a000c",
"landingPageResolved": true,
"landingPageApplicationName": "Fearless Wallet: DeFi Wallet",
"testFlightDeepLinkPresent": true
},
"whatToTestSHA256": "d5d0c2063934c4ca7dd8bb82af038d79909738c93cf96c11ab63bca8c0b7f272",
"automaticTesterNotification": true,
"publicationAttestationSHA256": "84e53d78316cf46cc1956d04e86782241d6e575da90fef109acaaf44cb368d4c"
},
"symbolication": {
"status": "degraded-third-party-symbolication",
"runtimeInstallLaunchOrMigrationImpact": false,
"mainApplicationDSYMPresent": true,
"runtimeFrameworkMissingVendorDSYM": "MPQRCoreSDK.framework",
"staticStubFrameworkWarnings": [
"blake2lib.framework",
"libed25519.framework",
"sr25519lib.framework"
],
"productionFollowUpRequired": true
},
"readiness": {
"status": "blocked",
"publicationVerified": true,
"externalTestingActive": true,
"exactAppleDeliveredInstallVerified": false,
"preservedDataPostInstallRehearsalVerified": false,
"releaseEnabled": false,
"blockerCode": "testflight-exact-build-preserved-data-install-pending",
"requiredAction": "Update in place through TestFlight without deleting or clearing app data, then repeat the five-cold-launch and store-integrity rehearsal."
}
}
3 changes: 0 additions & 3 deletions config/todo-debt-baseline.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ fearless/Common/Crypto/Data+Keccak.swift // TODO: move to library
fearless/Common/Extension/Foundation/NSPredicate+Filter.swift // TODO: Remove
fearless/Common/Extension/Foundation/NSPredicate+Validation.swift // TODO: Add deriviationPathHardSoftNumeric?
fearless/Common/Extension/Foundation/NSPredicate+Validation.swift // TODO: Add deriviationPathHardSoftNumericPassword?
fearless/Common/Extension/Storage/CDAccountItem+CoreDataDecodable.swift // TODO: Fix logic
fearless/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeHotBootSnapshotFactory.swift // TODO: think about it
fearless/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeSnapshotOperationFactory.swift // TODO: think about it
fearless/Common/Services/ExtrinsicService/ExtrinsicOperationFactory.swift method: "author_submitAndWatchExtrinsic", // TODO: add to fearless utils
fearless/Common/Storage/Migration/UserStorage/MultiassetV2MigrationPolicy.swift // TODO: tech debt
fearless/Common/Storage/Migration/UserStorage/StorageMigrator+Sync.swift // TODO: Move this logic to app loading state
fearless/Modules/AddAccount/Interactors/AddAccount+AccountConfirmInteractor.swift // TODO: Check how to convert this to chain account import
fearless/Modules/Export/ExportMnemonic/ExportMnemonicPresenter.swift // TODO: Support custom accounts
fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentPresenter.swift // TODO: - need think about this
Expand Down
Loading
Loading