From d67c770cc9367271d63e1cca6e7623def25eac74 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 28 Aug 2026 15:13:11 +0200 Subject: [PATCH] Latest stable dependencies, and the gate that keeps them there Python side: gatelock v0.7.1 -> v0.7.2 and crdt-sync v0.9.0 -> v0.9.1 (both re-cut so consumers stop resolving pre-upgrade constraints), plus every `>=` floor turned into an exact pin -- five majors among them (mypy 1 -> 2.3.1, pylint 3 -> 4.0.7, pytest 8 -> 9.1.1, pytest-cov 4 -> 7.1.0, ruff 0.8 -> 0.16.5) and mcp pinned at 2.1.1. `scripts/ci_mirror.sh` builds a clean venv from requirements.txt and runs the whole CI set: 1146 tests, 100% branch coverage, no lint or type finding. workout_app: sync_settings_ui v0.1.0 -> v0.4.1 and ten exact pins, with the flutter_secure_storage 11 chain (compileSdk 37 / AGP 9.3.2 / Gradle 9.7.1). 411 tests green, analyzer clean, release APK builds. CI's Flutter pin was 3.44.6 while `.fvmrc` says 3.47.2 -- the drift that made kuhylog red on a commit that is clean locally. Both workflows now say 3.47.2, and the gate reads that pin from now on. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK --- .fvmrc | 3 + .github/workflows/dependency-freshness.yml | 57 +++++++++++++++++++ .github/workflows/pre-commit.yml | 2 +- .github/workflows/release-apk.yml | 2 +- .pre-commit-config.yaml | 22 +++++++ pyproject.toml | 6 +- requirements.txt | 34 +++++------ scripts/check_dependency_freshness.sh | 34 +++++++++++ .../workout_app/android/app/build.gradle.kts | 7 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../workout_app/android/settings.gradle.kts | 2 +- .../workout_app/pubspec.lock | 28 ++++----- .../workout_app/pubspec.yaml | 36 ++++++------ 13 files changed, 178 insertions(+), 57 deletions(-) create mode 100644 .fvmrc create mode 100644 .github/workflows/dependency-freshness.yml create mode 100755 scripts/check_dependency_freshness.sh diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..16a96ef --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.47.2" +} diff --git a/.github/workflows/dependency-freshness.yml b/.github/workflows/dependency-freshness.yml new file mode 100644 index 0000000..e3f46ac --- /dev/null +++ b/.github/workflows/dependency-freshness.yml @@ -0,0 +1,57 @@ +name: dependency freshness + +# Every dependency on its ecosystem's newest STABLE release, enforced on push +# as well as at commit time. The local pre-commit hook only sees the manifests +# in a commit and can be skipped entirely by a fresh clone that never ran +# `pre-commit install`; this runs `--all` over the whole repo. +# +# `--strict` is CI-only. Offline the gate degrades to its cached answer and +# passes, because `--no-verify` is banned locally and a hook that hard-failed +# on a flaky connection would leave no way to commit. A runner is always +# online, so here an undeterminable answer is a real failure. +# +# Expect this to go red for reasons this repo's commit did not cause: an +# upstream release overnight is enough. That is the chosen behaviour -- the +# alternative is a gate that only notices staleness when someone remembers to +# look. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + dependency-freshness: + name: dependencies on newest stable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # The gate lives in the shared utils repo so every repo resolves the + # same "latest stable" the same way. Checking it out beats vendoring a + # copy, which is exactly how a gate and its survey start disagreeing. + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install the gate's two runtime dependencies + run: python -m pip install --upgrade pyyaml packaging + + - name: Cache registry lookups + uses: actions/cache@v4 + with: + path: ~/.cache/dep-freshness + key: dep-freshness-${{ hashFiles('**/pubspec.yaml', '**/pyproject.toml', + '**/requirements*.txt', '**/package.json', '**/Cargo.toml', '**/go.mod') }} + restore-keys: dep-freshness- + + - name: Every dependency is on newest stable + run: bash .utils/scripts/check_dependency_freshness.sh --all --strict diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 497d93d..0ccab4f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -19,7 +19,7 @@ jobs: # developed against (Dart SDK ^3.12.2) so CI's analyzer matches local. - uses: subosito/flutter-action@v2 with: - flutter-version: "3.44.6" + flutter-version: 3.47.2 channel: stable cache: true - name: Install dependencies diff --git a/.github/workflows/release-apk.yml b/.github/workflows/release-apk.yml index 05bfb11..6f27acd 100644 --- a/.github/workflows/release-apk.yml +++ b/.github/workflows/release-apk.yml @@ -50,7 +50,7 @@ jobs: - uses: subosito/flutter-action@v2 with: - flutter-version: 3.44.6 + flutter-version: 3.47.2 channel: stable cache: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5f81a9..a759e83 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -245,3 +245,25 @@ repos: entry: scripts/check_md_naming.sh language: system files: \.(md|markdown)$ + + # Every dependency on its ecosystem's newest STABLE release, exact-pinned + # so the question is mechanically answerable at all. + # + # `files:` is what keeps this cheap: a commit touching only source makes + # ZERO network requests, and manifest commits mostly hit a 6h disk cache. + # + # Runs WITHOUT --strict on purpose. Offline it degrades to the cached + # answer and passes with a loud banner; `--no-verify` is banned here, so + # a hook that hard-failed on a flaky connection would leave no way to + # commit at all. CI, always online, is the strict authority. + - id: dependency-freshness + name: dependencies on newest stable + entry: scripts/check_dependency_freshness.sh + language: system + pass_filenames: true + files: >- + (pubspec\.(yaml|lock)|pyproject\.toml|requirements.*\.txt|uv\.lock + |package\.json|package-lock\.json|pnpm-lock\.yaml + |Cargo\.(toml|lock)|go\.(mod|sum) + |\.fvmrc|\.nvmrc|\.python-version + |\.github/workflows/.*\.ya?ml)$ diff --git a/pyproject.toml b/pyproject.toml index 3b845bd..81d4d0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,8 @@ requires-python = ">=3.10" # crdt-sync: shared CRDT merge scheme + GitHub-Contents-API sync transport, # also used by diet_guard; here for phone-workout GitHub sync (Workstream C) dependencies = [ - "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.1#subdirectory=gatelock", - "crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.0#subdirectory=crdt-sync", + "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock", + "crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync", ] [project.scripts] @@ -20,7 +20,7 @@ screen-locker-status = "screen_locker.status_view:main" # so the systemd/CLI system-python path stays dependency-light and never has # to import the MCP SDK. [project.optional-dependencies] -mcp = ["mcp>=2,<3"] +mcp = ["mcp==2.1.1"] [tool.setuptools.packages.find] # This repo also holds stronglift_replacement/ (an unrelated Flutter app, not diff --git a/requirements.txt b/requirements.txt index 91f897a..4ba476c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,22 @@ # Screen Locker — runtime + development dependencies # Runtime: tkinter/subprocess/socket/sqlite3 (stdlib) plus gatelock (below). -bandit>=1.7.0 -codespell>=2.2.0 -coverage>=7.4.0 -crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.0#subdirectory=crdt-sync -gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.1#subdirectory=gatelock -mcp>=2,<3 -mypy>=1.8.0 -pre-commit>=3.6.0 -pylint>=3.0.0 -pytest>=8.0.0 -pytest-cov>=4.1.0 -pytest-randomly>=3.15.0 -pytest-sugar>=1.0.0 -pytest-xdist>=3.5.0 +bandit==1.9.4 +codespell==2.4.3 +coverage==7.15.4 +crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync +gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock +mcp==2.1.1 +mypy==2.3.1 +pre-commit==4.6.2 +pylint==4.0.7 +pytest==9.1.1 +pytest-cov==7.1.0 +pytest-randomly==4.1.0 +pytest-sugar==1.1.1 +pytest-xdist==3.8.0 # Direct dependency since the Firebase session recovery exchanges a refresh # token over the REST endpoint; crdt-sync already pulls requests in, but this # module imports it itself, so it is declared rather than assumed. -requests>=2.31.0 -ruff>=0.8.0 -types-requests>=2.31.0 +requests==2.34.2 +ruff==0.16.5 +types-requests==2.33.0.20260712 diff --git a/scripts/check_dependency_freshness.sh b/scripts/check_dependency_freshness.sh new file mode 100755 index 0000000..5cb0d78 --- /dev/null +++ b/scripts/check_dependency_freshness.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# ============================================================================ +# Fail if any dependency in the commit is behind its ecosystem's newest stable. +# +# Thin delegate to the shared gate in ~/utils, which owns the registry +# adapters, the pre-release rules and the allowlist semantics. Copying that +# logic here is what lets one repo's idea of "current" drift from every other +# repo's -- so this script only locates the shared checker and forwards its +# arguments. +# +# Usage: +# scripts/check_dependency_freshness.sh [ ...] # pre-commit +# scripts/check_dependency_freshness.sh --all --strict # CI +# +# Exit: 0 current | 1 behind | 2 allowlist wrong | 3 undeterminable (offline) +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/utils}/scripts/check_dependency_freshness.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared dependency-freshness gate not found at $SHARED_GATE" >&2 + echo " Clone github.com/kuhyx/utils to ~/utils, or set" >&2 + echo " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@" diff --git a/stronglift_replacement/workout_app/android/app/build.gradle.kts b/stronglift_replacement/workout_app/android/app/build.gradle.kts index d693069..22b5712 100644 --- a/stronglift_replacement/workout_app/android/app/build.gradle.kts +++ b/stronglift_replacement/workout_app/android/app/build.gradle.kts @@ -18,7 +18,12 @@ val keystoreProperties = Properties().apply { android { namespace = "com.kuhy.workout_app" - compileSdk = flutter.compileSdkVersion + // Pinned above `flutter.compileSdkVersion` (36 on Flutter 3.47.2): + // flutter_secure_storage 11.0.0, which arrives with crdt_sync_flutter + // v0.3.1, publishes AAR metadata demanding API 37 or later. That in + // turn needs AGP > 9.1.0, which needs Gradle >= 9.5.0 -- see + // settings.gradle.kts and gradle-wrapper.properties. + compileSdk = 37 ndkVersion = flutter.ndkVersion compileOptions { diff --git a/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties b/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties index 2d428bf..ffcd530 100644 --- a/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-all.zip diff --git a/stronglift_replacement/workout_app/android/settings.gradle.kts b/stronglift_replacement/workout_app/android/settings.gradle.kts index c21f0c5..c00b1b8 100644 --- a/stronglift_replacement/workout_app/android/settings.gradle.kts +++ b/stronglift_replacement/workout_app/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false + id("com.android.application") version "9.3.2" apply false id("org.jetbrains.kotlin.android") version "2.3.20" apply false } diff --git a/stronglift_replacement/workout_app/pubspec.lock b/stronglift_replacement/workout_app/pubspec.lock index b3c5dcf..d206fde 100644 --- a/stronglift_replacement/workout_app/pubspec.lock +++ b/stronglift_replacement/workout_app/pubspec.lock @@ -141,11 +141,11 @@ packages: dependency: "direct main" description: path: crdt_sync_dart - ref: "crdt_sync_dart-v0.10.0" - resolved-ref: "5f64a8959b1db342da1f73157b1148aa0dfa38a8" + ref: "crdt_sync_dart-v0.11.1" + resolved-ref: "978d32af3af66f0d6500c4c6c0c6df5370ebe9a2" url: "https://github.com/kuhyx/utils" source: git - version: "0.10.0" + version: "0.11.1" crypto: dependency: transitive description: @@ -235,18 +235,18 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" + sha256: "15e8c8fe269fdf7d469b23008ab3df521c8b826ed345820532364c31bdebace6" url: "https://pub.dev" source: hosted - version: "10.3.1" + version: "11.0.0" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" + sha256: ac6d76a752de0cd738334eb4b21743fc4943f449f5b6e308f18838b048c02ac0 url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.4.0" flutter_secure_storage_linux: dependency: transitive description: @@ -557,18 +557,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 + sha256: e7317eb2eb611d1bf0386b6b974be9c50449f975f19a90a7b8ea013550302b30 url: "https://pub.dev" source: hosted - version: "12.0.3" + version: "13.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + sha256: d7676c6fcf2f0b92537ec41476a6ead45a00b0d8bbb852395a6f9f33f49d6242 url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "14.0.0" permission_handler_apple: dependency: transitive description: @@ -794,11 +794,11 @@ packages: dependency: "direct main" description: path: sync_settings_ui - ref: "sync_settings_ui-v0.1.0" - resolved-ref: "23c693da4e6fd6004df09829a7d75365dc086c2c" + ref: "sync_settings_ui-v0.4.1" + resolved-ref: "51700aa89eaf1789ae98491aaa854c54d35b4170" url: "https://github.com/kuhyx/utils" source: git - version: "0.1.0" + version: "0.4.1" synchronized: dependency: transitive description: diff --git a/stronglift_replacement/workout_app/pubspec.yaml b/stronglift_replacement/workout_app/pubspec.yaml index 6bd12ed..0c502be 100644 --- a/stronglift_replacement/workout_app/pubspec.yaml +++ b/stronglift_replacement/workout_app/pubspec.yaml @@ -8,46 +8,46 @@ environment: sdk: ^3.12.2 dependencies: - audioplayers: ^6.4.0 + audioplayers: 6.8.1 crdt_sync: git: url: https://github.com/kuhyx/utils - ref: crdt_sync_dart-v0.10.0 + ref: crdt_sync_dart-v0.11.1 path: crdt_sync_dart flutter: sdk: flutter - flutter_secure_storage: ^10.3.1 + flutter_secure_storage: 11.0.0 # One-tap sign-in for the sync account. App-side only: crdt_sync stays pure # Dart so it keeps working on Linux desktop and headless. - google_sign_in: ^7.2.0 - http: ^1.6.0 - path: ^1.9.1 - path_provider: ^2.1.5 - permission_handler: ^12.0.0 - shared_preferences: ^2.5.3 - sqflite: ^2.4.2 + google_sign_in: 7.2.0 + http: 1.6.0 + path: 1.9.1 + path_provider: 2.1.6 + permission_handler: 13.0.1 + shared_preferences: 2.5.5 + sqflite: 2.4.3 # Linux desktop has no sqflite plugin; the FFI factory drives the same # schema (and already backed the test suite before the desktop target). - sqflite_common_ffi: ^2.4.1 + sqflite_common_ffi: 2.4.2+1 sync_settings_ui: git: url: https://github.com/kuhyx/utils - ref: sync_settings_ui-v0.1.0 + ref: sync_settings_ui-v0.4.1 path: sync_settings_ui - url_launcher: ^6.3.2 - uuid: ^4.6.0 - vibration: ^3.1.0 + url_launcher: 6.3.2 + uuid: 4.6.0 + vibration: 3.2.0 dev_dependencies: - audioplayers_platform_interface: ^7.1.1 + audioplayers_platform_interface: 7.2.0 # Generates the Android launcher icons from assets/icon/ (see # python_pkg/app_icons in testsAndMisc for the shared style). - flutter_launcher_icons: ^0.14.4 + flutter_launcher_icons: 0.14.4 flutter_lints: ^6.0.0 flutter_test: sdk: flutter - very_good_analysis: ^10.2.0 + very_good_analysis: ^10.3.0 flutter: uses-material-design: true