Skip to content

Add stack cache tags#419

Merged
binaryfire merged 15 commits into
0.4from
feature/stack-cache-tags
Jul 6, 2026
Merged

Add stack cache tags#419
binaryfire merged 15 commits into
0.4from
feature/stack-cache-tags

Conversation

@binaryfire

@binaryfire binaryfire commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR adds any-mode cache tag support to stack cache stores, allowing a short-lived local cache layer to sit in front of Redis while still preserving tagged invalidation semantics.

For more information, see: docs/plans/2026-07-06-stack-cache-tags-and-tag-architecture-refactor.md

The implementation also refactors the tag architecture so each tag mode has abstractions that match how it actually works. All-mode remains namespace/version based. Any-mode remains index based. Stack stores expose tags only when the composed store chain can honestly support any-mode tags.

What Changed

The cache tag hierarchy is split into mode-specific classes:

  • TagSet is now the mode-neutral base.
  • NamespacedTagSet and VersionedTagSet model namespace/version semantics.
  • AnyModeTaggedCache models key-addressed any-mode semantics.
  • Redis all-mode and any-mode tagged caches now sit on the correct side of that split.

Stack cache stores now support tags and locks:

  • Stack tags are enabled only for valid any-mode compositions.
  • Non-taggable fast layers may sit above the tagged region.
  • Every store from the first taggable layer through the bottom layer must support any-mode tags.
  • Stack locks delegate to the bottom store, where shared lock state belongs.
  • Capability probes now report whether a store actually supports tags and lock flushing.

Redis tagged cache lifecycle behavior is tightened:

  • Tagged clear() no longer risks flushing an entire backing store.
  • Any-mode plain forget() removes reverse-index and tag membership entries.
  • Any-mode finite touch() keeps key TTLs and tag index TTLs coherent.
  • All-mode Redis touch() updates both the item TTL and tag sorted-set scores.
  • Any-mode tagged put() / putMany() with non-positive TTLs delete plain keys instead of leaving inconsistent state.
  • Redis tagged remember() and rememberForever() normalize enum keys before use.

Framework consumers were updated to use the new capabilities:

  • Auth's Eloquent user provider accepts any-mode stack tags instead of assuming a direct Redis tagged store.
  • JWT blacklist storage supports both all-mode and any-mode tagged caches.
  • JWT documentation now calls out the expected short stale window when a stack cache has a fast L1 layer.
  • Auth cache documentation and config examples now describe stack tag support.

Design Notes

Plain cache writes remain plain. put() and forever() do not inspect tag indexes just because a key may have previously been tagged. Tag membership is maintained by the tagged APIs; plain deletion cleans up indexes because otherwise it leaves stale membership behind.

Stack tag support intentionally requires any-mode tags. Namespace/version based all-mode tags are not safe to compose behind a fast L1 cache because invalidation changes namespaces instead of directly addressing keys.

Verification

  • composer fix
  • ./vendor/bin/phpunit --no-progress tests/Cache/CacheStackStoreTagsTest.php
  • ./vendor/bin/phpunit --no-progress tests/Cache/Redis/AnyTaggedCacheTest.php
  • ./vendor/bin/phpunit --no-progress tests/Cache/Redis/AllTaggedCacheTest.php

Summary by CodeRabbit

  • New Features

    • Added cache “touch” support to refresh TTLs, including tagged entries (with correct tag-metadata updates in Redis).
    • Enhanced tagged-cache stacks and any-mode tagging behavior, including lock flushing delegation.
    • Improved JWT blacklist caching with safer behavior for any-mode/tagged setups (direct-key support when appropriate).
  • Bug Fixes

    • Fixed/clarified tag membership cleanup and reuse safety in any-mode forget/prune flows.
    • Hardened capability checks for tagging and lock flushing; improved consistency around expired/missing entries and null/sentinel values.
  • Documentation

    • Updated cache/auth/JWT guidance for supported store capabilities, any/all tag modes, stacks, and lock/touch semantics.

binaryfire added 10 commits July 6, 2026 10:41
Record the reviewed implementation plan for stack cache tag support and the tag architecture refactor.

The plan captures the agreed design decisions, rejected alternatives, implementation phases, verification expectations, and comment guidance used for this PR. Keeping it in the repo gives future maintainers the rationale behind the larger cache changes rather than only the final code shape.
Introduce a slim abstract TagSet that only owns the store and tag names, then move namespace-based behavior into NamespacedTagSet and the generic random-id machinery into VersionedTagSet.

Redis all-mode tags now extend the namespaced branch directly, while Redis any-mode tags extend the slim base without inherited namespace methods to neutralize. This makes the tag mode distinction structural instead of relying on overrides that disable inherited behavior.

Update the Redis tag set tests to assert the new shape and remove coverage for methods that no longer exist on any-mode tag sets.
Make TaggedCache an abstract mode-neutral base, add NamespacedTaggedCache for all-mode semantics, and add AnyModeTaggedCache for write/index/flush-only semantics.

Wire Redis all-mode and any-mode tagged caches onto those branches, remove the inherited-then-neutralized any-mode behavior, and keep optimized Redis remember paths while normalizing enum keys at their public boundaries.

Add Redis touch operations that keep key TTLs and tag metadata in sync, and add any-mode plain forget cleanup so deleting a tagged key removes reverse-index and tag hash membership. This prevents stale tag metadata from deleting a later unrelated value at the same key.

Update Redis store routing, doctor namespaced-key handling, and the affected unit/integration tests for clear scope, touch score synchronization, any-mode metadata cleanup, zero-TTL semantics, and enum-key regressions.
Replace method_exists-based tag support detection with a TaggableStore capability probe so stores whose tag support depends on configuration or composition can report their real state.

Add a supportsFlushingLocks probe to CanFlushLocks for the same reason: composite stores can implement the lock-flush contract while delegating to a lower layer that may not actually support flushing locks.

