Skip to content

Add CI-ready entitlement smart diff - #74

Open
hosom wants to merge 5 commits into
mainfrom
hosom/smart-diff-ci
Open

Add CI-ready entitlement smart diff#74
hosom wants to merge 5 commits into
mainfrom
hosom/smart-diff-ci

Conversation

@hosom

@hosom hosom commented Sep 2, 2026

Copy link
Copy Markdown

Summary

  • add a deterministic desired-membership exporter that avoids provider reconciliation
  • add entitlements-smart-diff for base/head JSON and bounded Markdown reports
  • use one frozen people snapshot and evaluation timestamp for both trees
  • warn and mark results incomplete when arbitrary Ruby groups or their dependents cannot be evaluated safely offline
  • bump entitlements-app to 1.2.2 and package the new executable

Validation

  • script/test -d (544 examples, 100% coverage)
  • built and inspected entitlements-app-1.2.2.gem
  • exercised the command against github/entitlements#140563

Export deterministic desired memberships and compare PR base/head without provider reconciliation. Report incomplete results when arbitrary Ruby groups prevent a complete offline calculation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b96e14a4-88bb-45b9-9b94-a681a34d05bc
Copilot AI balanced review requested due to automatic review settings September 2, 2026 21:21
Comment thread lib/entitlements/smart_diff.rb Fixed
Comment thread lib/entitlements/smart_diff.rb Fixed

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.

Copilot review overview

🟡 Changes recommended

Dynamic filter dependencies and LDAP snapshot assumptions can produce unsafe or falsely complete results.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 2 Medium severity · 1 Low severity

New issues introduced by this change (4)
Severity Finding
High severity lib/​entitlements.rb — This removes every extension-defined constant under Entitlements::Rule, not only classes loaded…
Medium severity lib/​entitlements/​desired_groups.rbskip_dynamic_groups is not propagated through calculated-group filters. Filters are instantiated…
Medium severity lib/​entitlements/​extras/​ldap_group/​rules/​ldap_group.rb — Missing shellentitlements is treated as definite non-membership, although that attribute is…
Low severity README.md — Remove the stray -app suffix. This issue also appears on line 21 of the same file.
What changed in this PR

Adds deterministic, CI-ready entitlement membership comparison using frozen people and time inputs.

Changes:

  • Adds desired-membership export and bounded JSON/Markdown smart diff.
  • Handles dynamic groups, mirrors, LDAP rules, and frozen expiration evaluation.
  • Packages the new executable and adds comprehensive fixtures/specs.
