Conversation
Avoid repeated filter scans and linear membership checks, reuse recursively calculated file objects, and preserve target configuration for recursive rulesets. Co-authored-by: Copilot <[email protected]> Copilot-Session: 5b8ed060-ed02-4402-b5e1-af407bcb2f61
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Canonical cache collisions can bypass ruleset extension validation and use the wrong file object.
Get a fresh assessment by requesting another Copilot review.
Review tier: Balanced (auto)
Findings: 1
Note
Copilot is running an experiment and ran this review at Balanced.
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
lib/entitlements/data/groups/calculated.rb — Validate colliding filenames before reusing the cache entry View comment |
What changed in this PR
Optimizes entitlement calculation by reducing repeated filtering, membership lookups, and recursive object creation.
Changes:
- Precomputes filter data and uses sets for membership checks.
- Prevents duplicate filter passes.
- Canonicalizes recursive and top-level ruleset caching.
| File | Description |
|---|---|
lib/entitlements/data/groups/calculated.rb |
Canonicalizes file-object cache keys. |
lib/entitlements/data/groups/calculated/base.rb |
Resolves filter applicability once. |
lib/entitlements/data/groups/calculated/filters/base.rb |
Precomputes filter entries and set membership. |
lib/entitlements/data/groups/calculated/filters/member_of_group.rb |
Removes hot-path contract validation. |
lib/entitlements/data/groups/calculated/rules/group.rb |
Uses configured ruleset construction recursively. |
lib/entitlements/extras/ldap_group/filters/member_of_ldap_group.rb |
Uses set membership and removes contracts. |
spec/unit/entitlements/data/groups/calculated/base_spec.rb |
Tests single filter execution. |
spec/unit/entitlements/data/groups/calculated/rules/group_spec.rb |
Tests recursive cache reuse. |
spec/unit/entitlements/data/groups/calculated_spec.rb |
Tests canonical cache keys. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| cache_key = file_object_key(filename) | ||
| Entitlements.cache[:file_objects][cache_key] ||= ruleset(filename: filename, config: cfg_obj, options: options) | ||
| file_object = Entitlements.cache[:file_objects][cache_key] |
There was a problem hiding this comment.
Addressed in 2c13bf0. Both top-level and recursive calculations now cache by the exact full filename, including extension. Recursive lookup resolves the physical file before consulting the cache, preserving reuse for the same file without allowing basename collisions to bypass ruleset validation.
| def filter_usernames | ||
| @filter_usernames ||= filter.each_with_object(Set.new) do |filter_val, result| | ||
| result.add(filter_val.downcase) unless filter_val.include?("/") | ||
| end | ||
| end | ||
|
|
||
| def filter_groups | ||
| @filter_groups ||= filter.select { |filter_val| filter_val.include?("/") } | ||
| end |
There was a problem hiding this comment.
Memoizing these values and creating sets out of them should speed up calculations by a decent chunk.
| # member - Entitlements::Models::Person object | ||
| # | ||
| # Returns true if a member of the filter conditions, false otherwise. | ||
| Contract Entitlements::Models::Person => C::Bool |
There was a problem hiding this comment.
Ok, so here's the thing. This is in the MIDDLE of the hot path and the allocation for Contracts is really expensive. This method is entirely private and isn't called outside of our code. I think that this contract hurts us more than it helps.
Use exact filenames for both recursive and top-level calculated group caching so identical files are reused without collisions between different ruleset extensions. Co-authored-by: Copilot <[email protected]>
| result = members.dup | ||
| filters.reject { |_, filter_val| filter_val == :all }.each do |filter_name, filter_val| | ||
| filter_cfg = Entitlements::Data::Groups::Calculated.filters_index[filter_name] | ||
| next unless filter_applies?(filter_cfg.fetch(:config, {})) |
There was a problem hiding this comment.
Previously, we had filters that were in some cases being applied two times to the same file. We now have a helper to determine if the filter applies and we only run it once.

Summary
This fixes several compounding calculation-path issues discovered with a production-shaped synthetic entitlement benchmark.
Issue breakdown
filtered?and membership helper calls for every candidate member. Contract reflection and validation dominated allocations.included_pathsandexcluded_paths, a selected file could execute the identicalreject!pass twice.rejectandselectfor every candidate member.Array#include?for every candidate.Setfor constant-time lookup.Calculated.rulesetwith target configuration and options.Correctness impact
allowed_types.Performance
Ruby 3.3.12, synthetic production-shaped calculation, three unprofiled runs where noted:
This reduces calculation time by approximately 70%, allocations by 82%, and peak memory by about 10%. Duplicate calculation log events fell from 8,401 to 8.
Validation
bundle exec rspec spec/unit— 538 examples, 0 failures, 100% unit coverageThe acceptance suite requires its Docker/LDAP environment and is not runnable directly on the host checkout.