Update repository and event tests so mocks describe the new capability surface explicitly instead of relying on structural shortcuts.
Teach StackStore to validate taggable layer composition and expose any-mode tagged caches when the composition is safe. Non-taggable microcache layers may sit above the first taggable layer, while every layer from that point down must be an any-mode taggable store to avoid flushed-value resurrection.

Add StackTagSet and StackTaggedCache so tagged stack writes record indexes in taggable layers while reads continue through the normal plain-key stack path. Stack tagged writes preserve the existing record format, rollback behavior, and proxy TTL clamping, including the taggable-layer path where tagged writes bypass the proxy.

Delegate stack locks and lock flushing to the bottom layer so lock-backed cache helpers work when the bottom store is lock-capable without ever caching lock state in upper tiers.

Cover composition validation, tagged write mechanics, remember hits and misses, TTL clamps, lock delegation, empty-stack validation, and Redis integration behavior for flush staleness and no-resurrection guarantees.
Make the JWT blacklist storage mode-aware. All-mode stores keep the existing tag-scoped read/delete behavior, while any-mode stores write through tags but read and delete direct prefixed keys because any-mode tags are invalidation indexes only.

Keep the provider gate on Repository::supportsTags(), which is now composition-aware, so valid stack stores are accepted and invalid or non-taggable stores fail before blacklist storage is used.

Add tests for all-mode behavior, any-mode direct-key isolation, disabled blacklist construction over unsupported stores, and valid stack acceptance.
Update the Eloquent user provider cache tag gate to check store type, tag support, and tag mode in that order. This lets invalid stack compositions report unsupported tags without treating getTagMode() as a probe.

Valid any-mode stacks now satisfy the same auth-cache tag contract as any-mode Redis stores, while all-mode and non-taggable stores continue to be rejected with explicit errors.

Extend auth cache tests for valid stack acceptance, invalid stack rejection without getTagMode(), and the updated mock capability surface.
Describe stack tag support as an any-mode-only composition and document the safe layer shape for microcache stacks.

Call out why non-taggable layers below the taggable region are rejected, how bounded staleness works for non-taggable upper layers, and how direct stack writes and touch operations relate to tag metadata.

Also clarify the any-mode Redis metadata contract for tagged writes, plain forget, finite touch, plain rewrites, and tagged touch failures.
Update authentication docs and config comments to describe valid any-mode cache stacks as supported stores for auth cache tags.

Clarify that untagged auth caching still validates the outer stack store while tagged auth caching validates the stack tag composition, and preserve the node-local staleness caveat for stack microcaches.
Document that JWT blacklist storage supports taggable all-mode and any-mode stores while any-mode uses direct prefixed blacklist keys under the hood.

Add the security caveat for cache stacks and node-local tiers: revoked tokens can remain visible to another node until that node's local cache entry expires, so strict revocation should use a fully shared store or a very short upper-tier TTL.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bb0e7a7-9ba8-4e0c-8d32-8ac2bb8daae6

📥 Commits

Reviewing files that changed from the base of the PR and between 10c8b1a and c89b807.

📒 Files selected for processing (8)
  • src/cache/src/Redis/AllTaggedCache.php
  • src/cache/src/Redis/Console/Doctor/Checks/HashFieldExpirationCheck.php
  • src/cache/src/Redis/Console/DoctorCommand.php
  • src/cache/src/Repository.php
  • tests/Cache/CacheRepositoryTest.php
  • tests/Cache/Redis/AllTaggedCacheTest.php
  • tests/Cache/Redis/Console/DoctorCommandTest.php
  • tests/Integration/Cache/Redis/TtlHandlingIntegrationTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cache/src/Redis/AllTaggedCache.php

📝 Walkthrough

Walkthrough

This PR updates cache tagging, Redis tag operations, stack-store delegation, lock-flush capability detection, and Auth/JWT validation to distinguish any-mode tag support from general taggable stores. It also adds matching documentation and tests for the new behavior.

Changes

Cache Tagging and Stack Store Overhaul

