Derive catalog cache keys from the filter (#207) - #212
Closed
deverman wants to merge 1 commit into
Closed
Conversation
CacheKey listed its fields by hand and stayed correct only because a separate shouldBypassCache list happened to name every field the key omitted. Nothing connected the two. The gap was already widening. #88 nearly shipped a rootOnly omission that would have served filed projects to a request for unfiled ones -- no error, no warning, a plausible answer. #171 then added searches and matchLimitPerSearch to both filters, and neither appeared in the key; that was harmless only because the batch path bypasses the cache entirely, a property nothing enforced. The key is now a fingerprint of the filter's own sorted-key encoding, so any new filter field changes the identity automatically. Page limit, cursor, and requested fields stay explicit because they are not part of the filter. Cacheability is decided from that same encoding. "Do not cache" remains a judgement about semantics -- time-relative review and completion windows, and live batch resolution -- so it stays an explicit field-name list, but it is now checked against the encoded filter rather than duplicated as a boolean expression that could drift from it. Fixed while here: the tags path consulted the cache conditionally but wrote to it unconditionally, so an uncacheable tag result would still have been stored. Existing key-separation tests are preserved and migrated, with new coverage for rootOnly and searches separation, and for the invariant that any field change alters the fingerprint. Validation impact: query. 345 tests pass; all semantic gates pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Closing unmerged. This fixed no reachable bug: the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #207.
The problem
CacheKeylisted its fields by hand and stayed correct only because a separateshouldBypassCacheexpression happened to name every field the key omitted. Nothing connected the two — they agreed by inspection, not construction.The gap was already widening:
rootOnlyomission. A cached full-catalogue page would have satisfied a request for unfiled projects only: no error, no warning, a plausible-looking answer.searchesandmatchLimitPerSearchto both filters, and neither appeared in the key. That was harmless only because the batch path bypasses the cache entirely — a property nothing enforced and nobody had written down.The change
The key is now a fingerprint of the filter's own sorted-key encoding, so any new filter field changes the identity automatically. Page limit, cursor, and requested fields stay explicit, because they are not part of the filter.
Cacheability is decided from that same encoding. "Do not cache" stays an explicit field-name list — it is a judgement about semantics (time-relative review and completion windows, live batch resolution), not something derivable — but it is now checked against the encoded filter rather than duplicated as a boolean expression that can drift from it.
Fixed while here: the tags path consulted the cache conditionally but wrote to it unconditionally, so an uncacheable tag result would still have been stored.
Tests
Existing key-separation tests are preserved and migrated rather than replaced — they pin real behaviour and all still pass. Added:
projectCacheKeySeparatesRootOnly— the Expose project folder membership and root filtering in list_projects #88 near-miss, now structurally impossibleprojectCacheKeySeparatesBatchSearches/tagCacheKeySeparatesBatchSearches— the Resolve multiple project or tag names in one bounded query #171 fields the old key never sawanyFieldChangeChangesTheFingerprint— the invariant itself, across five distinct fieldsabsentFieldsDoNotCountAsPresent— a nil field must not encode, or every filter would look uncacheableOne honest note on the acceptance criteria: the issue asked for a test that fails when a filter gains a field the derivation cannot see. That test cannot be written directly, because derivation sees every field by construction — which is the point. The invariant is covered instead by asserting that representative field changes each alter the fingerprint.
Validation
Impact:
query. 345 tests pass; all semantic gates pass. Verified live that arootOnlyquery and an unscoped query return different result sets (73 vs 150 items) with no cross-contamination.