From e36e0a8cfdc8e8b35de6a2fbdb5d7156c3f3a278 Mon Sep 17 00:00:00 2001 From: codeandsolder Date: Fri, 30 Oct 2026 05:40:41 +0100 Subject: [PATCH 1/5] ci: make shared workflow pins Renovate-trackable --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6ab54a..a015183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: jobs: rust-skills2-strict: name: rust-skills2 strict nightly gate - uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@ca8e5368b41e5699095dd0458a3849ecb8db7712 + uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@ca8e5368b41e5699095dd0458a3849ecb8db7712 # main lockfile: name: Lockfile consistency @@ -223,7 +223,7 @@ jobs: if: github.event_name != 'pull_request' permissions: contents: read - uses: codeandsolder/ci-workflows/.github/workflows/rust-sccache.yml@main + uses: codeandsolder/ci-workflows/.github/workflows/rust-sccache.yml@b2414c5358d1061e9b442656298506e98c9d13e7 # main with: command: cargo +1.99.0 test --locked --workspace --lib rust-toolchain: "1.99.0" From 363d78487450077ab528edbd3ef41c3f8d9282ea Mon Sep 17 00:00:00 2001 From: codeandsolder Date: Fri, 30 Oct 2026 05:48:25 +0100 Subject: [PATCH 2/5] ci: move old strict pin to immutable gate --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a015183..ff0c017 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: jobs: rust-skills2-strict: name: rust-skills2 strict nightly gate - uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@ca8e5368b41e5699095dd0458a3849ecb8db7712 # main + uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@94a3b140fac9fc0b2d413871c247951afc660eff # main lockfile: name: Lockfile consistency From 4bd7b417fd0585d19d304e51cc0ffcf2ca698dd6 Mon Sep 17 00:00:00 2001 From: codeandsolder Date: Fri, 30 Oct 2026 07:42:30 +0100 Subject: [PATCH 3/5] ci: satisfy strict Python policy --- .github/scripts/cargo-release-quarantine.py | 4 ++-- .github/scripts/render-maintenance-template.py | 0 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 .github/scripts/cargo-release-quarantine.py mode change 100644 => 100755 .github/scripts/render-maintenance-template.py diff --git a/.github/scripts/cargo-release-quarantine.py b/.github/scripts/cargo-release-quarantine.py old mode 100644 new mode 100755 index 267fbce..4bc8323 --- a/.github/scripts/cargo-release-quarantine.py +++ b/.github/scripts/cargo-release-quarantine.py @@ -51,7 +51,7 @@ def crate_versions(name: str) -> dict[str, dict]: def publication_time(pkg: dict) -> dt.datetime | None: info = crate_versions(pkg["name"]).get(pkg["version"]) value = info.get("created_at") if info else None - return dt.datetime.fromisoformat(value.replace("Z", "+00:00")) if value else None + return dt.datetime.fromisoformat(value) if value else None def main() -> int: @@ -67,7 +67,7 @@ def main() -> int: after = load_lock(Path("Cargo.lock").read_bytes()) changed = [pkg for pkg in after if key(pkg) not in before and is_crates_io(pkg)] - cutoff = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=args.hours) + cutoff = dt.datetime.now(dt.UTC) - dt.timedelta(hours=args.hours) blocked: list[str] = [] for pkg in changed: diff --git a/.github/scripts/render-maintenance-template.py b/.github/scripts/render-maintenance-template.py old mode 100644 new mode 100755 From 70bbf14697f04cb1f4fa55d60d6acf8eafed8d61 Mon Sep 17 00:00:00 2001 From: codeandsolder Date: Fri, 30 Oct 2026 07:57:46 +0100 Subject: [PATCH 4/5] ci: type-check quarantine tooling --- .github/scripts/cargo-release-quarantine.py | 52 ++++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/.github/scripts/cargo-release-quarantine.py b/.github/scripts/cargo-release-quarantine.py index 4bc8323..bc485e8 100755 --- a/.github/scripts/cargo-release-quarantine.py +++ b/.github/scripts/cargo-release-quarantine.py @@ -12,43 +12,67 @@ import urllib.parse import urllib.request from pathlib import Path +from typing import NotRequired, TypedDict, cast CRATES_IO_MARKERS = ("crates.io-index", "index.crates.io") -_cache: dict[str, dict[str, dict]] = {} + + +class LockPackage(TypedDict): + name: str + version: str + source: NotRequired[str] + + +class VersionInfo(TypedDict): + num: str + created_at: NotRequired[str] + + +class CratesResponse(TypedDict): + versions: list[VersionInfo] + + +_cache: dict[str, dict[str, VersionInfo]] = {} def git_show(ref: str, path: str) -> bytes: return subprocess.check_output(["git", "show", f"{ref}:{path}"]) -def load_lock(raw: bytes) -> list[dict]: - return tomllib.loads(raw.decode()).get("package", []) +def load_lock(raw: bytes) -> list[LockPackage]: + packages = tomllib.loads(raw.decode()).get("package", []) + return cast(list[LockPackage], packages) -def key(pkg: dict) -> tuple[str, str, str | None]: +def key(pkg: LockPackage) -> tuple[str, str, str | None]: return pkg["name"], pkg["version"], pkg.get("source") -def is_crates_io(pkg: dict) -> bool: +def is_crates_io(pkg: LockPackage) -> bool: source = pkg.get("source") or "" - return source.startswith("registry+") and any(marker in source for marker in CRATES_IO_MARKERS) + return source.startswith("registry+") and any( + marker in source for marker in CRATES_IO_MARKERS + ) -def crate_versions(name: str) -> dict[str, dict]: +def crate_versions(name: str) -> dict[str, VersionInfo]: if name in _cache: return _cache[name] req = urllib.request.Request( "https://crates.io/api/v1/crates/" + urllib.parse.quote(name, safe=""), - headers={"Accept": "application/json", "User-Agent": "codeandsolder-renovate-quarantine/1.0"}, + headers={ + "Accept": "application/json", + "User-Agent": "codeandsolder-renovate-quarantine/1.0", + }, ) with urllib.request.urlopen(req, timeout=30) as response: - payload = json.load(response) + payload = cast(CratesResponse, json.load(response)) versions = {item["num"]: item for item in payload.get("versions", [])} _cache[name] = versions return versions -def publication_time(pkg: dict) -> dt.datetime | None: +def publication_time(pkg: LockPackage) -> dt.datetime | None: info = crate_versions(pkg["name"]).get(pkg["version"]) value = info.get("created_at") if info else None return dt.datetime.fromisoformat(value) if value else None @@ -73,7 +97,9 @@ def main() -> int: for pkg in changed: when = publication_time(pkg) if when is None: - blocked.append(f"{pkg['name']} {pkg['version']}: missing authoritative crates.io publication time") + blocked.append( + f"{pkg['name']} {pkg['version']}: missing authoritative crates.io publication time" + ) elif when > cutoff: blocked.append( f"{pkg['name']} {pkg['version']}: published {when.isoformat()}, " @@ -86,7 +112,9 @@ def main() -> int: print(f"- {item}", file=sys.stderr) return 1 - print(f"Cargo release-age quarantine passed for {len(changed)} newly introduced crates.io package versions.") + print( + f"Cargo release-age quarantine passed for {len(changed)} newly introduced crates.io package versions." + ) return 0 From d6447d84db09c7c6eec6622062e93a8d5dbdd270 Mon Sep 17 00:00:00 2001 From: codeandsolder Date: Fri, 30 Oct 2026 08:05:16 +0100 Subject: [PATCH 5/5] ci: use structured-output-safe strict gate --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0c017..7b91346 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: jobs: rust-skills2-strict: name: rust-skills2 strict nightly gate - uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@94a3b140fac9fc0b2d413871c247951afc660eff # main + uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@cc82bd6c31d8f9e5e6e2a3cc9d2f00663bd57e15 # main lockfile: name: Lockfile consistency