Layer / File(s) Summary
Tag-set and tagged-cache base contracts
src/cache/src/TagSet.php, src/cache/src/TaggedCache.php, src/cache/src/NamespacedTagSet.php, src/cache/src/NamespacedTaggedCache.php, src/cache/src/VersionedTagSet.php, src/cache/src/AnyModeTaggedCache.php, src/cache/src/TaggableStore.php, tests/Cache/CacheTaggedCacheTest.php, tests/Cache/Redis/AnyTagSetTest.php
TagSet/TaggedCache become abstract; new namespaced/versioned tag-key implementations and an any-mode tagged cache throwing BadMethodCallException on tag-scoped reads are introduced; TaggableStore gains supportsTags() and uses the versioned namespaced model.
Redis all-mode tag set and tagged cache
src/cache/src/Redis/AllTagSet.php, src/cache/src/Redis/AllTaggedCache.php, tests/Cache/Redis/AllTagSetTest.php, tests/Cache/Redis/AllTaggedCacheTest.php
AllTagSet rebased on NamespacedTagSet with reset/flush; AllTaggedCache extends NamespacedTaggedCache, adds zero-TTL deletion, a touch() implementation, and enum key normalization.
Redis any-mode tag set and tagged cache
src/cache/src/Redis/AnyTagSet.php, src/cache/src/Redis/AnyTaggedCache.php, tests/Cache/Redis/AnyTaggedCacheTest.php
Tag-identity helpers are removed from AnyTagSet in favor of hash-key lookups; AnyTaggedCache now extends AnyModeTaggedCache, changes zero-TTL handling to key deletion, and removes tag-scoped read methods.
Redis touch/forget tag operations and RedisStore wiring
src/cache/src/Redis/Operations/**, src/cache/src/RedisStore.php, src/cache/src/Redis/Console/Doctor/**, tests/Cache/Redis/RedisStoreTest.php, tests/Cache/Redis/Console/DoctorCommandTest.php
New Redis Touch/Forget operation classes for all-tag and any-tag modes with cluster/Lua execution; wired into operation containers; RedisStore dispatches touch/forget by tag mode; doctor checks/context updated for hash-field expiration.
Lock-flush capability probe
src/contracts/src/Cache/CanFlushLocks.php, src/cache/src/AbstractArrayStore.php, src/cache/src/DatabaseStore.php, src/cache/src/FileStore.php, src/cache/src/RedisStore.php, src/cache/src/Repository.php, tests/Cache/CacheRepositoryTest.php, tests/Cache/CacheEventsTest.php, tests/Cache/CacheDatabaseStoreTest.php, tests/Cache/CacheFileStoreTest.php, tests/Cache/CacheRedisStoreTest.php, tests/Integration/Cache/RedisStoreTest.php
supportsFlushingLocks() is added to CanFlushLocks and implemented across stores; Repository tag/lock validation now checks both interface and capability method.
Proxy-based StackStore
src/cache/src/StackStore.php, src/cache/src/StackStoreProxy.php, src/cache/src/StackTagSet.php, src/cache/src/StackTaggedCache.php, tests/Cache/CacheStackStoreTest.php, tests/Cache/CacheStackStoreLocksTest.php, tests/Cache/CacheStackStoreTagsTest.php, tests/Cache/CacheTaggedCacheTest.php, tests/Integration/Cache/Redis/StackTaggedCacheIntegrationTest.php
StackStore wraps layers in StackStoreProxy, delegates locks/flush to the bottom layer, validates tag composition, and supports tagged writes/increments; new StackTagSet/StackTaggedCache classes are added.
Auth/JWT store validation
src/auth/src/EloquentUserProvider.php, src/jwt/src/JWTServiceProvider.php, src/jwt/src/Storage/TaggedCache.php, tests/Auth/AuthEloquentUserProviderCacheTest.php, tests/JWT/JWTServiceProviderTest.php, tests/JWT/Storage/TaggedCacheTest.php
Validation now requires TaggableStore plus supportsTags(); JWT blacklist storage supports a direct-key mode for any-mode stores.
Documentation
src/boost/docs/*.md, src/foundation/config/auth.php, src/jwt/config/jwt.php, src/testbench/hypervel/config/auth.php
Docs and config comments are updated for any-mode tag requirements, tagged cache stacks, and lock-flush delegation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RedisStore
  participant AnyTagOperations
  participant Touch
  participant Redis

  RedisStore->>AnyTagOperations: touch()
  AnyTagOperations->>Touch: execute(key, seconds)
  Touch->>Redis: evalWithShaCache(touchWithTagsScript)
  Redis-->>Touch: success boolean
  Touch-->>RedisStore: bool
Loading
sequenceDiagram
  participant Client
  participant StackTaggedCache
  participant StackStore
  participant UpperLayer
  participant TaggableLayer

  Client->>StackTaggedCache: put(key, value, ttl)
  StackTaggedCache->>StackStore: putRecordTagged(tags, key, record)
  StackStore->>UpperLayer: putToStore(record)
  StackStore->>TaggableLayer: putToStoreTagged(tags, record)
  TaggableLayer-->>StackStore: success
  StackStore-->>StackTaggedCache: bool
Loading

Possibly related PRs

  • hypervel/components#405: Both PRs change JWT blacklist storage validation and tagged-cache tag-support requirements in JWTServiceProvider.
  • hypervel/components#406: Both PRs add supportsFlushingLocks()/CanFlushLocks capability probing to AbstractArrayStore.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the primary change: adding cache tags support for stack stores.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stack-cache-tags

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI 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.

Pull request overview

This PR extends Hypervel’s cache tagging system to support any-mode tags across stack cache stores, enabling a short-lived L1 cache layer in front of Redis while preserving tagged invalidation semantics. It also refactors tag internals into mode-specific abstractions and tightens Redis tagged-cache lifecycle behavior (touch/forget/clear semantics, TTL consistency, enum key normalization).

Changes:

  • Refactor tag architecture into mode-specific TagSet/TaggedCache variants (namespaced vs any-mode), and update Redis tagged-cache operations accordingly.
  • Add stack-store tag support (any-mode only) plus stack lock delegation to the bottom layer, with capability probes reflecting true support.
  • Update framework consumers (Auth + JWT) and add/expand unit + integration tests and docs covering the new semantics.

Reviewed changes

Copilot reviewed 58 out of 59 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/JWT/Storage/TaggedCacheTest.php Expands JWT tagged-cache tests to cover all-mode vs any-mode behavior and direct-key prefixing semantics.
tests/JWT/JWTServiceProviderTest.php Updates blacklist storage validation to accept any-mode (including valid stacks) and improves error messaging.
tests/Integration/Cache/Redis/TtlHandlingIntegrationTest.php Adds integration coverage for all-mode touch score updates and any-mode plain touch metadata synchronization.
tests/Integration/Cache/Redis/TagConsistencyIntegrationTest.php Updates any-mode forget expectations to verify immediate tag membership cleanup and reuse safety.
tests/Integration/Cache/Redis/StackTaggedCacheIntegrationTest.php New integration tests validating stack tagged writes, bounded L1 staleness after flush, and composition safety.
tests/Integration/Cache/Redis/PruneIntegrationTest.php Adjusts prune tests to reflect plain any-mode forget cleanup and simulates orphan creation via direct Redis deletes.
tests/Integration/Cache/Redis/HashLifecycleIntegrationTest.php Updates orphan-field tests to delete outside cache-aware paths to preserve the new forget cleanup behavior.
tests/Cache/Redis/RedisStoreTest.php Adds unit tests ensuring RedisStore routes touch/forget through the correct all/any-mode paths.
tests/Cache/Redis/AnyTagSetTest.php Removes tests for API methods no longer applicable after TagSet refactor.
tests/Cache/Redis/AnyTaggedCacheTest.php Adds negative API tests (PSR + ArrayAccess) for any-mode tagged caches, and validates new zero-TTL semantics + enum normalization.
tests/Cache/Redis/AllTagSetTest.php Adds coverage for reset/flush walking all tags under the refactored TagSet API.
tests/Cache/Redis/AllTaggedCacheTest.php Adds clear(), touch(), putMany(0 TTL), and enum normalization tests for all-mode Redis tagged caches.
tests/Cache/FunnelUnsupportedStoresTest.php Removes the “StackStore doesn’t implement LockProvider” assertion since StackStore now delegates locks.
tests/Cache/CacheTaggedCacheTest.php Adds clear() semantics test ensuring tagged clear does not wipe plain keys in the same store.
tests/Cache/CacheStackStoreTest.php Adds constructor validation test requiring at least one stack layer.
tests/Cache/CacheStackStoreTagsTest.php New unit tests for stack tag composition validation and tagged write behavior across layers.
tests/Cache/CacheStackStoreLocksTest.php New unit tests for stack lock delegation, and lock flushing capability behavior.
tests/Cache/CacheRepositoryTest.php Updates Repository behavior tests for supportsTags/lock flushing probes and taggable-store “supportsTags() false” handling.
tests/Cache/CacheRepositoryEnumTest.php Updates enum tag tests to reflect supportsTags probing behavior.
tests/Cache/CacheEventsTest.php Updates lock flushing event test to account for supportsFlushingLocks probing.
tests/Auth/AuthEloquentUserProviderCacheTest.php Adds acceptance for any-mode stack tags and improves rejection messages/behavior for invalid taggable stacks.
src/testbench/hypervel/config/auth.php Updates auth cache config docs to mention stack tag composition validation when tags are enabled.
src/jwt/src/Storage/TaggedCache.php Adds direct-key mode for any-mode stores (with prefix isolation) while keeping tagged writes for invalidation indexing.
src/jwt/src/JWTServiceProvider.php Improves JWT blacklist “requires tags” exception message to acknowledge both tag modes.
src/jwt/config/jwt.php Documents bounded blacklist visibility with node-local stack tiers.
src/foundation/config/auth.php Mirrors testbench auth config doc updates regarding stack tag validation.
src/contracts/src/Cache/CanFlushLocks.php Adds supportsFlushingLocks() probe to represent real lock flushing capability (esp. composite stores).
src/cache/src/VersionedTagSet.php Introduces versioned/namespaced tag set for stores without native tag indexes.
src/cache/src/TagSet.php Refactors TagSet into an abstract base with mode-specific subclasses owning semantics.
src/cache/src/TaggedCache.php Refactors base tagged cache, adds PSR clear() scoped to tags.
src/cache/src/TaggableStore.php Updates default tags() to return a namespaced tagged cache with a versioned tag set; introduces supportsTags().
src/cache/src/StackTagSet.php New tag set delegating flush across taggable stack layers (any-mode).
src/cache/src/StackTaggedCache.php New any-mode tagged cache implementation for StackStore with tagged writes + plain reads/remember semantics.
src/cache/src/StackStoreProxy.php Adds accessors for underlying store + ttl override to support taggable layer introspection.
src/cache/src/StackStore.php Adds tag validation/composition rules, taggable-layer traversal, and lock delegation + lock flushing support.
src/cache/src/Repository.php Updates supportsTags/supportsFlushingLocks probes and tag operation gating based on TaggableStore/supportsTags().
src/cache/src/RedisStore.php Routes touch/forget through any-mode metadata-aware operations; introduces Touch op and supportsFlushingLocks probe.
src/cache/src/Redis/Operations/Touch.php New all-mode plain expire wrapper operation.
src/cache/src/Redis/Operations/AnyTagOperations.php Adds any-mode Touch + Forget operations and caches them.
src/cache/src/Redis/Operations/AnyTag/Touch.php Implements metadata-aware any-mode touch (key + reverse index + tag fields + registry TTL coherence).
src/cache/src/Redis/Operations/AnyTag/Forget.php Implements metadata-aware any-mode forget (key + tag membership + reverse index cleanup).
src/cache/src/Redis/Operations/AllTagOperations.php Adds all-mode tagged Touch operation and caches it.
src/cache/src/Redis/Operations/AllTag/Touch.php Implements all-mode tagged touch updating both key expiry and tag ZSET scores coherently.
src/cache/src/Redis/Console/Doctor/DoctorContext.php Updates namespaced key derivation to use NamespacedTaggedCache explicitly.
src/cache/src/Redis/AnyTagSet.php Simplifies any-mode tag set behavior and aligns reset/flush with any-mode semantics.
src/cache/src/Redis/AnyTaggedCache.php Rehomes any-mode behavior onto AnyModeTaggedCache base; updates TTL<=0 semantics and enum key normalization.
src/cache/src/Redis/AllTagSet.php Updates AllTagSet to use NamespacedTagSet base and implement reset/flush.
src/cache/src/Redis/AllTaggedCache.php Refactors all-mode tagged cache onto NamespacedTaggedCache; adds touch and adjusts TTL<=0 behavior.
src/cache/src/NamespacedTagSet.php New base class for tag sets that namespace item keys (getNamespace/tagIds contract).
src/cache/src/NamespacedTaggedCache.php New tagged cache for namespaced semantics (taggedItemKey + itemKey override).
src/cache/src/FileStore.php Adds supportsFlushingLocks probe to match new CanFlushLocks interface.
src/cache/src/DatabaseStore.php Adds supportsFlushingLocks probe to match new CanFlushLocks interface.
src/cache/src/AnyModeTaggedCache.php New base class enforcing any-mode contract: tags are write/flush only; read/delete/touch operations throw.
src/cache/src/AbstractArrayStore.php Adds supportsFlushingLocks probe to match new CanFlushLocks interface.
src/boost/docs/jwt.md Documents JWT blacklist requirements and bounded stale window with stack/L1 tiers.
src/boost/docs/cache.md Updates cache docs to describe stack tags, any-mode forget/touch behavior, and stack lock delegation.
src/boost/docs/authentication.md Updates auth docs to include any-mode stack tag support and composition validation.
src/auth/src/EloquentUserProvider.php Updates auth cache tag requirements and expands rationale to include stack any-mode tag support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cache/src/NamespacedTaggedCache.php
Comment thread src/cache/src/Redis/AllTaggedCache.php
Comment thread src/cache/src/Repository.php
Comment thread src/cache/src/RedisStore.php
Comment thread src/cache/src/FileStore.php
Comment thread src/cache/src/DatabaseStore.php
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds any-mode tag support to StackStore, refactors the tag class hierarchy into mode-specific abstractions, and fixes several lifecycle correctness gaps in Redis tagged cache operations.

  • StackStore gains tags(), lock(), flushLocks(), and supportsFlushingLocks(), delegating locks to the bottom layer and tags only to valid any-mode compositions validated lazily via validateTagComposition().
  • New operations: AnyTag/Forget, AnyTag/Touch, and AllTag/Touch keep cache-key expiry and tag-metadata TTLs coherent; RedisStore::forget() and RedisStore::touch() route through these for any-mode stores.
  • Repository::touch() is corrected to use getRaw() and itemKey(); JWT Storage\TaggedCache is updated for any-mode direct-key reads; CanFlushLocks gains supportsFlushingLocks().

Confidence Score: 5/5

Safe to merge. Tag-composition validation is lazily computed and memoized; new Redis operations handle both Lua and cluster paths consistently; correctness fixes in AnyTaggedCache/AllTaggedCache are straightforward.

The core invariants are structurally enforced by the class hierarchy and tested. The two findings are design trade-offs that do not break existing behaviour or introduce data loss.

src/cache/src/StackTaggedCache.php — add() is non-atomic; worth a follow-up if callers rely on single-winner semantics.

Important Files Changed

Filename Overview
src/cache/src/StackStore.php Adds tags(), lock(), flushLocks(), and supportsFlushingLocks() with lazy tag-composition validation; the public putRecordTagged() and incrementTagged() methods expose the internal write protocol needed by StackTaggedCache.
src/cache/src/StackTaggedCache.php New any-mode tagged cache for the stack store; put/forever/remember/increment are all correctly routed through putRecordTagged; add() has a non-atomic existence check that can race in Swoole coroutines.
src/cache/src/Redis/AllTaggedCache.php Gains touch() using getRaw() to distinguish null-stored items from misses, and putMany() now properly deletes on non-positive TTL; remember/rememberForever fix missing enum_value() calls.
src/cache/src/Redis/AnyTaggedCache.php Refactored to extend AnyModeTaggedCache; non-positive TTL put/putMany now properly delete keys with tag cleanup; remember/rememberForever fix missing enum_value() normalization.
src/cache/src/Redis/Operations/AnyTag/Forget.php New operation: removes a key's reverse-index and tag-hash membership before deleting the key; Lua and cluster paths correct.
src/cache/src/Redis/Operations/AnyTag/Touch.php New operation: touches key TTL, reverse-index TTL, tag-hash field TTLs, and tag registry ZSET scores atomically via Lua or sequential cluster commands.
src/cache/src/Redis/Operations/AllTag/Touch.php New operation: EXPIRE on item key + ZADD GT on each tag ZSET; KEYS/ARGV correct; itemKey as ZSET member matches put.
src/cache/src/Repository.php touch() fixed to use getRaw() and itemKey(); supportsTags() now checks TaggableStore::supportsTags().
src/cache/src/TagSet.php Made abstract; only getNames() and abstract reset()/flush() remain.
src/cache/src/TaggedCache.php Made abstract; taggedItemKey()/itemKey() moved to NamespacedTaggedCache; clear() delegates to flush().
src/jwt/src/Storage/TaggedCache.php Detects any-mode stores at construction and routes get/destroy through plain-key reads with DIRECT_KEY_PREFIX.
src/cache/src/AnyModeTaggedCache.php New abstract base throwing BadMethodCallException for all read/forget/touch operations.
src/cache/src/RedisStore.php forget() and touch() route through anyTagOps() in any-mode; supportsFlushingLocks() added.
src/contracts/src/Cache/CanFlushLocks.php supportsFlushingLocks() added as a required capability probe.
src/cache/src/StackTagSet.php New tag set delegating flush to each taggable layer's own tag set.

Reviews (4): Last reviewed commit: "Harden Redis hash expiration doctor prob..." | Re-trigger Greptile

Comment thread src/contracts/src/Cache/CanFlushLocks.php
Comment thread src/cache/src/TaggedCache.php
Comment thread src/cache/src/StackTagSet.php
Comment thread src/cache/src/StackStore.php
The stack tagged cache Redis integration test creates its file-store scratch directory after the Redis integration base setup runs. That base setup can skip before returning when Redis or any-mode tag support is unavailable in a CI job, but PHPUnit still invokes tearDown().

Make the temp directory property nullable and only delete it when setup reached the directory creation step. The test still uses ParallelTesting::tempDir(), and real test paths assert the directory has been initialized before creating the file store.

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

🧹 Nitpick comments (3)
src/cache/src/Redis/Operations/AllTag/Touch.php (1)

73-88: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Misleading comment about phpredis OPT_PREFIX for eval() keys.

The comment states static tag keys go in KEYS "so phpredis applies OPT_PREFIX," but phpredis/predis do not apply the client's key prefix option to eval/evalSha KEYS arguments (this is a well-documented phpredis limitation) — that's precisely why the code must manually build $this->context->prefix() . $key and $this->context->prefix() . $tagId here. The code itself is correct, but the comment's stated rationale is backwards and could mislead a future maintainer into "simplifying" this by removing the manual prefix concatenation, which would break key resolution.

📝 Suggested comment fix
-            // Static tag ZSET keys belong in KEYS so phpredis applies
-            // OPT_PREFIX; ARGV-built keys are only for dynamic Lua paths.
+            // phpredis/predis do not apply the client prefix option to eval()'s
+            // KEYS array, so static tag ZSET keys must be manually prefixed here
+            // before being placed in KEYS; ARGV-built keys are only for dynamic Lua paths.
🤖 Prompt for 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.

In `@src/cache/src/Redis/Operations/AllTag/Touch.php` around lines 73 - 88, The
logic in Touch::evalWithShaCache key construction is correct, but the comment
above the $keys array gives the wrong rationale for manually prefixing keys.
Update the comment near the $this->context->prefix() concatenation to state that
eval/evalSha KEY arguments do not get client prefixing applied, so the prefix
must be added explicitly for the static tag and tagId keys. Keep the code in
Touch unchanged and only fix the misleading explanation so future edits do not
remove the manual prefixing.
src/cache/src/StackStore.php (2)

208-273: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared tag-composition guard.

tags(), getTagMode(), and taggableLayers() each repeat the identical tagCompositionError()-check-and-throw block. Extracting a small guard method would keep the three call sites in sync and shorten each method.

♻️ Proposed refactor
+    /**
+     * `@throws` NotSupportedException when the layer composition cannot support tags
+     */
+    private function assertTagCompositionValid(): void
+    {
+        if (! is_null($error = $this->tagCompositionError())) {
+            throw new NotSupportedException($error);
+        }
+    }
+
     public function tags(mixed $names): TaggedCache
     {
-        if (! is_null($error = $this->tagCompositionError())) {
-            throw new NotSupportedException($error);
-        }
+        $this->assertTagCompositionValid();

         return new StackTaggedCache($this, new StackTagSet($this, is_array($names) ? $names : func_get_args()));
     }
