Skip to content

fix: L1-only mode (backend=None) should not attempt Redis connection - #6

Merged
27Bslash6 merged 3 commits into
mainfrom
fix/l1-only-mode-no-redis
Dec 11, 2025
Merged

27Bslash6 merged 3 commits into
mainfrom
fix/l1-only-mode-no-redis

Conversation

@27Bslash6

Copy link
Copy Markdown
Contributor

Summary

Fixes #3

When using @cache(backend=None) for L1-only caching, the decorator was still attempting to connect to Redis on every operation.

Root Cause

Sentinel problem - couldn't distinguish explicit backend=None from unspecified backend.

Changes

  • Track explicit backend=None intent via _l1_only_mode flag in intent.py
  • Skip backend provider lookup in L1-only mode in wrapper.py
  • Fix invalidate_cache() and ainvalidate_cache() to honor L1-only mode
  • Add DEBUG log when L1-only mode ignores available Redis (helps debugging)
  • Clean up ttl or ttl copy-paste cruft (8 instances)

Test Plan

  • New tests in tests/unit/test_l1_only_mode.py (10 tests)
  • Tests for sync and async L1-only mode
  • Tests for invalidate_cache() in L1-only mode
  • Tests verify backend provider is never called
  • All existing tests pass

Files Changed

  • src/cachekit/decorators/intent.py - Track explicit L1-only intent
  • src/cachekit/decorators/wrapper.py - Honor L1-only flag, add DEBUG log
  • tests/unit/test_l1_only_mode.py - New comprehensive test suite

When using @cache(backend=None) for L1-only caching, the decorator
was still attempting to connect to Redis on every operation.

Root cause: Sentinel problem - couldn't distinguish explicit backend=None
from unspecified backend.

Changes:
- Track explicit backend=None intent via _l1_only_mode flag
- Skip backend provider lookup in L1-only mode
- Fix invalidate_cache() to honor L1-only mode
- Add DEBUG log when L1-only mode ignores available Redis
- Clean up ttl or ttl copy-paste cruft

Closes #3
… level

Remove buggy check that treated DecoratorConfig.backend defaulting to
None as L1-only mode. Now only @cache(backend=None) triggers L1-only.

For L1-only with presets, use: @cache(backend=None, config=...)
Adds TestDefaultBackendBehavior class with 6 tests verifying that:
- @cache() without backend=None DOES call get_backend_provider()
- @cache.minimal/dev/test() presets behave correctly
- DecoratorConfig default (backend=None) != explicit backend=None
- Explicit backend instances bypass provider lookup