File Description
.gitignore Tracks the new executable.
README.md Documents CI smart diff usage.
Gemfile.lock Updates the gem version.
entitlements-app.gemspec Packages the executable.
bin/​entitlements-smart-diff Adds the command entry point.
lib/​version.rb Bumps version to 1.2.2.
lib/​entitlements.rb Adds evaluation time, reset behavior, and requires.
lib/​entitlements/​desired_groups.rb Implements deterministic membership export.
lib/​entitlements/​smart_diff.rb Implements comparison and report rendering.
lib/​entitlements/​smart_diff/​cli.rb Implements CLI parsing and output.
lib/​entitlements/​data/​groups/​calculated.rb Adds dynamic-group skipping and mirror warnings.
lib/​entitlements/​data/​groups/​calculated/​base.rb Cleans up failed dynamic dependencies.
lib/​entitlements/​data/​groups/​calculated/​ruby.rb Detects and reloads Ruby groups.
lib/​entitlements/​data/​groups/​calculated/​text.rb Uses frozen evaluation time.
lib/​entitlements/​data/​groups/​calculated/​modifiers/​expiration.rb Freezes expiration evaluation.
lib/​entitlements/​data/​groups/​calculated/​rules/​group.rb Resets caches and propagates options.
lib/​entitlements/​extras/​ldap_group/​rules/​ldap_group.rb Adds snapshot-based offline matching.
spec/​unit/​entitlements/​desired_groups_spec.rb Tests export behavior.
spec/​unit/​entitlements/​smart_diff_spec.rb Tests comparison and rendering.
spec/​unit/​entitlements/​extras/​ldap_group/​rules/​ldap_group_spec.rb Tests offline LDAP matching.
spec/​unit/​fixtures/​smart-diff/​config.yaml Configures smart-diff fixtures.
spec/​unit/​fixtures/​smart-diff/​people.yaml Supplies frozen people data.
spec/​unit/​fixtures/​smart-diff/​groups/​internal/​contractors.txt Defines contractor membership.
spec/​unit/​fixtures/​smart-diff/​groups/​internal/​engineers.txt Defines engineer membership.
spec/​unit/​fixtures/​smart-diff/​groups/​teams/​direct.txt Defines direct membership.
spec/​unit/​fixtures/​smart-diff/​groups/​teams/​expiring.yaml Exercises expiration behavior.
spec/​unit/​fixtures/​smart-diff/​groups/​teams/​filtered.txt Exercises filtering.
spec/​unit/​fixtures/​smart-diff/​groups/​teams/​nested.yaml Exercises nested groups.
spec/​unit/​fixtures/​smart-diff/​groups/​teams/​ruby-group.rb Exercises Ruby membership.
spec/​unit/​fixtures/​dynamic-groups/​config.yaml Configures dynamic-group fixtures.
spec/​unit/​fixtures/​dynamic-groups/​groups/​teams/​dependent.txt Tests dynamic dependencies.
spec/​unit/​fixtures/​dynamic-groups/​groups/​teams/​dynamic.rb Defines unsafe dynamic behavior.
spec/​unit/​fixtures/​dynamic-groups/​groups/​teams/​static-ruby.rb Tests conservative Ruby detection.
spec/​unit/​fixtures/​dynamic-groups/​groups/​teams/​static.txt Provides exportable static membership.
Suppressed comments (1)

README.md:21

  • Remove the stray -app suffix.
Ruby entitlement groups are treated as dynamic by smart diff because arbitrary Ruby cannot be proven deterministic from frozen inputs. Groups that use or transitively depend on Ruby definitions are omitted from the membership comparison, and both JSON and Markdown report that the result is incomplete. Normal deployment behavior is unchanged.-app

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/entitlements.rb
Comment on lines +107 to +109
Entitlements::Rule.constants(false).each do |constant|
Entitlements::Rule.send(:remove_const, constant) unless constant == :Base
end
Entitlements::Data::Groups::Calculated.read_all(
group_name,
group_config,
skip_dynamic_groups: allow_incomplete
Comment on lines +25 to +31
if Entitlements.cache[:desired_groups_export]
return Set.new(Entitlements.cache[:people_obj].read.values.select do |person|
begin
Array(person["shellentitlements"]).map(&:downcase).include?(value.downcase)
rescue KeyError
false
end
Comment thread README.md
--markdown /output/smart-diff.md
```

The default configuration path in each tree is `config/entitlements.yaml`; use `--base-config` and `--head-config` when repositories use another path. When the configuration uses plugin-defined backend types, preload the trusted plugin assembly with `RUBYOPT=-r/path/to/entitlements-and-plugins`. PR-controlled entitlement code must still run in a credential-free, network-isolated sandbox.-app
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b96e14a4-88bb-45b9-9b94-a681a34d05bc
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b96e14a4-88bb-45b9-9b94-a681a34d05bc
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b96e14a4-88bb-45b9-9b94-a681a34d05bc
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b96e14a4-88bb-45b9-9b94-a681a34d05bc

def self.escape_table(value)
escaped = value.to_s.gsub(/[\r\n]+/, " ").gsub("\\") { "\\\\" }
CGI.escapeHTML(escaped).gsub("|") { "\\|" }
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.

3 participants