@@
     public function getTagMode(): TagMode
     {
-        if (! is_null($error = $this->tagCompositionError())) {
-            throw new NotSupportedException($error);
-        }
+        $this->assertTagCompositionValid();

         return TagMode::Any;
     }
@@
     public function taggableLayers(): array
     {
-        if (! is_null($error = $this->tagCompositionError())) {
-            throw new NotSupportedException($error);
-        }
+        $this->assertTagCompositionValid();

         $layers = [];
🤖 Prompt for 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.

In `@src/cache/src/StackStore.php` around lines 208 - 273, Extract the repeated
tag-composition error check in StackStore by adding a small shared guard method
that wraps tagCompositionError() and throws NotSupportedException when needed.
Update tags(), getTagMode(), and taggableLayers() to call that helper instead of
duplicating the same if-not-null block so the behavior stays identical and the
three methods remain in sync.

169-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce duplicated bottom-layer lock-flush check.

flushLocks() re-implements the same instanceof CanFlushLocks && supportsFlushingLocks() check already encapsulated in supportsFlushingLocks(), calling bottomStore() twice. Consider delegating to avoid the two implementations drifting apart later.

♻️ Proposed refactor
     public function flushLocks(): bool
     {
-        $store = $this->bottomStore();
-
-        if (! $store instanceof CanFlushLocks || ! $store->supportsFlushingLocks()) {
+        if (! $this->supportsFlushingLocks()) {
             throw new NotSupportedException(sprintf(
                 'The stack\'s bottom layer [%s] does not support flushing locks.',
-                $store::class
+                $this->bottomStore()::class
             ));
         }

-        return $store->flushLocks();
+        return $this->bottomStore()->flushLocks();
     }
🤖 Prompt for 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.

In `@src/cache/src/StackStore.php` around lines 169 - 196, Reduce the duplicated
bottom-layer lock-flush capability check in StackStore by making flushLocks()
delegate to supportsFlushingLocks() instead of repeating the instanceof
CanFlushLocks and supportsFlushingLocks() logic. Keep bottomStore() lookup and
the NotSupportedException behavior in flushLocks(), but centralize the
eligibility check through supportsFlushingLocks() so the two methods cannot
drift apart. Use the existing StackStore methods supportsFlushingLocks() and
flushLocks() as the main touchpoints.
🤖 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 `@src/cache/src/Redis/Operations/AnyTag/Touch.php`:
- Line 62: The any-mode tag touch path in Touch::touch now relies on Redis HASH
expiry commands, so document that this feature requires Redis 7.4+ or add a
runtime capability guard before calling hexpire/httl. Update the feature
description near Touch and any related any-tag cache docs/config checks to
mention the minimum Redis version and fail or skip gracefully when the server
does not support these commands.

---

Nitpick comments:
In `@src/cache/src/Redis/Operations/AllTag/Touch.php`:
- Around line 73-88: The logic in Touch::evalWithShaCache key construction is
correct, but the comment above the $keys array gives the wrong rationale for
manually prefixing keys. Update the comment near the $this->context->prefix()
concatenation to state that eval/evalSha KEY arguments do not get client
prefixing applied, so the prefix must be added explicitly for the static tag and
tagId keys. Keep the code in Touch unchanged and only fix the misleading
explanation so future edits do not remove the manual prefixing.

In `@src/cache/src/StackStore.php`:
- Around line 208-273: Extract the repeated tag-composition error check in
StackStore by adding a small shared guard method that wraps
tagCompositionError() and throws NotSupportedException when needed. Update
tags(), getTagMode(), and taggableLayers() to call that helper instead of
duplicating the same if-not-null block so the behavior stays identical and the
three methods remain in sync.
- Around line 169-196: Reduce the duplicated bottom-layer lock-flush capability
check in StackStore by making flushLocks() delegate to supportsFlushingLocks()
instead of repeating the instanceof CanFlushLocks and supportsFlushingLocks()
logic. Keep bottomStore() lookup and the NotSupportedException behavior in
flushLocks(), but centralize the eligibility check through
supportsFlushingLocks() so the two methods cannot drift apart. Use the existing
StackStore methods supportsFlushingLocks() and flushLocks() as the main
touchpoints.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ef6df04-31a3-454f-8775-2ea2a08875e9

📥 Commits

Reviewing files that changed from the base of the PR and between abb9d20 and ca2030b.

📒 Files selected for processing (59)
  • docs/plans/2026-07-06-stack-cache-tags-and-tag-architecture-refactor.md
  • src/auth/src/EloquentUserProvider.php
  • src/boost/docs/authentication.md
  • src/boost/docs/cache.md
  • src/boost/docs/jwt.md
  • src/cache/src/AbstractArrayStore.php
  • src/cache/src/AnyModeTaggedCache.php
  • src/cache/src/DatabaseStore.php
  • src/cache/src/FileStore.php
  • src/cache/src/NamespacedTagSet.php
  • src/cache/src/NamespacedTaggedCache.php
  • src/cache/src/Redis/AllTagSet.php
  • src/cache/src/Redis/AllTaggedCache.php
  • src/cache/src/Redis/AnyTagSet.php
  • src/cache/src/Redis/AnyTaggedCache.php
  • src/cache/src/Redis/Console/Doctor/DoctorContext.php
  • src/cache/src/Redis/Operations/AllTag/Touch.php
  • src/cache/src/Redis/Operations/AllTagOperations.php
  • src/cache/src/Redis/Operations/AnyTag/Forget.php
  • src/cache/src/Redis/Operations/AnyTag/Touch.php
  • src/cache/src/Redis/Operations/AnyTagOperations.php
  • src/cache/src/Redis/Operations/Touch.php
  • src/cache/src/RedisStore.php
  • src/cache/src/Repository.php
  • src/cache/src/StackStore.php
  • src/cache/src/StackStoreProxy.php
  • src/cache/src/StackTagSet.php
  • src/cache/src/StackTaggedCache.php
  • src/cache/src/TagSet.php
  • src/cache/src/TaggableStore.php
  • src/cache/src/TaggedCache.php
  • src/cache/src/VersionedTagSet.php
  • src/contracts/src/Cache/CanFlushLocks.php
  • src/foundation/config/auth.php
  • src/jwt/config/jwt.php
  • src/jwt/src/JWTServiceProvider.php
  • src/jwt/src/Storage/TaggedCache.php
  • src/testbench/hypervel/config/auth.php
  • tests/Auth/AuthEloquentUserProviderCacheTest.php
  • tests/Cache/CacheEventsTest.php
  • tests/Cache/CacheRepositoryEnumTest.php
  • tests/Cache/CacheRepositoryTest.php
  • tests/Cache/CacheStackStoreLocksTest.php
  • tests/Cache/CacheStackStoreTagsTest.php
  • tests/Cache/CacheStackStoreTest.php
  • tests/Cache/CacheTaggedCacheTest.php
  • tests/Cache/FunnelUnsupportedStoresTest.php
  • tests/Cache/Redis/AllTagSetTest.php
  • tests/Cache/Redis/AllTaggedCacheTest.php
  • tests/Cache/Redis/AnyTagSetTest.php
  • tests/Cache/Redis/AnyTaggedCacheTest.php
  • tests/Cache/Redis/RedisStoreTest.php
  • tests/Integration/Cache/Redis/HashLifecycleIntegrationTest.php
  • tests/Integration/Cache/Redis/PruneIntegrationTest.php
  • tests/Integration/Cache/Redis/StackTaggedCacheIntegrationTest.php
  • tests/Integration/Cache/Redis/TagConsistencyIntegrationTest.php
  • tests/Integration/Cache/Redis/TtlHandlingIntegrationTest.php
  • tests/JWT/JWTServiceProviderTest.php
  • tests/JWT/Storage/TaggedCacheTest.php
💤 Files with no reviewable changes (2)
  • tests/Cache/FunnelUnsupportedStoresTest.php
  • tests/Cache/Redis/AnyTagSetTest.php

Comment thread src/cache/src/Redis/Operations/AnyTag/Touch.php
Make cache capability probes match the operations they guard. Redis, file, and database stores now report lock flushing support only when lock storage is separate, while their direct flushLocks guards remain in place for store-level calls.

Let conditional taggable stores own their detailed tag support failures by removing Repository::tags()' supportsTags preflight. Non-taggable stores still get the generic BadMethodCallException, but invalid StackStore compositions now preserve their NotSupportedException message.

Clarify the TaggableStore contract for conditional tag support, extract the StackStore tag-composition guard, and tighten Redis doctor/docs wording so any-mode requirements are tied to hash-field expiration commands including HSETEX and HEXPIRE.

Add regression coverage for preserved StackStore tag errors, honest lock-flush probes, repository/store exception boundaries, and the updated enum tag expectations.

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

🧹 Nitpick comments (1)
src/cache/src/Redis/Console/Doctor/Checks/HexpireCheck.php (1)

14-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Message mentions HSETEX but the probe only tests HEXPIRE.

The doc comment and fix instructions now reference both HSETEX and HEXPIRE, but run() (Lines 45-58) only exercises hexpire(). If a server supports one but not the other, the check name ('HEXPIRE Command', Line 31) and message could be misleading about which command actually failed.

Also applies to: 70-70

🤖 Prompt for 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.

In `@src/cache/src/Redis/Console/Doctor/Checks/HexpireCheck.php` around lines 14 -
15, The HexpireCheck probe is describing both HSETEX and HEXPIRE even though
run() only tests HEXPIRE, so align the check text and messaging with the actual
command being exercised. Update the doc comment and the check name/message in
HexpireCheck, and if HSETEX is intentionally part of the requirement then extend
run() to probe it explicitly; otherwise keep the references limited to HEXPIRE
so the reported failure matches the real failing command.
🤖 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.

Nitpick comments:
In `@src/cache/src/Redis/Console/Doctor/Checks/HexpireCheck.php`:
- Around line 14-15: The HexpireCheck probe is describing both HSETEX and
HEXPIRE even though run() only tests HEXPIRE, so align the check text and
messaging with the actual command being exercised. Update the doc comment and
the check name/message in HexpireCheck, and if HSETEX is intentionally part of
the requirement then extend run() to probe it explicitly; otherwise keep the
references limited to HEXPIRE so the reported failure matches the real failing
command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b074166-1ff6-4348-a1e5-8d90c24a0bc4

📥 Commits

Reviewing files that changed from the base of the PR and between ca2030b and 10c8b1a.

📒 Files selected for processing (15)
  • src/boost/docs/cache.md
  • src/cache/src/DatabaseStore.php
  • src/cache/src/FileStore.php
  • src/cache/src/Redis/Console/Doctor/Checks/HexpireCheck.php
  • src/cache/src/Redis/Console/Doctor/Checks/RedisVersionCheck.php
  • src/cache/src/RedisStore.php
  • src/cache/src/Repository.php
  • src/cache/src/StackStore.php
  • src/cache/src/TaggableStore.php
  • tests/Cache/CacheDatabaseStoreTest.php
  • tests/Cache/CacheFileStoreTest.php
  • tests/Cache/CacheRedisStoreTest.php
  • tests/Cache/CacheRepositoryTest.php
  • tests/Integration/Cache/Redis/StackTaggedCacheIntegrationTest.php
  • tests/Integration/Cache/RedisStoreTest.php
✅ Files skipped from review due to trivial changes (1)
  • src/cache/src/Redis/Console/Doctor/Checks/RedisVersionCheck.php
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/cache/src/FileStore.php
  • tests/Integration/Cache/Redis/StackTaggedCacheIntegrationTest.php
  • src/cache/src/TaggableStore.php
  • src/cache/src/Repository.php
  • src/cache/src/RedisStore.php
  • src/cache/src/StackStore.php
  • src/boost/docs/cache.md

Comment thread src/cache/src/Redis/AllTaggedCache.php
Comment thread src/cache/src/Redis/AllTaggedCache.php
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai responding to the first-round review-body notes here, separate from the newer HexpireCheck nitpick.

  • AllTag Touch OPT_PREFIX comment: leaving as-is. The current comment is intentional: static Lua KEYS go through phpredis key prefixing, while ARGV-built dynamic keys need the cache prefix added explicitly.
  • StackStore tag-composition guard: fixed in 10c8b1a with a shared guard method.
  • StackStore flushLocks delegation: leaving as-is. The current shape keeps the bottom-store type narrowing local to the method that needs it, and avoids turning a small explicit guard into indirection without changing behavior.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@binaryfire Thanks for following up on all three points.

  • AllTag Touch OPT_PREFIX comment: Makes sense — noted the distinction between static Lua KEYS (auto-prefixed by phpredis) and ARGV-built dynamic keys (need explicit prefixing). No further action needed there.
  • StackStore tag-composition guard: Confirmed the fix in 10c8b1a with the shared guard method. Looks good.
  • StackStore flushLocks delegation: Understood — keeping the type narrowing local to the method avoids introducing indirection for no behavioral gain. Fair tradeoff.

Thanks for the detailed rationale on each.

Repository::touch() now normalizes enum keys before probing the store and reads the existing value through getRaw(). This keeps cached null sentinels distinct from misses and avoids passing enum instances through the string-only item-key path.

All-mode Redis tagged touch now uses the same raw read before updating TTL metadata. Finite touches update the key and tag scores for cached-null values, while touch(key, null) re-stores the raw sentinel through the tagged forever path instead of writing a literal null.

The regression coverage pins the plain repository enum/sentinel behavior, the all-mode tagged unit path, and the Redis integration case where a nullable tagged value is touched, survives stale pruning, and remains stored as the sentinel.
Rename the old HEXPIRE-only doctor check to HashFieldExpirationCheck so the name matches the capability any-mode tags actually require.

The environment check now probes both HSETEX and HEXPIRE in any tag mode. HSETEX is used by tagged writes and HEXPIRE is used when plain touch extends any-mode tag metadata, so checking only HEXPIRE could report a store as ready when writes would still fail.

The tests cover all-mode skipping, successful any-mode probing, and the throwing failure path with the existing fix instructions.
The doctor probe now treats false returns from either HSETEX or HEXPIRE as a failed capability check instead of relying only on exceptions.

This matches phpredis behavior where server-side command errors may surface as false plus connection error state rather than Throwable. Without this, cache:redis-doctor could report any-mode tag support as available on servers that do not actually support the required hash-field expiration commands.

The regression test covers the silent false-return path and verifies that the existing upgrade/switch-mode fix instructions are still reported.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptileai @coderabbitai follow-up on the remaining review-body items.

Greptile:

  • AllTaggedCache::touch() cached-null handling is fixed in 1735c57. It now uses getRaw(), updates finite TTL scores for cached-null values, and preserves the raw sentinel through touch($key, null). Added unit and Redis integration regressions.
  • StackTaggedCache::add() is left as-is. A stack is a write-through composite; making add() globally atomic across heterogeneous layers would require a separate cross-layer primitive/lock design and would be a different abstraction than the rest of StackStore writes. The current implementation matches the stack write path and rollback model.

CodeRabbit:

  • The HexpireCheck nitpick is addressed in 9aea4c6 and c89b807. The check is now HashFieldExpirationCheck, probes both HSETEX and HEXPIRE, and treats false returns as failures as well as thrown exceptions.

Validation after the fixes: focused doctor tests green, focused Horizon test green after the transient teardown failure, and composer fix green end-to-end.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Your plan includes PR reviews subject to rate limits. More reviews will be available in 58 minutes.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

All three items are confirmed resolved:

AllTaggedCache::touch() cached-null (1735c57): The current code matches the fix — getRaw() is used instead of get(), so a NullSentinel entry distinguishes itself from a genuine miss. The null-TTL branch re-stores the raw sentinel through forever() rather than passing an unwrapped null, keeping the ZSET score coherent. The regression tests (unit + Redis integration via stale-prune path) close the gap cleanly.

StackTaggedCache::add() atomicity: The rationale is sound. Globally atomic add() across heterogeneous stores would need a cross-layer lock primitive that's outside the scope of this PR, and the current write-through + rollback model is consistent with the rest of StackStore. Accepted.

HashFieldExpirationCheck (9aea4c6 + c89b807): The rename to HashFieldExpirationCheck is accurate since it now validates both HSETEX and HEXPIRE. The dual probe is the right shape — HSETEX is used on tagged writes, HEXPIRE on the touch() path, so checking only one could produce a false positive. The false-return hardening in c89b807 covers phpredis's silent failure mode. The version floor of Redis 8.0+/Valkey 9.0+ documented in the fix instructions is correct given HSETEX availability.

The PR looks good to merge from my side.

@binaryfire binaryfire merged commit 96d6c66 into 0.4 Jul 6, 2026
36 checks passed
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.

2 participants