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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
This commit is contained in:
Krzysztof Rudnicki 2026-08-28 15:13:11 +02:00
parent d8bb8943eb
commit d67c770cc9
13 changed files with 178 additions and 57 deletions

3
.fvmrc Normal file
View File

@ -0,0 +1,3 @@
{
"flutter": "3.47.2"
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)$

View File

@ -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

View File

@ -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

View File

@ -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 <file> [<file> ...] # 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 "$@"

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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:

View File

@ -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