These regression tests protect against the bug where checking
`config.backend is None` would treat ALL decorators as L1-only
(since DecoratorConfig.backend defaults to None).
@27Bslash6
27Bslash6 force-pushed the fix/l1-only-mode-no-redis branch from 47f3b46 to 872fe23 Compare December 11, 2025 21:55
@27Bslash6
27Bslash6 merged commit 0898986 into main Dec 11, 2025
18 checks passed
27Bslash6 added a commit that referenced this pull request Sep 1, 2026
…LAB-1149) (#263)

* fix(fuzz): commit per-target corpus seeds cargo-fuzz actually loads (LAB-1149)

The fuzz corpus was buried by three stacked defects: the gitignore pattern
corpus/*/ excluded every seed the generator produced; the committed
CORPUS_INFO.md documented 1,758 seeds (6.9MB, 2025-11) that were not in the
repo; and the generator wrote a category tree (byte_storage/, encryption/)
that no [[bin]] target name matches, so cargo-fuzz could never have loaded
those seeds even if committed. Every run cold-started from random bytes.

- generate_corpus.sh: rewritten to write deterministic seeds into
  corpus/<target>/ for all 14 targets, shaped per target input format.
  StorageEnvelope seeds are byte-exact against cachekit-core 0.4.0's wire
  format (raw LZ4 block, xxHash3-64 big-endian checksum, rmp-serde array
  form), verified against the crate's pinned empty-input checksum vector.
- .gitignore: corpus is no longer ignored; comment states the actual policy.
- CORPUS_INFO.md: describes the real tree; no volatile counts (live numbers
  come from validate_corpus.sh).
- validate_corpus.sh: derives targets from Cargo.toml [[bin]] stanzas and
  FAILS when any target has no seeds or the corpus exceeds the 10MB budget.
- minimize_corpus.sh: target list derived from Cargo.toml instead of a
  hand-maintained copy; a failed cmin now fails the script.
- README.md: corpus sections match the per-target layout; stale Blake3 and
  FUZZ_TARGETS-list references corrected.
- .pre-commit-config.yaml: corpus excluded from trailing-whitespace and
  end-of-file-fixer - appending a newline to a seed silently changes it.

No CI change needed: cargo fuzz run defaults its corpus to fuzz/corpus/
<target>, so fuzz-smoke.yml and security-deep.yml pick the seeds up as-is.

Measured (60s, nightly-2026-04-27, -print_final_stats=1):
- encryption_large_payload cold: cov 185, ft 194, max input len 53 bytes
  after 5,376 execs. Seeded (5 files found): ft 300, 256KB inputs from
  exec #6 - the target finally tests large payloads.
- byte_storage_decompress cold: cov 2156, ft 3492. Seeded (14 files found):
  cov 2420, ft 4110 - valid-checksum envelopes unlock the
  post-decompression verify branches.

* fix(fuzz): apply expert-panel findings to corpus tooling (LAB-1149)

Panel: bug-hunter-supreme, security-specialist, code-craftsman,
catchphrase-agent (high stakes). Security: no findings. Applied:

- fuzz-smoke.yml: validate corpus layout before the build - without this
  step the every-target-has-seeds guarantee was manual-only, and a target
  added tomorrow would fuzz cold forever behind a green badge. The step can
  only fail loudly; no existing guarantee weakened.
- validate_corpus.sh: assert grep-derived target count matches
  fuzz_targets/*.rs (a [[bin]] stanza whose name line drifts from the grep
  pattern would silently stop requiring seeds); drop dead .gitkeep filters,
  the 8MB soft tier, and the --help block.
- minimize_corpus.sh: reject unknown target names (a typo'd target
  previously printed 'Skipping' and exited 0); --help before env checks;
  drop the duplicate 10MB check (validate_corpus.sh is the single budget
  enforcer) and dead .gitkeep filters.
- rust/fuzz/Makefile: fail quick/deep when FUZZ_TARGETS derives empty - a
  zero-target loop reported '✓ No crashes found' having fuzzed nothing.
- README.md: fix Quick Start commands (make fuzz-* lives at the repo root,
  not rust/Makefile); delete the AFL++ section and multi-engine claims (no
  afl feature, no fuzz-afl target - doc fiction); honest large-payload
  numbers; corpus section now points at CORPUS_INFO.md instead of
  duplicating it.
- generate_corpus.sh: drop 10 filler seeds (key-derivation content variants
  the target already hard-loops every exec, redundant large-payload sizes,
  trivial invalid-msgpack bytes) - 72 seeds, 696K total.

Re-measured with the final corpus (60s, nightly-2026-04-27):
- encryption_large_payload cold: cov 185, ft 194, lim 53b, 5,553 execs.
  Seeded (3 files): ft 300, 256KB inputs.
- byte_storage_decompress cold: cov 2358, ft 3770. Seeded (13 files):
  cov 2423, ft 3968.

* fix: address coderabbit review — corpus tooling correctness (LAB-1149)

Parity check (Kody medium + CodeRabbit): the old check compared a count of
Cargo.toml [[bin]] names against a count of every *.rs in fuzz_targets/. That
was both too strict and too loose — a shared helper module in that directory
failed the whole CI step unconditionally, while a duplicated or misspelled
name kept the counts equal and slipped through. Neither bot's suggested
name-set comparison fixes the helper-module case, so the check now asserts the
invariant the comment actually claimed: every [[bin]] stanza must yield exactly
one name (stanza count == derived name count), names must be unique, and each
must have a fuzz_targets/<name>.rs source. Extra .rs files are now tolerated.

minimize_corpus.sh: mapfile is bash 4+, so the script died immediately on
stock macOS bash 3.2 — replaced with a portable read loop. `cargo fuzz cmin`
needed +nightly like every other cargo-fuzz call in the Makefile; it failed
outright on a stable default toolchain. SC2155 split on all four locals.

generate_corpus.sh: regeneration was not idempotent — a renamed or dropped
seed left its old file committed forever, invisible to a validator that checks
presence and size but not provenance. First write per target now clears the
*.bin names the script owns, leaving libFuzzer's extensionless hash-named
discoveries and committed crash reproducers untouched. The byte-identical
claim is scoped to a pinned dependency set (msgpack 1.2.1, lz4 4.4.5, xxhash
4.0.0 — verified to reproduce corpus/ with a clean git status), since the LZ4
block format constrains decoding, not encoder match-finding.

fuzz-smoke.yml: corpus validation moved ahead of the toolchain install. It is
pure bash over files present at checkout, so a missing seed directory now
fails in seconds instead of after ~12 min of rustup + cargo-fuzz install.

Makefile: the byte-identical zero-target guard in quick and deep extracted
into a require_targets define, matching the file's existing require_binary /
warn_if_missing idiom.

Docs: MD040 fence language and the missing working directory in
CORPUS_INFO.md (its commands resolve from rust/fuzz/, one level up from the
file); non-copy-pasteable `make fuzz-*` and `make quick|target|deep|coverage`
forms in README.md replaced with the real goal names; MD022/MD031 blank lines
added at the three flagged sections.

Verified: shellcheck clean on all three scripts; 8/8 checks in a throwaway
harness covering each parity failure mode, the helper-module regression, seed
orphan removal, discovery preservation, and byte-identical regeneration.

Rejected, with reasons on the PR: Kody's print-vs-logging rule (fires on a
progress line in an inline heredoc in a one-shot generator whose entire
interface is stdout) and CodeRabbit's request to extract the target-derivation
grep into a shared helper (the expert panel already rejected this; the
fragility it cites is now detected loudly rather than silent).

CodeRabbit-Resolved: rust/fuzz/scripts/validate_corpus.sh:43:Compare target na
CodeRabbit-Resolved: rust/fuzz/scripts/validate_corpus.sh:31:One fragile targe
CodeRabbit-Resolved: rust/fuzz/scripts/minimize_corpus.sh:36:mapfile requires
CodeRabbit-Resolved: rust/fuzz/scripts/minimize_corpus.sh:54:Split the local d
CodeRabbit-Resolved: rust/fuzz/scripts/minimize_corpus.sh:71:Run corpus minimi
CodeRabbit-Resolved: rust/fuzz/scripts/generate_corpus.sh:62:Generation does n
CodeRabbit-Resolved: rust/fuzz/scripts/generate_corpus.sh:15:Pin the corpus-ge
CodeRabbit-Resolved: .github/workflows/fuzz-smoke.yml:71:Move the corpus valid
CodeRabbit-Resolved: rust/fuzz/corpus/CORPUS_INFO.md:9:Add a language to the f
CodeRabbit-Resolved: rust/fuzz/Makefile:71:Extract the duplicated zero-target
CodeRabbit-Resolved: rust/fuzz/README.md:25:Make these commands copy-paste saf
CodeRabbit-Resolved: rust/fuzz/README.md:118:Add the blank lines required by m

* fix: address coderabbit review — working-dir docs + parity-filter coherence (LAB-1149)

Round 3. CodeRabbit: the README's uv one-liner invoked
scripts/generate_corpus.sh from the repo root where that path does not
resolve, and CORPUS_INFO.md said 'run from rust/fuzz/' above blocks whose
first line is 'cd rust/fuzz' (only correct from the root). One convention
now, both files: commands run from the repository root and the cd is part
of the recipe.

fuzz-smoke.yml source-parity check: filter SRC_COUNT to files containing
fuzz_target!. validate_corpus.sh (previous commit) deliberately tolerates
a shared helper module in fuzz_targets/, but the workflow's unfiltered
*.rs count would fail the job on the same tree that just passed
validation — 12 minutes after the fast-fail step, with a misleading 'add
the missing stanza' error. The filtered count still catches the real
drift (a fuzz_target! source with no [[bin]] stanza).

Kody's base-image-digest rule fired on CORPUS_INFO.md line 19 — a
markdown doc with no container images; rejected on the thread.

CodeRabbit-Resolved: rust/fuzz/README.md:121:working-directory consistency
CodeRabbit-Resolved: rust/fuzz/corpus/CORPUS_INFO.md:18:working-directory

---------

Co-authored-by: Mark S <ray.geo30@insighttimer.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: @cache(backend=None) L1-only mode attempts Redis connection

1 participant