From 86f44bfd11550d25fd94932f146bfc8362be4877 Mon Sep 17 00:00:00 2001 From: adeshi Date: Mon, 19 Oct 2026 08:17:35 +0100 Subject: [PATCH 1/3] ci: pass a real build-size baseline and fail on main without one (#560) CI reads the baseline from ANDROID_BASE_SIZE_MB and IOS_BASE_SIZE_MB, so the 5% regression check runs. A build on main with no baseline fails with a clear message. Tests cover growth over and under the threshold. Closes #560 Co-Authored-By: Claude Haiku 5.5 --- .../scripts/tests/test_track_build_size.py | 57 +++++++++++++++++++ .github/scripts/track_build_size.py | 9 ++- .github/workflows/android-ci.yml | 2 +- .github/workflows/ios-ci.yml | 2 +- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/tests/test_track_build_size.py diff --git a/.github/scripts/tests/test_track_build_size.py b/.github/scripts/tests/test_track_build_size.py new file mode 100644 index 00000000..22dec276 --- /dev/null +++ b/.github/scripts/tests/test_track_build_size.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Tests for track_build_size.py regression handling. + +Run directly: + python3 -m unittest discover -s .github/scripts/tests -p "test_*.py" -v +""" +from __future__ import annotations + +import contextlib +import importlib.util +import io +import os +import pathlib +import tempfile +import unittest + +SCRIPT = pathlib.Path(__file__).resolve().parents[1] / "track_build_size.py" +spec = importlib.util.spec_from_file_location("track_build_size", SCRIPT) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + +APK = "android/app/build/outputs/apk/release/app-release.apk" + + +class AndroidRegressionTest(unittest.TestCase): + def setUp(self): + self._old_cwd = os.getcwd() + self._tmp = tempfile.TemporaryDirectory() + os.chdir(self._tmp.name) + pathlib.Path(APK).parent.mkdir(parents=True) + + def tearDown(self): + os.chdir(self._old_cwd) + self._tmp.cleanup() + + def _write_apk(self, size_mb: float): + pathlib.Path(APK).write_bytes(b"x" * int(size_mb * 1024 * 1024)) + + def test_growth_over_five_percent_warns(self): + self._write_apk(10.6) + out = io.StringIO() + with contextlib.redirect_stdout(out): + result = module.track_android_size(10.0) + self.assertEqual(result, "warning") + self.assertIn("::warning::Android APK size increased", out.getvalue()) + + def test_growth_within_five_percent_does_not_warn(self): + self._write_apk(10.2) + out = io.StringIO() + with contextlib.redirect_stdout(out): + result = module.track_android_size(10.0) + self.assertNotEqual(result, "warning") + self.assertNotIn("::warning::", out.getvalue()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/track_build_size.py b/.github/scripts/track_build_size.py index a73e27eb..06074345 100644 --- a/.github/scripts/track_build_size.py +++ b/.github/scripts/track_build_size.py @@ -128,12 +128,19 @@ def main(): platform = sys.argv[1].lower() base_size_mb = None - if len(sys.argv) > 2: + if len(sys.argv) > 2 and sys.argv[2].strip(): try: base_size_mb = float(sys.argv[2]) except ValueError: pass + if base_size_mb is None and os.environ.get("GITHUB_REF") == "refs/heads/main": + print( + f"::error::No {platform} size baseline on main. " + f"Set the {platform.upper()}_BASE_SIZE_MB repository variable so the 5% check can run." + ) + sys.exit(1) + if platform == "android": result = track_android_size(base_size_mb) elif platform == "ios": diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 99aa7174..047d319b 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -160,7 +160,7 @@ jobs: - name: Measure APK size run: | - python3 ../.github/scripts/track_build_size.py android + python3 ../.github/scripts/track_build_size.py android "${{ vars.ANDROID_BASE_SIZE_MB }}" # task #173: A release build must never ship placeholder certificate pins — the # failure mode is silent until every user hits a connection failure in production. diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 263da725..4870fe07 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -252,7 +252,7 @@ jobs: - name: Measure app size run: | - python3 "$GITHUB_WORKSPACE/.github/scripts/track_build_size.py" ios + python3 "$GITHUB_WORKSPACE/.github/scripts/track_build_size.py" ios "${{ vars.IOS_BASE_SIZE_MB }}" # Runs hosted tests that require a real app process (Keychain, UNUserNotificationCenter). # These tests are in EthosProtocolTests target within the Xcode project, not in the SPM From 15532e19b037d375678a9a9c0e95c9414f0f4fd1 Mon Sep 17 00:00:00 2001 From: adeshi Date: Mon, 19 Oct 2026 08:17:42 +0100 Subject: [PATCH 2/3] ci: cap the Android dependency scan and pass the NVD key when set (#531) Refs #557, #558: the same scheduled failure. Its cause is only in the run log, so this change does not confirm a fix for those. Closes #531 Co-Authored-By: Claude Haiku 5.5 --- .github/workflows/android-dependency-check.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/android-dependency-check.yml b/.github/workflows/android-dependency-check.yml index ab2771fc..a6ff5cba 100644 --- a/.github/workflows/android-dependency-check.yml +++ b/.github/workflows/android-dependency-check.yml @@ -38,6 +38,8 @@ jobs: # scanning happening without blocking unrelated PRs. dependency-check: runs-on: ubuntu-latest + # Caps the run so a stalled NVD sync cannot hang the job for hours (see #178). + timeout-minutes: 45 # The "Alert on failure" step files a GitHub issue on scheduled-run # failures, which needs issues:write — the default GITHUB_TOKEN is # read-only, so without this the alert itself fails with a 403. @@ -82,6 +84,9 @@ jobs: # failure — anything else (a real CVSS >= 7.0 finding, or any other error) # still fails the job. - name: Scan dependencies for high severity vulnerabilities + env: + # Optional: with a key the NVD feed is not rate-limited (HTTP 429). + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} run: | set +e ./gradlew dependencyCheckAggregate 2>&1 | tee dependency-check-output.log From 5fcd69a1543e9e2b5adb2d9f235e21034c2d64a3 Mon Sep 17 00:00:00 2001 From: adeshi Date: Mon, 19 Oct 2026 08:41:15 +0100 Subject: [PATCH 3/3] ci: keep the dependency-check output as a run artifact (#558) Refs #557. The failed scheduled runs only show their cause in the log, so the output is uploaded on every run. This does not yet confirm the cause. Refs #558 Co-Authored-By: Claude Haiku 5.5 --- .github/workflows/android-dependency-check.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/android-dependency-check.yml b/.github/workflows/android-dependency-check.yml index a6ff5cba..8120b3db 100644 --- a/.github/workflows/android-dependency-check.yml +++ b/.github/workflows/android-dependency-check.yml @@ -104,6 +104,17 @@ jobs: exit "$STATUS" + # Keeps the scan output as a run artifact, so the cause of a failed scheduled run + # can be read without opening the log. Refs #557, #558. + - name: Keep dependency-check output + if: always() + uses: actions/upload-artifact@v4 + with: + name: dependency-check-output + path: android/dependency-check-output.log + if-no-files-found: ignore + retention-days: 30 + - name: Alert on failure (scheduled run) if: failure() && github.event_name == 'schedule' uses: actions/github-script@v7