<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/views/authorization_rules/_suggestion.erb</filename>
    </added>
    <added>
      <filename>app/views/authorization_rules/change.html.erb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,6 +1,7 @@
 if Authorization::activate_authorization_rules_browser?
 
-require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization authorization_rules_analyzer})
+require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization development_support analyzer})
+require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization development_support change_analyzer})
 
 begin
   # for nice auth_rules output:
@@ -28,6 +29,44 @@ class AuthorizationRulesController &lt; ApplicationController
     end
   end
 
+  def change
+    # TODO not generic enough
+    @users = User.all
+    @users.sort! {|a, b| a.login &lt;=&gt; b.login }
+  end
+
+  def suggest_change
+    users_permission = params[:user].inject({}) do |memo, (user_id, data)|
+      if data[:permission] != &quot;undetermined&quot;
+        begin
+          memo[User.find(user_id)] = (data[:permission] == 'yes')
+        rescue ActiveRecord::NotFound
+        end
+      end
+      memo
+    end
+
+    users_keys = users_permission.keys
+    analyzer = Authorization::DevelopmentSupport::ChangeAnalyzer.new(authorization_engine)
+    
+    privilege = params[:privilege].to_sym
+    context = params[:context].to_sym
+    @context = context
+    @approaches = analyzer.find_approaches_for(params[:goal].to_sym,
+        :permission, :on =&gt; context, :to =&gt; privilege, :users =&gt; users_keys) do
+      users.each_with_index do |user, idx|
+        args = [privilege, {:context =&gt; context, :user =&gt; user}]
+        assert(users_permission[users_keys[idx]] ? permit?(*args) : !permit?(*args))
+      end
+    end
+
+    respond_to do |format|
+      format.js do
+        render :partial =&gt; 'suggestion'
+      end
+    end
+  end
+
   private
   def auth_to_dot (options = {})
     options = {
@@ -36,21 +75,35 @@ class AuthorizationRulesController &lt; ApplicationController
       :only_relevant_contexts =&gt; true,
       :filter_roles =&gt; nil,
       :filter_contexts =&gt; nil,
-      :highlight_privilege =&gt; nil
+      :highlight_privilege =&gt; nil,
+      :changes =&gt; nil
     }.merge(options)
 
+    @has_changes = options[:changes] &amp;&amp; !options[:changes].empty?
     @highlight_privilege = options[:highlight_privilege]
-    @roles = authorization_engine.roles
-    @roles = @roles.select {|r| r == options[:filter_roles] } if options[:filter_roles]
-    @role_hierarchy = authorization_engine.role_hierarchy
-    @privilege_hierarchy = authorization_engine.privilege_hierarchy
+
+    engine = authorization_engine.clone
+
+    filter_roles_flattened = nil
+    if options[:filter_roles]
+      filter_roles_flattened = options[:filter_roles].collect do |role_sym|
+        Authorization::DevelopmentSupport::AnalyzerEngine::Role.for_sym(role_sym, engine).
+            ancestors.map(&amp;:to_sym) + [role_sym]
+      end.flatten.uniq
+    end
+
+    @changes = replay_changes(engine, options[:changes]) if options[:changes]
+    @roles = engine.roles
+    @roles = @roles.select {|r| filter_roles_flattened.include?(r) } if options[:filter_roles]
+    @role_hierarchy = engine.role_hierarchy
+    @privilege_hierarchy = engine.privilege_hierarchy
     
-    @contexts = authorization_engine.auth_rules.
+    @contexts = engine.auth_rules.
                     collect {|ar| ar.contexts.to_a}.flatten.uniq
     @contexts = @contexts.select {|c| c == options[:filter_contexts] } if options[:filter_contexts]
     @context_privs = {}
     @role_privs = {}
-    authorization_engine.auth_rules.each do |auth_rule|
+    engine.auth_rules.each do |auth_rule|
       @role_privs[auth_rule.role] ||= []
       auth_rule.contexts.
             select {|c| options[:filter_contexts].nil? or c == options[:filter_contexts]}.
@@ -88,6 +141,17 @@ class AuthorizationRulesController &lt; ApplicationController
     
     render_to_string :template =&gt; 'authorization_rules/graph.dot.erb', :layout =&gt; false
   end
