From 1ec8a78b396a68bce7abf8db02fb2e294a855d1c Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Fri, 24 Apr 2026 09:01:26 +1000 Subject: [PATCH 1/8] test: align observability tests with current header injection behavior _inject_metrics_headers(None) returns {'X-CacheKit-L1-Status': 'disabled'} since #64, but 3 tests still expected {}. Session ID uniqueness test used default function_identifier for all instances, producing identical IDs. --- tests/integration/test_saas_observability.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_saas_observability.py b/tests/integration/test_saas_observability.py index 2d0dcd1..af3dff8 100644 --- a/tests/integration/test_saas_observability.py +++ b/tests/integration/test_saas_observability.py @@ -103,8 +103,8 @@ def test_graceful_degradation_none_stats(self): """Verify graceful degradation when stats is None.""" headers = _inject_metrics_headers(None) - # Should return empty dict, not raise error - assert headers == {} + # Should return L1-Status: disabled for standalone usage, not raise error + assert headers == {"X-CacheKit-L1-Status": "disabled"} assert isinstance(headers, dict) @@ -257,9 +257,9 @@ def test_backend_receives_metrics_headers_on_put(self): def test_backend_graceful_handling_missing_stats(self): """Verify backend gracefully handles missing stats.""" - # When stats is None, should return empty dict, not raise + # When stats is None, should return L1-Status: disabled for standalone usage headers = _inject_metrics_headers(None) - assert headers == {} + assert headers == {"X-CacheKit-L1-Status": "disabled"} assert isinstance(headers, dict) def test_metrics_headers_immutable_after_call(self): @@ -372,9 +372,9 @@ def test_session_unique_per_stats_instance(self): multiple users each decorate the same function). Without per-instance session IDs, different wrappers would collide and cause 'counters_decreased' validation errors. """ - stats1 = _FunctionStats() - stats2 = _FunctionStats() - stats3 = _FunctionStats() + stats1 = _FunctionStats(function_identifier="module.func_a") + stats2 = _FunctionStats(function_identifier="module.func_b") + stats3 = _FunctionStats(function_identifier="module.func_c") headers1 = _inject_metrics_headers(stats1) headers2 = _inject_metrics_headers(stats2) @@ -494,8 +494,8 @@ def test_inject_metrics_headers_signature(self): # Should have single parameter 'stats' assert "stats" in params - # Should accept None - assert _inject_metrics_headers(None) == {} + # Should accept None and return L1-Status: disabled + assert _inject_metrics_headers(None) == {"X-CacheKit-L1-Status": "disabled"} def test_cache_info_all_fields_present(self): """Verify CacheInfo has all expected fields.""" From 13513115c3285747b160eef26b444e4dfa77f5e6 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Fri, 24 Apr 2026 09:32:06 +1000 Subject: [PATCH 2/8] test: move saas_observability tests from integration/ to unit/ Every test in this file exercises pure Python functions (_inject_metrics_headers, _FunctionStats, get_session_id) with zero Redis or network I/O. Placing them in integration/ forced a pytest-redis dependency for no reason. --- tests/{integration => unit}/test_saas_observability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{integration => unit}/test_saas_observability.py (99%) diff --git a/tests/integration/test_saas_observability.py b/tests/unit/test_saas_observability.py similarity index 99% rename from tests/integration/test_saas_observability.py rename to tests/unit/test_saas_observability.py index af3dff8..b155d2d 100644 --- a/tests/integration/test_saas_observability.py +++ b/tests/unit/test_saas_observability.py @@ -1,4 +1,4 @@ -"""Integration tests for SaaS observability - headers injection end-to-end.""" +"""Unit tests for SaaS observability - header injection and session ID logic.""" from __future__ import annotations From 374bc18329b2b7f1c19003bea3d3ed9b6b7c4d23 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 08:01:59 +1000 Subject: [PATCH 3/8] ci: fix post-merge doc tests and Kani SHA-pinning failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mark redis.md and encryption.md examples as notest (require DI/env setup the doc test harness can't provide) - Replace kani-github-action composite action with direct install on self-hosted runner — upstream action uses unpinned dtolnay/rust-toolchain and actions/checkout refs that violate org SHA-pinning policy --- .github/workflows/security-deep.yml | 14 +++++++++----- docs/backends/redis.md | 2 +- docs/serializers/encryption.md | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index 15ec3a4..c8c81aa 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -16,16 +16,20 @@ jobs: # Deep security analysis (< 2 hours) - nightly scheduled kani-verification: name: Kani Formal Verification - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 30 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install Kani + run: | + cargo +stable install --locked kani-verifier + cargo-kani setup + - name: Run Kani verification - uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1 - with: - working-directory: rust - args: --tests --no-default-features --features compression,checksum,messagepack,encryption + run: | + cd rust + cargo-kani --tests --no-default-features --features compression,checksum,messagepack,encryption fuzzing: name: Extended Fuzzing (3 targets × 1h) diff --git a/docs/backends/redis.md b/docs/backends/redis.md index 416e574..ffbcad7 100644 --- a/docs/backends/redis.md +++ b/docs/backends/redis.md @@ -6,7 +6,7 @@ The default L2 backend. Connects to Redis via environment variable or explicit c ## Basic Usage -```python +```python notest from cachekit.backends import RedisBackend from cachekit import cache diff --git a/docs/serializers/encryption.md b/docs/serializers/encryption.md index b5d92ce..c8705b2 100644 --- a/docs/serializers/encryption.md +++ b/docs/serializers/encryption.md @@ -19,7 +19,7 @@ The backend stores opaque ciphertext only. The master key never leaves the clien ## Basic Usage -```python +```python notest from cachekit import cache from cachekit.serializers import EncryptionWrapper, OrjsonSerializer From 4c96a303a91fdf7445fce5c574258b44de226d14 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 08:06:35 +1000 Subject: [PATCH 4/8] ci: migrate all workflows to self-hosted runners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all jobs to cachekit runners except summary gates and release-please (needs cross-platform matrix). Remove dtolnay/rust-toolchain actions, Swatinem/rust-cache, actions/cache, and astral-sh/setup-uv — all pre-installed on self-hosted infra. Replaces Kani composite action with direct install to avoid upstream unpinned action refs. --- .github/workflows/codeql.yml | 2 +- .github/workflows/fuzz-smoke.yml | 14 +++----- .github/workflows/security-deep.yml | 52 +++------------------------ .github/workflows/security-fast.yml | 42 +++------------------- .github/workflows/security-medium.yml | 14 ++------ 5 files changed, 17 insertions(+), 107 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6b57c7b..4854cbf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,7 +35,7 @@ concurrency: jobs: analyze: name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 30 strategy: diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 7151be2..564f47a 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -22,22 +22,16 @@ concurrency: jobs: fuzz-smoke: name: Fuzz Smoke Test (60s per target) - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust nightly - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # nightly - with: - toolchain: nightly - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust/fuzz - cache-all-crates: true + run: | + rustup toolchain install nightly + rustup default nightly - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@18470a17439d5a7ec5f5ab40c95a6f0b217e652e # main diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index c8c81aa..66617bd 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -33,7 +33,7 @@ jobs: fuzzing: name: Extended Fuzzing (3 targets × 1h) - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 200 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -43,11 +43,6 @@ jobs: rustup toolchain install nightly rustup default nightly - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@18470a17439d5a7ec5f5ab40c95a6f0b217e652e # main @@ -90,37 +85,11 @@ jobs: atheris-fuzzing: name: Atheris Python-Rust Fuzzing - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 60 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install uv - uses: astral-sh/setup-uv@8d55fbecc275b1c35dbe060458839f8d30439ccf # v3 - with: - enable-cache: true - - - name: Set up Python - run: uv python install 3.11 - - - name: Set up Rust - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable - with: - toolchain: stable - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - - - name: Cache Python virtual environment - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 - with: - path: .venv - key: venv-${{ runner.os }}-py3.11-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }} - restore-keys: | - venv-${{ runner.os }}-py3.11- - - name: Install dependencies run: | uv sync --group dev --group fuzz @@ -153,7 +122,7 @@ jobs: miri-full: name: Miri Full Suite - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 30 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -163,11 +132,6 @@ jobs: rustup toolchain install nightly --component miri rustup default nightly - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - - name: Run full Miri test suite run: | cd rust @@ -175,7 +139,7 @@ jobs: sanitizers: name: Sanitizers (ASan, TSan, MSan) - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 40 strategy: fail-fast: false @@ -193,12 +157,6 @@ jobs: if: matrix.sanitizer == 'memory' || matrix.sanitizer == 'thread' run: rustup component add rust-src --toolchain nightly - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - key: ${{ matrix.sanitizer }} - - name: Run AddressSanitizer if: matrix.sanitizer == 'address' env: @@ -229,7 +187,7 @@ jobs: generate-security-report: name: Generate Security Report - runs-on: ubuntu-latest + runs-on: cachekit needs: [kani-verification, fuzzing, atheris-fuzzing, miri-full, sanitizers] if: always() steps: diff --git a/.github/workflows/security-fast.yml b/.github/workflows/security-fast.yml index 23fade4..7479dd7 100644 --- a/.github/workflows/security-fast.yml +++ b/.github/workflows/security-fast.yml @@ -24,7 +24,7 @@ jobs: # Fast security checks (< 3 min) - parallel execution cargo-audit: name: Vulnerability Scan - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -35,7 +35,7 @@ jobs: cargo-deny: name: License & Supply Chain - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -44,22 +44,11 @@ jobs: clippy-security: name: Security Lints - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Rust - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable - with: - toolchain: stable - components: clippy - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - - name: Run Clippy security lints run: | cd rust @@ -68,16 +57,11 @@ jobs: cargo-machete: name: Unused Dependencies - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Rust - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable - with: - toolchain: stable - - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@18470a17439d5a7ec5f5ab40c95a6f0b217e652e # main @@ -91,27 +75,11 @@ jobs: pip-audit: name: Python Dependency CVEs - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install uv - uses: astral-sh/setup-uv@8d55fbecc275b1c35dbe060458839f8d30439ccf # v3 - with: - enable-cache: true - - - name: Set up Python - run: uv python install 3.12 - - - name: Cache Python virtual environment - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 - with: - path: .venv - key: venv-${{ runner.os }}-py3.12-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }} - restore-keys: | - venv-${{ runner.os }}-py3.12- - - name: Install dependencies run: | uv sync --group dev diff --git a/.github/workflows/security-medium.yml b/.github/workflows/security-medium.yml index 692d216..94fccd8 100644 --- a/.github/workflows/security-medium.yml +++ b/.github/workflows/security-medium.yml @@ -20,16 +20,11 @@ jobs: # Medium security checks (< 15 min) - post-merge validation cargo-geiger: name: Unsafe Code Tracking - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Rust - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable - with: - toolchain: stable - - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@18470a17439d5a7ec5f5ab40c95a6f0b217e652e # main @@ -76,7 +71,7 @@ jobs: miri-subset: name: Miri UB Detection (Subset) - runs-on: ubuntu-latest + runs-on: cachekit timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -86,11 +81,6 @@ jobs: rustup toolchain install nightly --component miri rustup default nightly - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - workspaces: rust - - name: Run Miri on byte_storage module run: | cd rust From e9d4d2023f3e26c501017cf0e11d5b24369763ef Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 08:11:29 +1000 Subject: [PATCH 5/8] ci: fix cargo-deny Docker mount + pip CVE ignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo-deny-action uses Docker which breaks on self-hosted workspace paths — replace with direct cargo-deny install. Add pip GHSA-58qw-9mgm-455v ignore (pip tar/zip confusion, no fix available). --- .github/workflows/ci.yml | 7 ++++++- .github/workflows/security-fast.yml | 10 ++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fbca17..34928f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,7 +165,12 @@ jobs: run: uv sync --python ${{ env.DEFAULT_PYTHON_VERSION }} --group dev - name: Scan Python dependencies for CVEs - run: uv run pip-audit --desc + run: | + # GHSA-5239-wwwm-4pmq: pygments ReDoS in AdlLexer (dev-only, no fix available) + # GHSA-58qw-9mgm-455v: pip tar/zip confusion (pip itself, no fix available) + uv run pip-audit --desc \ + --ignore-vuln GHSA-5239-wwwm-4pmq \ + --ignore-vuln GHSA-58qw-9mgm-455v - name: Run markdown documentation tests run: make test-docs-examples diff --git a/.github/workflows/security-fast.yml b/.github/workflows/security-fast.yml index 7479dd7..2aa4d0a 100644 --- a/.github/workflows/security-fast.yml +++ b/.github/workflows/security-fast.yml @@ -40,7 +40,11 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2 + - name: Install cargo-deny + run: cargo install --locked cargo-deny + + - name: Run cargo-deny + run: cargo deny --all-features check clippy-security: name: Security Lints @@ -87,8 +91,10 @@ jobs: - name: Run pip-audit run: | # GHSA-5239-wwwm-4pmq: pygments ReDoS in AdlLexer (dev-only, no fix available) + # GHSA-58qw-9mgm-455v: pip tar/zip confusion (pip itself, no fix available) uv run pip-audit --desc --format json --output pip-audit-report.json \ - --ignore-vuln GHSA-5239-wwwm-4pmq + --ignore-vuln GHSA-5239-wwwm-4pmq \ + --ignore-vuln GHSA-58qw-9mgm-455v - name: Upload report if: always() From bfdc58d8d838983519593eda73c14b790065bd3e Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 08:36:07 +1000 Subject: [PATCH 6/8] ci: address review feedback - Pin kani-verifier to 0.67.0 for reproducible nightly runs - Ensure stable toolchain before cargo +stable install - Drop rustup default nightly in fuzz-smoke (already uses cargo +nightly) - Rename test to test_session_unique_per_function_identifier (matches behavior) - Add notest reason comments to redis.md and encryption.md --- .github/workflows/fuzz-smoke.yml | 4 +--- .github/workflows/security-deep.yml | 5 ++++- docs/backends/redis.md | 1 + docs/serializers/encryption.md | 1 + tests/unit/test_saas_observability.py | 10 +++++----- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/fuzz-smoke.yml b/.github/workflows/fuzz-smoke.yml index 564f47a..2de85fc 100644 --- a/.github/workflows/fuzz-smoke.yml +++ b/.github/workflows/fuzz-smoke.yml @@ -29,9 +29,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust nightly - run: | - rustup toolchain install nightly - rustup default nightly + run: rustup toolchain install nightly - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@18470a17439d5a7ec5f5ab40c95a6f0b217e652e # main diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index 66617bd..d10fc48 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -21,9 +21,12 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Ensure stable toolchain + run: rustup toolchain install stable + - name: Install Kani run: | - cargo +stable install --locked kani-verifier + cargo +stable install --locked kani-verifier --version 0.67.0 cargo-kani setup - name: Run Kani verification diff --git a/docs/backends/redis.md b/docs/backends/redis.md index ffbcad7..64107e4 100644 --- a/docs/backends/redis.md +++ b/docs/backends/redis.md @@ -7,6 +7,7 @@ The default L2 backend. Connects to Redis via environment variable or explicit c ## Basic Usage ```python notest +# notest: RedisBackend() requires DI container setup not available in doc tests from cachekit.backends import RedisBackend from cachekit import cache diff --git a/docs/serializers/encryption.md b/docs/serializers/encryption.md index c8705b2..988ea7c 100644 --- a/docs/serializers/encryption.md +++ b/docs/serializers/encryption.md @@ -20,6 +20,7 @@ The backend stores opaque ciphertext only. The master key never leaves the clien ## Basic Usage ```python notest +# notest: @cache.secure validation requires CACHEKIT_MASTER_KEY before conftest runs from cachekit import cache from cachekit.serializers import EncryptionWrapper, OrjsonSerializer diff --git a/tests/unit/test_saas_observability.py b/tests/unit/test_saas_observability.py index b155d2d..6eedafc 100644 --- a/tests/unit/test_saas_observability.py +++ b/tests/unit/test_saas_observability.py @@ -365,12 +365,12 @@ def test_hit_rate_precision_rounding(self): # Should round to 0.333 assert headers["X-CacheKit-L1-Hit-Rate"] == "0.333" - def test_session_unique_per_stats_instance(self): - """Verify each _FunctionStats instance has its own unique session ID. + def test_session_unique_per_function_identifier(self): + """Verify distinct function_identifiers produce distinct session IDs. - This is critical for multi-wrapper scenarios (e.g., Locust load testing where - multiple users each decorate the same function). Without per-instance session IDs, - different wrappers would collide and cause 'counters_decreased' validation errors. + Session IDs are composed as "{process_uuid}:{function_identifier}", so uniqueness + is per function_identifier. This matters for multi-wrapper scenarios where different + decorated functions must not share session state. """ stats1 = _FunctionStats(function_identifier="module.func_a") stats2 = _FunctionStats(function_identifier="module.func_b") From 6e04ed79710baf2359878f64e781601a7767873d Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 08:40:17 +1000 Subject: [PATCH 7/8] ci: shift nightly schedules to AEDT overnight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security Deep: 2:00 UTC → 20:00 UTC (7 AM AEDT, results ready by morning) CodeQL: 3:00 UTC Sunday → 20:00 UTC Sunday (7 AM AEDT Monday) --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/security-deep.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4854cbf..9596c58 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,8 +17,8 @@ on: pull_request: branches: [main] schedule: - # Weekly deep scan on Sunday at 3am UTC - - cron: "0 3 * * 0" + # Weekly deep scan: 20:00 UTC Sunday = 7 AM AEDT Monday + - cron: "0 20 * * 0" workflow_dispatch: # Manual trigger for ad-hoc security analysis diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index d10fc48..5ca3be9 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -2,7 +2,7 @@ name: Security Deep on: schedule: - - cron: '0 2 * * *' # 2 AM daily + - cron: '0 20 * * *' # 20:00 UTC = 7 AM AEDT next day workflow_dispatch: # Allow manual triggers permissions: From c9f3840751220ae64e96a487b54dac960ee71d19 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Thu, 9 Apr 2026 07:19:54 +1000 Subject: [PATCH 8/8] test: fix flaky L1-only secure test missing CACHEKIT_MASTER_KEY validate_encryption_config() checks the env var independently of the inline master_key param passed to @cache.secure. The test was flaky (68% fail rate on main) because it depended on whether CACHEKIT_MASTER_KEY happened to be set from a prior test's singleton cache. --- tests/unit/test_l1_only_mode.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_l1_only_mode.py b/tests/unit/test_l1_only_mode.py index 08db76f..e681796 100644 --- a/tests/unit/test_l1_only_mode.py +++ b/tests/unit/test_l1_only_mode.py @@ -14,6 +14,7 @@ from __future__ import annotations +import os import time from unittest.mock import MagicMock, patch @@ -229,17 +230,27 @@ def production_func() -> str: assert production_call_count == 1, f"@cache.production L1 miss - called {production_call_count} times" # Test @cache.secure(master_key="...", backend=None) + # validate_encryption_config() checks CACHEKIT_MASTER_KEY env var + # independently of the inline master_key param, so we must set it. secure_call_count = 0 - - @cache.secure(master_key="a" * 64, backend=None) - def secure_func() -> str: - nonlocal secure_call_count - secure_call_count += 1 - return "secure" - - assert secure_func() == "secure" - assert secure_func() == "secure" - assert secure_call_count == 1, f"@cache.secure L1 miss - called {secure_call_count} times" + old_key = os.environ.get("CACHEKIT_MASTER_KEY") + os.environ["CACHEKIT_MASTER_KEY"] = "a" * 64 + try: + + @cache.secure(master_key="a" * 64, backend=None) + def secure_func() -> str: + nonlocal secure_call_count + secure_call_count += 1 + return "secure" + + assert secure_func() == "secure" + assert secure_func() == "secure" + assert secure_call_count == 1, f"@cache.secure L1 miss - called {secure_call_count} times" + finally: + if old_key is None: + os.environ.pop("CACHEKIT_MASTER_KEY", None) + else: + os.environ["CACHEKIT_MASTER_KEY"] = old_key # Backend provider should NEVER have been called for any preset mock_provider.return_value.get_backend.assert_not_called()