Skip to content

feat(backend): Redis lock + Workers lock/TTL capability parity (LAB-426) - #37

Merged
27Bslash6 merged 4 commits into
mainfrom
lab-426-backend-capability-parity
Jul 23, 2026
Merged

feat(backend): Redis lock + Workers lock/TTL capability parity (LAB-426)#37
27Bslash6 merged 4 commits into
mainfrom
lab-426-backend-capability-parity

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes LAB-426. Backend capability parity per the LAB-273 audit: Redis gains distributed locking; the Workers backend gains locking + TTL inspection against the SaaS it already speaks.

What

  • impl LockableBackend for RedisBackend (redis.rs) — SET NX PX acquire returning a generated UUID capability token; release via atomic Lua compare-and-delete (only the token holder can release, and expiry races can't delete someone else's lock). Honours the bare-cache-key contract: callers pass the bare key, the backend derives <key>:lock on the wire — byte-identical to cachekit-py's Redis lock namespace, so py and rs workloads contend on the same lock. Requires fred's i-scripts feature for EVAL (added to the feature activation, default fred features unchanged).
  • impl LockableBackend for WorkersCachekitIO (workers.rs) — same SaaS lock endpoint as the native CachekitIO impl. The lock token travels in the X-CacheKit-Lock-Id header, never ?lock_id= (CWE-532). Contested acquire is 200 {"lock_id": null} — branched on the body, never on a 409 status (protocol#22 / LAB-240 canonical shape).
  • impl TtlInspectable for WorkersCachekitIO — same GET/PATCH /v1/cache/{key}/ttl endpoints as the native impl, including the refresh_ttl zero-seconds guard and 404→None/false mapping.
  • workers.rs fetch() learns POST/PATCH, plus an error_from_response helper mirroring the native backend's.
  • workers feature now activates dep:serde_json (previously only pulled in via cachekitio).

Wire-contract enforcement (LAB-411 guard)

The SaaS lock/TTL JSON bodies live in a new backend/saas_wire.rs, compiled under cfg(test) unconditionally so its tests run in the default native CI suite even though only the workers backend consumes the structs at runtime. Tests pin literal wire JSON ({"timeout_ms":5000}, {"lock_id":null}, {"ttl":3542}) rather than round-tripping structs with themselves, and include an explicit camelCase tripwire ({"lockId":"x"} must fail to parse). This does not touch cachekitio_lock.rs — the standalone LAB-411 fix stays in #34.

Tests

  • 8 new unit tests (wire round-trips, <key>:lock derivation, dyn LockableBackend compile proofs) — run in default CI.
  • redis_lock_live_semantics integration test, env-gated on CACHEKIT_TEST_REDIS_URL (CI has no Redis service; skips with a notice). Proven locally against a real redis-server: acquire → token stored at <key>:lock; contested acquire → None; foreign-token release refused; holder release → true; double release → false; reacquire after release; timeout_ms TTL self-expiry.

Verification matrix (all local, all green)

  • cargo test --features "cachekitio,redis,encryption,l1,macros" (CI-equivalent) — plus the same with a live Redis
  • cargo clippy --all-targets --features "cachekitio,redis,encryption,l1,macros" -- -D warnings
  • cargo check --target wasm32-unknown-unknown --features workers,encryption --no-default-features (CI wasm job)
  • cargo check with --no-default-features, default, redis, unsync, unsync,cachekitio,redis
  • cargo fmt --all -- --check

Docs

README backend sections updated (Redis + Workers capability claims, project layout). Companion PR on protocol flips the rs Redis-lock / Workers-lock / Workers-TTL matrix cells — that PR should merge only after (or with) this one.

Summary by CodeRabbit

  • New Features

    • Added distributed locking support for Redis and Cloudflare Workers backends.
    • Added TTL inspection and refresh support for Cloudflare Workers.
    • Improved safe lock release and lock ownership handling.
  • Bug Fixes

    • Improved Workers backend error reporting for unexpected HTTP responses.
  • Documentation

    • Expanded backend documentation covering locking, TTL behaviour and architecture.
  • Tests

    • Added integration coverage for Redis lock lifecycle and semantics.

…y impls

Backend capability parity per the LAB-273 audit:

- RedisBackend gains LockableBackend: SET NX PX acquire with a UUID
  capability token, atomic Lua compare-and-delete release. Callers pass
  the bare cache key; the backend derives <key>:lock on the wire,
  byte-identical to cachekit-py's Redis lock namespace so py and rs
  workloads contend on the same lock. Needs fred's i-scripts feature
  for EVAL.
- WorkersCachekitIO gains LockableBackend + TtlInspectable against the
  same SaaS endpoints the native CachekitIO impls call. The lock token
  travels in X-CacheKit-Lock-Id, never the query string (CWE-532).
  Contested acquire is 200 {"lock_id": null} — branched on the body,
  never on a 409 status (protocol#22 / LAB-240).
- SaaS lock/TTL JSON bodies live in backend/saas_wire.rs with literal
  wire-JSON round-trip tests compiled under cfg(test) unconditionally,
  so the snake_case contract is enforced in native CI (guards against
  the LAB-411 camelCase serde regression).
- Live Redis lock semantics covered by an env-gated integration test
  (CACHEKIT_TEST_REDIS_URL): acquire, contested None, foreign-token
  release refusal, double release, TTL self-expiry, wire key shape.

Co-authored-by: multica-agent <github@multica.ai>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 72e9b296-34ed-47dd-ae5e-5a0e920a0a09

📥 Commits

Reviewing files that changed from the base of the PR and between e8f9ef9 and fe79bc7.

📒 Files selected for processing (3)
  • .github/actionlint.yaml
  • .github/workflows/ci.yml
  • README.md

Walkthrough

Adds distributed locking to the Redis backend, lock and TTL operations to the Workers backend, shared SaaS JSON wire contracts, live Redis integration coverage, CI execution, and corresponding documentation.

Changes

Distributed backend capabilities

Layer / File(s) Summary
SaaS lock and TTL wire contracts
crates/cachekit/src/backend/mod.rs, crates/cachekit/src/backend/saas_wire.rs, crates/cachekit/Cargo.toml
Adds strict serde request and response types for lock acquisition and TTL operations, with JSON round-trip tests and Workers support for serde_json.
Workers lock and TTL operations
crates/cachekit/src/backend/workers.rs
Adds lock and TTL HTTP endpoints, POST/PATCH request support, shared HTTP error handling, lock-token handling, TTL inspection, and TTL refresh behaviour.
Redis distributed lock implementation
crates/cachekit/src/backend/redis.rs, crates/cachekit/Cargo.toml
Adds <key>:lock namespacing, token-based SET NX PX acquisition, atomic compare-and-delete release, and unit coverage for the trait and key format.
Live Redis semantics and CI coverage
crates/cachekit/tests/redis_tests.rs, .github/workflows/ci.yml
Tests acquisition, contention, token ownership, release, reacquisition, and expiry against Redis; CI runs the test with a Redis 7 service and rejects skipped execution.
Backend capability documentation
README.md
Documents Redis locking and TTL support, Workers parity, the shared lock namespace, and the SaaS wire module.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant WorkersCachekitIO
  participant SaaSEndpoints
  Client->>WorkersCachekitIO: Request lock or TTL operation
  WorkersCachekitIO->>SaaSEndpoints: Send JSON HTTP request
  SaaSEndpoints-->>WorkersCachekitIO: Return JSON response or status
  WorkersCachekitIO-->>Client: Return lock token, TTL, or operation result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarises the main change: Redis locking plus Workers lock/TTL parity.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lab-426-backend-capability-parity

Comment @coderabbitai help to get the list of available commands.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 1 minute.

@27Bslash6

Copy link
Copy Markdown
Contributor Author

Expert-panel review (crypto/protocol gate) — verdict: SHIP

Per the project's mandatory crypto/protocol gate (this PR derives an on-wire <key>:lock cache key + adds a SaaS wire-format module, in the py#135 / ts#70 regression class), the full expert panel ran at high stakes — bug-hunter, security, code-craftsman, catchphrase, in parallel — against the current head (3a58ffc). CI + CodeRabbit do not satisfy this gate; the panel does.

Crypto/protocol gate: SATISFIED — no surviving in-diff findings. Security returned clean after tracing every new path:

  • Lock capability token travels in the X-CacheKit-Lock-Id header only, never a URL/query string (CWE-532) — verified in both redis.rs and workers.rs.
  • Token is a CSPRNG uuid::Uuid::new_v4() (122-bit, unguessable); server-minted on the Workers path.
  • Redis release is atomic Lua compare-and-delete via KEYS[1]/ARGV[1] (no interpolation → no Lua injection); an expired holder cannot delete the next holder's lease.
  • Keys are urlencoding::encode'd before hitting SaaS URLs (no path-segment/query smuggling, no SSRF).
  • Errors route through the existing sanitize_* paths — no Redis URL / bearer key leakage.
  • Crypto is genuinely untouched: the lock holds a plaintext ephemeral token — no ciphertext, so no AES-256-GCM / AAD / HKDF / interop-v1-keygen / ByteStorage surface. The <key>:lock derivation is format!("{key}:lock"), byte-identical to cachekit-py, pinned by lock_key_derives_py_compatible_namespace. saas_wire.rs matches spec/saas-api.md ({"timeout_ms":N} in, {"lock_id":null|uuid} out), with a snake_case round-trip + camelCase-tripwire test.

No CRIT/MAJ in-diff. Disposition of the non-blocking findings, in writing:

  1. [surfaced, out-of-diff — tracked] Native cachekitio_lock.rs still serializes camelCase. Three panelists independently noted that the default CachekitIO backend sends {"timeoutMs":…} / expects {"lockId":…} against a snake_case server — i.e. the default backend can't acquire a lock. This is pre-existing (not introduced here) and is exactly what open PR LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34 (LAB-411, lab-411-lock-wire-snake-case) fixes — I confirmed LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34 removes both rename_all="camelCase" attrs and adds snake_case round-trip tests. This PR correctly ships its own (Workers) path in snake_case rather than touching LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34's structs (avoids conflict, per the handoff). Merge-ordering for the owner: LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34 must land to fix the native default backend; it is independent of feat(backend): Redis lock + Workers lock/TTL capability parity (LAB-426) #37.
  2. [MIN, in-diff] README "same … as the native SaaS backend" wording. The claim is accurate at the capability level (Workers now implements lock + TTL like native); the only divergence is native's transient wire-casing bug above, which LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34 resolves. Left as-is rather than churning an otherwise-clean crypto-gated PR for a line that reads true once LAB-411: fix lock acquire wire contract — snake_case, not camelCase #34 merges. Flagging so it's a conscious call, not an oversight.
  3. [cut-list] redis.rs:262-272 _assert_lockable / redis_is_lockable compile-proof block. Tautological (the impl is the proof; dyn LockableBackend is used nowhere). Harmless and mirrors the identical pre-existing pattern in cachekitio_lock.rs; best reaped in the future saas_wire.rs unification follow-up rather than as a one-off here.

Panel verdict stands: clean, lean, correctly scoped. Ready for human signoff once CodeRabbit completes (a full re-review was forced — CR had only posted a rate-limit banner at open, never a substantive review).

27Bslash6 and others added 2 commits July 23, 2026 19:24
…ner (LAB-426)

Co-authored-by: multica-agent <github@multica.ai>
…rom_response

Co-authored-by: multica-agent <github@multica.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 66: Update the self-hosted-runner.labels configuration in
.github/actionlint.yaml to register the new cachekit runner label alongside the
existing cachekit-lean label, so the runs-on value in the CI workflow is
recognized.
- Line 79: Update the actions/checkout step in the CI workflow to set
persist-credentials to false, ensuring the checkout token is not retained on the
self-hosted runner; leave the existing checkout action version unchanged.
- Around line 68-77: Update the Redis service image in the workflow to use an
immutable SHA256 digest instead of the mutable redis:7 tag, while preserving the
existing Redis service configuration and health-check options.

In `@README.md`:
- Line 216: Update the README statement about the wasm32 Workers backend to
remove the claim of identical locking and TTL-inspection parity with native
SaaS; describe Workers support without asserting full parity until the shared
SaaS wire format is corrected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88319dcc-b860-46d8-9849-71ff3212a456

📥 Commits

Reviewing files that changed from the base of the PR and between ff1d490 and e8f9ef9.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • README.md
  • crates/cachekit/Cargo.toml
  • crates/cachekit/src/backend/mod.rs
  • crates/cachekit/src/backend/redis.rs
  • crates/cachekit/src/backend/saas_wire.rs
  • crates/cachekit/src/backend/workers.rs
  • crates/cachekit/tests/redis_tests.rs

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread README.md Outdated
… checkout creds, README wording

- Register cachekit-lean + cachekit self-hosted labels in
  .github/actionlint.yaml so actionlint (CodeRabbit runs it) recognises
  runs-on values.
- Pin the redis:7 service image to its OCI index digest (resolved from
  Docker Hub 2026-07-23) — matches the SHA-pinned actions posture.
- persist-credentials: false on the redis-lock job checkout; it never
  pushes and the runner is self-hosted (zizmor artipacked).
- README: describe Workers lock/TTL support without claiming parity
  with the native SaaS backend while its lock wire format is still
  camelCase-broken pending #34 (LAB-411).

CodeRabbit-Resolved: .github/workflows/ci.yml:66:Unknown cachekit runner label
CodeRabbit-Resolved: .github/workflows/ci.yml:77:Pin the redis:7 service image
CodeRabbit-Resolved: .github/workflows/ci.yml:79:Set persist-credentials false
CodeRabbit-Resolved: README.md:216:Avoid claiming full parity
Co-authored-by: multica-agent <github@multica.ai>
@27Bslash6

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@27Bslash6
27Bslash6 merged commit f6cf7b7 into main Jul 23, 2026
7 checks passed
@27Bslash6
27Bslash6 deleted the lab-426-backend-capability-parity branch July 23, 2026 11:52
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.

1 participant