+
+  def replay_changes (engine, changes)
+    changes.inject({}) do |memo, info|
+      case info[0]
+      when :add_privilege, :add_role
+        Authorization::DevelopmentSupport::AnalyzerEngine.apply_change(engine, info)
+      end
+      (memo[info[0]] ||= Set.new) &lt;&lt; info[1..-1]
+      memo
+    end
+  end
   
   def dot_to_svg (dot_data)
     gv = IO.popen(&quot;#{Authorization.dot_path} -q -Tsvg&quot;, &quot;w+&quot;)
@@ -102,11 +166,22 @@ class AuthorizationRulesController &lt; ApplicationController
     {
       :effective_role_privs =&gt; !params[:effective_role_privs].blank?,
       :privilege_hierarchy =&gt; !params[:privilege_hierarchy].blank?,
-      :filter_roles =&gt; params[:filter_roles].blank? ? nil : params[:filter_roles].to_sym,
+      :filter_roles =&gt; params[:filter_roles].blank? ? nil : (params[:filter_roles].is_a?(Array) ? params[:filter_roles].map(&amp;:to_sym) : [params[:filter_roles].to_sym]),
       :filter_contexts =&gt; params[:filter_contexts].blank? ? nil : params[:filter_contexts].to_sym,
-      :highlight_privilege =&gt; params[:highlight_privilege].blank? ? nil : params[:highlight_privilege].to_sym
+      :highlight_privilege =&gt; params[:highlight_privilege].blank? ? nil : params[:highlight_privilege].to_sym,
+      :changes =&gt; deserialize_changes(params[:changes])
     }
   end
+
+  def deserialize_changes (changes)
+    if changes
+      changes.split(';').collect do |info|
+        info.split(',').collect do |info_part|
+          info_part[0,1] == ':' ? info_part[1..-1].to_sym : info_part
+        end
+      end
+    end
+  end
 end
 
 else</diff>
      <filename>app/controllers/authorization_rules_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,7 @@ module AuthorizationRulesHelper
   end
 
   def policy_analysis_hints (marked_up, policy_data)
-    analyzer = Authorization::Analyzer.new(controller.authorization_engine)
+    analyzer = Authorization::DevelopmentSupport::Analyzer.new(controller.authorization_engine)
     analyzer.analyze(policy_data)
     marked_up_by_line = marked_up.split(&quot;\n&quot;)
     reports_by_line = analyzer.reports.inject({}) do |memo, report|
@@ -46,6 +46,7 @@ module AuthorizationRulesHelper
   
   def navigation
     link_to(&quot;Rules&quot;, authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
+    link_to(&quot;Edit&quot;, change_authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
     link_to(&quot;Graphical view&quot;, graph_authorization_rules_path) &lt;&lt; ' | ' &lt;&lt;
     link_to(&quot;Usages&quot;, authorization_usages_path) #&lt;&lt; ' | ' &lt;&lt;
   #  'Edit | ' &lt;&lt;
@@ -53,20 +54,76 @@ module AuthorizationRulesHelper
   end
   
   def role_color (role, fill = false)
-    fill_colors = %w{#ffdddd #ddffdd #ddddff #ffffdd #ffddff #ddffff}
-    colors = %w{#dd0000 #00dd00 #0000dd #dddd00 #dd00dd #00dddd}
-    @@role_colors ||= {}
-    @@role_colors[role] ||= begin
-      idx = @@role_colors.length % colors.length
-      [colors[idx], fill_colors[idx]]
+    if @has_changes
+      if has_changed(:add_role, role)
+        fill ? '#ddffdd' : '#000000'
+      elsif has_changed(:remove_role, role)
+        fill ? '#ffdddd' : '#000000'
+      else
+        fill ? '#ffffff' : '#000000'
+      end
+    else
+      fill_colors = %w{#ffdddd #ddffdd #ddddff #ffffdd #ffddff #ddffff}
+      colors = %w{#dd0000 #00dd00 #0000dd #dddd00 #dd00dd #00dddd}
+      @@role_colors ||= {}
+      @@role_colors[role] ||= begin
+        idx = @@role_colors.length % colors.length
+        [colors[idx], fill_colors[idx]]
+      end
+      @@role_colors[role][fill ? 1 : 0]
     end
-    @@role_colors[role][fill ? 1 : 0]
   end
   
   def role_fill_color (role)
     role_color(role, true)
   end
 
+  def privilege_color (privilege, context, role)
+    has_changed(:add_privilege, privilege, context, role) ? '#00dd00' :
+        (has_changed(:remove_privilege, privilege, context, role) ? '#dd0000' :
+          role_color(role))
+  end
+
+  def available_privileges
+    controller.authorization_engine.auth_rules.collect {|rule| rule.privileges.to_a}.flatten.uniq.map(&amp;:to_s).sort
+  end
+  def available_contexts
+    controller.authorization_engine.auth_rules.collect {|rule| rule.contexts.to_a}.flatten.uniq.map(&amp;:to_s).sort
+  end
+  def describe_step (step)
+    case step[0]
+    when :add_privilege
+      &quot;Add privilege &lt;strong&gt;#{h step[1].inspect} #{h step[2].inspect}&lt;/strong&gt; to role &lt;strong&gt;#{h step[3].to_sym.inspect}&lt;/strong&gt;&quot;
+    when :add_role
+      &quot;New role &lt;strong&gt;#{h step[1].to_sym.inspect}&lt;/strong&gt;&quot;
+    when :assign_role_to_user
+      &quot;Assign role &lt;strong&gt;#{h step[1].to_sym.inspect}&lt;/strong&gt; to &lt;strong&gt;#{h readable_step_info(step[2])}&lt;/strong&gt;&quot;
+    else
+      step.collect {|info| readable_step_info(info) }.map {|str| h str } * ', '
+    end
+  end
+  
+  def readable_step_info (info)
+    case info
+    when Symbol   then info.inspect
+    when User     then info.login
+    else               info.to_sym.inspect
+    end
+  end
+
+  def serialize_changes (approach)
+    approach.changes.collect {|step| step.collect {|info| readable_step_info(info) } * ','} * ';'
+  end
+
+  def serialize_relevant_roles (approach)
+    {:filter_roles =&gt; (Authorization::DevelopmentSupport::AnalyzerEngine.relevant_roles(approach.engine, approach.users).
+        map(&amp;:to_sym) + [:new_role_for_change_analyzer]).uniq}.to_param
+  end
+
+  def has_changed (*args)
+    @changes &amp;&amp; @changes[args[0]] &amp;&amp; @changes[args[0]].include?(args[1..-1])
+  end
+
   def auth_usage_info_classes (auth_info)
     classes = []
     if auth_info[:controller_permissions]</diff>
      <filename>app/helpers/authorization_rules_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -43,7 +43,7 @@ digraph rules {
 
   &lt;% @roles.each do |role| %&gt;
     &lt;% (@role_privs[role] || []).each do |context, privilege, unconditionally, attribute_string| %&gt;
-  &quot;&lt;%= role.inspect %&gt;&quot; -&gt; &lt;%=  privilege %&gt;_&lt;%= context %&gt; [color=&quot;&lt;%= role_color(role) %&gt;&quot;, minlen=3&lt;%= &quot;, arrowhead=opendot, URL=\&quot;javascript:\&quot;, edgetooltip=\&quot;#{attribute_string.gsub('&quot;','')}\&quot;&quot; unless unconditionally %&gt;]
+  &quot;&lt;%= role.inspect %&gt;&quot; -&gt; &lt;%=  privilege %&gt;_&lt;%= context %&gt; [color=&quot;&lt;%= privilege_color(privilege, context, role) %&gt;&quot;, minlen=3&lt;%= &quot;, arrowhead=opendot, URL=\&quot;javascript:\&quot;, edgetooltip=\&quot;#{attribute_string.gsub('&quot;','')}\&quot;&quot; unless unconditionally %&gt;]
     &lt;% end %&gt;
   &lt;% end %&gt;
 }
\ No newline at end of file</diff>
      <filename>app/views/authorization_rules/graph.dot.erb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 &lt;h1&gt;Authorization Usage&lt;/h1&gt;
 &lt;div style=&quot;margin: 1em;border:1px solid #ccc;max-width:50%;position:fixed;right:0;display:none&quot;&gt;
-&lt;object id=&quot;graph&quot; data=&quot;&lt;%= url_for :format =&gt; 'svg' %&gt;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width:100%&quot;/&gt;
+&lt;object id=&quot;graph&quot; data=&quot;&quot; type=&quot;image/svg+xml&quot; style=&quot;max-width:100%&quot;/&gt;
 &lt;/div&gt;
 &lt;p&gt;Filter rules in actions by controller:&lt;/p&gt;
 &lt;p&gt;&lt;%= navigation %&gt;&lt;/p&gt;</diff>
      <filename>app/views/authorization_usages/index.html.erb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,7 @@
 ActionController::Routing::Routes.draw do |map|
   if Authorization::activate_authorization_rules_browser?
-    map.resources :authorization_rules, :only =&gt; :index, :collection =&gt; {:graph =&gt; :get}
+    map.resources :authorization_rules, :only =&gt; [:index], 
+        :collection =&gt; {:graph =&gt; :get, :change =&gt; :get, :suggest_change =&gt; :get}
     map.resources :authorization_usages, :only =&gt; :index
   end
 end
\ No newline at end of file</diff>
      <filename>config/routes.rb</filename>
    </modified>
    <modified>
      <diff>@@ -15,6 +15,8 @@ module Authorization
 
     # Ideas for improvement
     # * moving rules up in the role hierarchy
+    # * merging roles
+    # * role hierarchy
     #
     class Analyzer &lt; AbstractAnalyzer
       def analyze (rules)</diff>
      <filename>lib/declarative_authorization/development_support/analyzer.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,28 +5,27 @@ module Authorization
   module DevelopmentSupport
     # Ideas for improvement
     # * Algorithm
-    #   * analyze tests for conflicts
+    #   * Plan by tackling each condition separately
     #     * e.g. two users have a permission through the same role,
     #       one should lose that
     #   * Consider privilege hierarchy
     #   * Consider merging, splitting roles, role hierarchies
-    #   * Consider adding privilege to existing rules
+    #   * Add privilege to existing rules
     # * Features
-    #   * UI for selecting intention, defining success tests,
-    #     reviewing and choosing best option
-    #   * Show consequences from changes:
-    #     * compare graphs (new edges, removed edges)
-    # * AI: decision tree, heuristic (backtracking?);
+    #   * Show consequences from changes: which users are affected,
+    #     show users in graph
+    #   * restructure GUI layout: more room for analyzing suggestions
+    # * AI: planning: ADL-like, actions with preconditions and effects
     # * Removing need of intention
     # * Evaluation of approaches with Analyzer algorithms
     # * Consider constraints
     #
+    # NOTE:
+    # * user.clone needs to clone role_symbols
+    # * user.role_symbols needs to respond to &lt;&lt;
+    # * user.login is needed
+    #
     class ChangeAnalyzer &lt; AbstractAnalyzer
-      attr_reader :engine
-
-      def initialize (engine)
-        @engine = engine
-      end
 
       def find_approaches_for (change_action, type, options, &amp;tests)
         raise ArgumentError, &quot;Missing options&quot; if !options[:on] or !options[:to]
@@ -48,7 +47,7 @@ module Authorization
         viable_approaches = []
         approach_checker = ApproachChecker.new(self, tests)
 
-        starting_candidate = Approach.new(self, @engine, options[:users], [])
+        starting_candidate = Approach.new(@engine, options[:users], [])
         if starting_candidate.check(approach_checker)
           viable_approaches &lt;&lt; starting_candidate
         else
@@ -61,6 +60,9 @@ module Authorization
               options[:on], strategy)
           step_count += 1
         end
+
+        # remove subsets
+
         viable_approaches.sort!
       end
 
@@ -92,24 +94,30 @@ module Authorization
 
       class Approach
         attr_reader :steps, :engine, :users
-        def initialize (analyzer, engine, users, steps)
-          @analyzer, @engine, @users, @steps = analyzer, engine, users, steps
+        def initialize (engine, users, steps)
+          @engine, @users, @steps = engine, users, steps
         end
 
         def check (approach_checker)
           res = approach_checker.check(@engine, @users)
           @failed_test_count = approach_checker.failed_test_count
+          #puts &quot;CHECKING #{inspect} (#{res}, #{sort_value})&quot;
           res
         end
 
         def clone_for_step (*step_params)
-          self.class.new(@analyzer, @engine.clone, @users.clone, @steps + [step_params])
+          self.class.new(@engine.clone, @users.clone, @steps + [Step.new(step_params)])
         end
 
         def changes
           @steps.select {|step| step.length &gt; 1}
         end
 
+        def subset? (other_approach)
+          other_approach.changes.length &gt;= changes.length &amp;&amp;
+              changes.all? {|step| other_approach.changes.any? {|step_2| step_2.eql?(step)} }
+        end
+
         def state_hash
           @engine.auth_rules.inject(0) do |memo, rule|
             memo + rule.privileges.hash + rule.contexts.hash +
@@ -121,12 +129,13 @@ module Authorization
         end
 
         def sort_value
-          (changes.length + 1) * (@failed_test_count.to_i + 1)
+          (changes.length + 1) + steps.length / 2 + (@failed_test_count.to_i + 1)
         end
 
         def inspect
-          &quot;Approach (#{state_hash}): Roles: #{@analyzer.roles(@engine).map(&amp;:to_sym).inspect}; &quot; +
-              &quot;Users: #{@users.map(&amp;:role_symbols).inspect}&quot;
+          &quot;Approach (#{state_hash}): Steps: #{changes.map(&amp;:inspect) * ', '}&quot;# +
+             # &quot;\n  Roles: #{AnalyzerEngine.roles(@engine).map(&amp;:to_sym).inspect}; &quot; +
+             # &quot;\n  Users: #{@users.map(&amp;:role_symbols).inspect}&quot;
         end
 
         def &lt;=&gt; (other)
@@ -134,6 +143,20 @@ module Authorization
         end
       end
 
+      class Step &lt; Array
+        def eql? (other)
+          other.is_a?(Array) &amp;&amp; other.length == length &amp;&amp;
+              (0...length).all? {|idx| self[idx].class == other[idx].class &amp;&amp;
+                  ((self[idx].respond_to?(:to_sym) &amp;&amp; self[idx].to_sym == other[idx].to_sym) ||
+                   (self[idx].respond_to?(:login) &amp;&amp; self[idx].login == other[idx].login) ||
+                   self[idx] == other[idx] ) }
+        end
+
+        def inspect
+          collect {|info| info.respond_to?(:to_sym) ? info.to_sym : (info.respond_to?(:login) ? info.login : info.class.name)}.inspect
+        end
+      end
+
       protected
       def next_step (viable_approaches, candidates, approach_checker,
             privilege, context, strategy)
@@ -154,15 +177,14 @@ module Authorization
         case next_in_strategy
         when :add_role
           # ensure non-existent name
-          role_symbol = :new_role_for_change_analyzer
-          unless candidate.engine.roles.include?(role_symbol)
-            approach = candidate.clone_for_step(:add_role, role_symbol)
-            approach.engine.roles &lt;&lt; role_symbol
+          approach = candidate.clone_for_step(:add_role, :new_role_for_change_analyzer)
+          if AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
+            #AnalyzerEngine.apply_change(approach.engine, [:add_privilege, privilege, context, :new_role_for_change_analyzer])
             new_approaches &lt;&lt; approach
           end
         when :assign_role_to_user
           candidate.users.each do |user|
-            roles(candidate.engine).each do |role|
+            relevant_roles(candidate).each do |role|
               next if user.role_symbols.include?(role.to_sym)
               approach = candidate.clone_for_step(:assign_role_to_user, role, user)
               # beware of shallow copies!
@@ -186,23 +208,15 @@ module Authorization
             end
           end
         when :add_privilege
-          roles(candidate.engine).each do |role|
+          relevant_roles(candidate).each do |role|
             approach = candidate.clone_for_step(:add_privilege, privilege, context, role)
-            approach.engine.auth_rules &lt;&lt; AuthorizationRule.new(role.to_sym,
-                [privilege], [context])
+            AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
             new_approaches &lt;&lt; approach
           end
         when :remove_privilege
-          roles(candidate.engine).each do |role|
+          relevant_roles(candidate).each do |role|
             approach = candidate.clone_for_step(:remove_privilege, privilege, context, role)
-            rule_with_priv = roles(approach.engine).
-                find {|cloned_role| cloned_role.to_sym == role.to_sym}.
-                rules.find do |rule|
-              rule.contexts.include?(context) and rule.privileges.include?(privilege)
-            end
-            if rule_with_priv
-              rule_with_priv.privileges.delete(privilege)
-              approach.engine.auth_rules.delete(rule_with_priv) if rule_with_priv.privileges.empty?
+            if AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
               new_approaches &lt;&lt; approach
             end
           end
@@ -212,14 +226,24 @@ module Authorization
 
         new_approaches.each do |new_approach|
           if new_approach.check(approach_checker)
-            viable_approaches &lt;&lt; new_approach unless viable_approaches.find {|v_a| v_a.state_hash == new_approach.state_hash}
+            unless viable_approaches.any? {|viable_approach| viable_approach.subset?(new_approach) }
+              #puts &quot;New: #{new_approach.changes.inspect}\n  #{viable_approaches.map(&amp;:changes).inspect}&quot;
+              viable_approaches.delete_if {|viable_approach| new_approach.subset?(viable_approach)}
+              viable_approaches &lt;&lt; new_approach unless viable_approaches.find {|v_a| v_a.state_hash == new_approach.state_hash}
+            end
           else
             candidates &lt;&lt; new_approach
           end
         end
 
         candidates.sort!
-        #p candidates.map(&amp;:sort_value)
+      end
+
+      def relevant_roles (approach)
+        #return AnalyzerEngine.roles(approach.engine)
+        (AnalyzerEngine.relevant_roles(approach.engine, approach.users) +
+            (approach.engine.roles.include?(:new_role_for_change_analyzer) ?
+               [AnalyzerEngine::Role.for_sym(:new_role_for_change_analyzer, approach.engine)] : [])).uniq
       end
     end
   end</diff>
      <filename>lib/declarative_authorization/development_support/change_analyzer.rb</filename>
    </modified>
    <modified>
      <diff>@@ -8,29 +8,84 @@ module Authorization
         @engine = engine
       end
 
-      def roles (specific_engine = nil)
-        specific_engine ||= engine
-        rules_by_role = specific_engine.auth_rules.inject({}) do |memo, rule|
+      def roles
+        AnalyzerEngine.roles(engine)
+      end
+
+      def rules
+        roles.collect {|role| role.rules }.flatten
+      end
+    end
+
+    # Groups utility methods and classes to better work with authorization object
+    # model.
+    module AnalyzerEngine
+
+      def self.roles (engine)
+        rules_by_role = engine.auth_rules.inject({}) do |memo, rule|
           memo[rule.role] ||= []
           memo[rule.role] &lt;&lt; rule
           memo
         end
-        specific_engine.roles.collect do |role|
+        engine.roles.collect do |role|
           Role.new(role, (rules_by_role[role] || []).
-                collect {|rule| Rule.new(rule, self)})
+                collect {|rule| Rule.new(rule, engine)}, engine)
         end
       end
 
-      def rules
-        roles.collect {|role| role.rules }.flatten
+      def self.relevant_roles (engine, users)
+        users.collect {|user| user.role_symbols.map {|role_sym| Role.for_sym(role_sym, engine)}}.
+            flatten.uniq.collect {|role| [role] + role.ancestors}.flatten.uniq
+      end
+
+      def self.rule_for_permission (engine,  privilege, context, role)
+        AnalyzerEngine.roles(engine).
+              find {|cloned_role| cloned_role.to_sym == role.to_sym}.rules.find do |rule|
+            rule.contexts.include?(context) and rule.privileges.include?(privilege)
+          end
+      end
+
+      def self.apply_change (engine, change)
+        case change[0]
+        when :add_role
+          role_symbol = change[1]
+          if engine.roles.include?(role_symbol)
+            false
+          else
+            engine.roles &lt;&lt; role_symbol
+            true
+          end
+        when :add_privilege
+          privilege, context, role = change[1,3]
+          if rule_for_permission(engine, privilege, context, role)
+            false
+          else
+            engine.auth_rules &lt;&lt; AuthorizationRule.new(role.to_sym,
+                [privilege], [context])
+            true
+          end
+        when :remove_privilege
+          privilege, context, role = change[1,3]
+          rule_with_priv = rule_for_permission(engine, privilege, context, role)
+          if rule_with_priv
+            rule_with_priv.privileges.delete(privilege)
+            engine.auth_rules.delete(rule_with_priv) if rule_with_priv.privileges.empty?
+            true
+          else
+            false
+          end
+        end
       end
 
       class Role
+        @@role_objects = {}
         attr_reader :role, :rules
-        def initialize (role, rules)
+        def initialize (role, rules, engine)
           @role = role
           @rules = rules
+          @engine = engine
         end
+
         def source_line
           @rules.empty? ? nil : @rules.first.source_line
         end
@@ -38,41 +93,50 @@ module Authorization
           @rules.empty? ? nil : @rules.first.source_file
         end
 
+        def ancestors (role_symbol = nil)
+          role_symbol ||= @role
+          (@engine.role_hierarchy[role_symbol] || []).
+              collect {|lower_priv| ancestors(lower_priv) }.flatten +
+            (role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
+        end
+
         def to_sym
           @role
         end
+        def self.for_sym (role_sym, engine)
+          @@role_objects[[role_sym, engine]] ||= new(role_sym, nil, engine)
+        end
       end
 
       class Rule
         @@rule_objects = {}
         delegate :source_line, :source_file, :contexts, :to =&gt; :@rule
         attr_reader :rule
-        def initialize (rule, analyzer)
+        def initialize (rule, engine)
           @rule = rule
-          @analyzer = analyzer
+          @engine = engine
         end
         def privileges
-          PrivilegesSet.new(self, @analyzer, @rule.privileges.collect {|privilege| Privilege.for_sym(privilege, @analyzer) })
+          PrivilegesSet.new(self, @engine, @rule.privileges.collect {|privilege| Privilege.for_sym(privilege, @engine) })
         end
-
-        def self.for_sym (rule_sym, analyzer)
-          @@rule_objects[[rule_sym, analyzer]] ||= new(rule_sym, analyzer)
+        def self.for_rule (rule, engine)
+          @@rule_objects[[rule, engine]] ||= new(rule, engine)
         end
       end
 
       class Privilege
         @@privilege_objects = {}
-        def initialize (privilege, analyzer)
+        def initialize (privilege, engine)
           @privilege = privilege
-          @analyzer = analyzer
+          @engine = engine
         end
 
         def ancestors (priv_symbol = nil)
           priv_symbol ||= @privilege
           # context-specific?
-          (@analyzer.engine.rev_priv_hierarchy[[priv_symbol, nil]] || []).
+          (@engine.rev_priv_hierarchy[[priv_symbol, nil]] || []).
               collect {|lower_priv| ancestors(lower_priv) }.flatten +
-            (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @analyzer)])
+            (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
         end
 
         def rules
@@ -88,14 +152,14 @@ module Authorization
         def to_sym
           @privilege
         end
-        def self.for_sym (privilege_sym, analyzer)
-          @@privilege_objects[[privilege_sym, analyzer]] ||= new(privilege_sym, analyzer)
+        def self.for_sym (privilege_sym, engine)
+          @@privilege_objects[[privilege_sym, engine]] ||= new(privilege_sym, engine)
         end
 
         private
         def find_rules_for_privilege
-          @analyzer.engine.auth_rules.select {|rule| rule.privileges.include?(@privilege)}.
-              collect {|rule| Rule.for_sym(rule, @analyzer)}
+          @engine.auth_rules.select {|rule| rule.privileges.include?(@privilege)}.
+              collect {|rule| Rule.for_rule(rule, @engine)}
         end
       end
 
@@ -103,7 +167,7 @@ module Authorization
         def initialize (*args)
           if args.length &gt; 2
             @rule = args.shift
-            @analyzer = args.shift
+            @engine = args.shift
           end
           super(*args)
         end
@@ -129,7 +193,7 @@ module Authorization
 
         private
         def privilege_from_symbol (privilege_sym)
-          Privilege.for_sym(privilege_sym, @analyzer)
+          Privilege.for_sym(privilege_sym, @engine)
         end
       end
     end</diff>
      <filename>lib/declarative_authorization/development_support/development_support.rb</filename>
    </modified>
    <modified>
      <diff>@@ -190,10 +190,34 @@ class AuthorizationRulesAnalyzerTest &lt; Test::Unit::TestCase
       end
     }
 
-    priv = Authorization::DevelopmentSupport::AbstractAnalyzer::Privilege.for_sym(:test, analyzer)
+    priv = Authorization::DevelopmentSupport::AnalyzerEngine::Privilege.for_sym(:test, engine)
     assert_equal 2, priv.rules.length
   end
 
+  def test_relevant_roles
+    reader = Authorization::Reader::DSLReader.new
+    reader.parse %{
+      authorization do
+        role :test_role do
+        end
+        role :test_role_2 do
+          includes :test_role
+        end
+        role :test_role_3 do
+        end
+        role :test_role_4 do
+        end
+        role :irrelevant_role do
+        end
+      end
+    }
+    engine = Authorization::Engine.new(reader)
+
+    users = [MockUser.new(:test_role_2, :test_role_3), MockUser.new(:test_role_4)]
+    relevant_roles = Authorization::DevelopmentSupport::AnalyzerEngine.relevant_roles(engine, users)
+    assert_equal 4, relevant_roles.length
+  end
+
   def test_analyze_for_proposed_privilege_hierarchy
     engine, analyzer = engine_analyzer_for %{
       authorization do</diff>
      <filename>test/development_support/analyzer_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -88,6 +88,34 @@ class AuthorizationRulesAnalyzerTest &lt; Test::Unit::TestCase
     #assert_equal :test_role_2, approaches.first.target.to_sym
   end
 
+  def test_adding_permission_with_new_role_complex
+    reader = Authorization::Reader::DSLReader.new
+    reader.parse %{
+      authorization do
+        role :lower_role do
+        end
+        role :test_role do
+          includes :lower_role
+        end
+      end
+    }
+    engine = Authorization::Engine.new(reader)
+    analyzer = Authorization::DevelopmentSupport::ChangeAnalyzer.new(engine)
+
+    user_to_extend_permissions = MockUser.new(:test_role)
+    another_user = MockUser.new(:test_role)
+
+    approaches = analyzer.find_approaches_for(:add, :permission, :on =&gt; :permissions,
+        :to =&gt; :read, :users =&gt; [another_user, user_to_extend_permissions]) do
+      assert permit?(:read, :context =&gt; :permissions, :user =&gt; users[1])
+      assert !permit?(:read, :context =&gt; :permissions, :user =&gt; users[0])
+    end
+
+    assert_not_equal 0, approaches.length
+    #assert_equal :role, approaches.first.target_type
+    #assert_equal :test_role_2, approaches.first.target.to_sym
+  end
+
   def test_removing_permission
     reader = Authorization::Reader::DSLReader.new
     reader.parse %{</diff>
      <filename>test/development_support/change_analyzer_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>0e8d55c16c2197c89c2f57d6346d4023e7e61f7b</id>
    </parent>
  </parents>
  <author>
    <name>Steffen Bartsch</name>
    <email>sbartsch@tzi.org</email>
  </author>
  <url>http://github.com/stffn/declarative_authorization/commit/370ff5aea639ce4a9e2229f4f7ffd7fce41eef05</url>
  <id>370ff5aea639ce4a9e2229f4f7ffd7fce41eef05</id>
  <committed-date>2009-05-15T03:15:47-07:00</committed-date>
  <authored-date>2009-04-09T01:03:58-07:00</authored-date>
  <message>GUI for change analyzer</message>
  <tree>23465590a50ca3cae8a7bd0ce7eaf5d3130a47bb</tree>
  <committer>
    <name>Steffen Bartsch</name>
    <email>sbartsch@tzi.org</email>
  </committer>
</commit>
