Module: Assistant::DataDomainPolicy

Defined in:
app/services/assistant/data_domain_policy.rb

Overview

Resolves a user's CanCanCan roles into analytics data domains, then
returns the set of database objects (views/tables) they may query
through the AI assistant.

Domain descriptions and role mappings live in config/analytics/data_domains.yml.
Object-to-domain assignments live in each db/comments/*.yml manifest (domain: field).

Two-layer security model:
Layer 1 — DataDomainPolicy (this module): object-level access ("can you query this table?")
Layer 2 — CommentManifest restricted flags: column-level redaction ("is this column redacted?")

Usage:
Assistant::DataDomainPolicy.allowed_objects_for(account: current_account)

=> Set["view_sales_facts", "orders", "items", ...] or nil (admin = unrestricted)

Assistant::DataDomainPolicy.domains_for(account: current_account)

=> ["sales", "workforce"]

Constant Summary collapse

CONFIG_PATH =

Filesystem path for config.

Rails.root.join('config/analytics/data_domains.yml').freeze

Class Method Summary collapse

Class Method Details

.allowed_objects_for(account:) ⇒ Set<String>?

Returns nil for admins/executive_managers (unrestricted) or a Set of allowed object names.

Parameters:

  • account (Account)

    the current user's account

Returns:

  • (Set<String>, nil)

    allowed object names, or nil when unrestricted



32
33
34
35
36
37
# File 'app/services/assistant/data_domain_policy.rb', line 32

def allowed_objects_for(account:)
  return nil if unrestricted_access?()

  domain_names = domains_for(account: )
  resolve_objects(domain_names)
end

.configHash

Returns parsed data_domains.yml config.

Returns:

  • (Hash)

    parsed data_domains.yml config



123
124
125
126
127
128
129
# File 'app/services/assistant/data_domain_policy.rb', line 123

def config
  if Rails.env.local?
    load_config # Always re-read in dev/test for fast iteration
  else
    @config ||= load_config
  end
end

.domain_descriptions_for(account:) ⇒ Hash{String => String}

Returns a human-friendly hash of domain name => description for the user's domains.

Parameters:

  • account (Account)

    the current user's account

Returns:

  • (Hash{String => String})

    domain descriptions



68
69
70
71
72
73
74
75
76
# File 'app/services/assistant/data_domain_policy.rb', line 68

def domain_descriptions_for(account:)
  domain_names = domains_for(account: )
  domains = config.fetch('domains', {})

  domain_names.each_with_object({}) do |name, out|
    meta = domains[name]
    out[name] = meta['description'] if meta
  end
end

.domains_for(account:) ⇒ Array<String>

Returns an array of domain names the user has access to.

Parameters:

  • account (Account)

    the current user's account

Returns:

  • (Array<String>)

    domain names



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/services/assistant/data_domain_policy.rb', line 43

def domains_for(account:)
  return config.fetch('domains', {}).keys if unrestricted_access?()

  role_names = Array(.inherited_role_names).map { |r| r.to_s.downcase }
  mappings = config.fetch('role_mappings', {})

  # Collect domains from all the user's roles
  mapped = role_names.flat_map { |r| Array(mappings[r]) }

  if mapped.empty?
    # No explicit role mapping found — use fallback
    mapped = if .is_manager?
               default_manager_domains
             else
               Array(config.fetch('default_employee', ['sales']))
             end
  end

  mapped.uniq
end

.reset_config!void

This method returns an undefined value.



145
146
147
# File 'app/services/assistant/data_domain_policy.rb', line 145

def reset_config!
  @config = nil
end

.tool_service_descriptions_for(account:) ⇒ Hash{String => String}

Returns a hash of tool service key => description for the user's allowed services.

Parameters:

  • account (Account)

    the current user's account

Returns:

  • (Hash{String => String})

    tool service descriptions



110
111
112
113
114
115
116
117
118
# File 'app/services/assistant/data_domain_policy.rb', line 110

def tool_service_descriptions_for(account:)
  service_keys = tool_services_for(account: )
  services = config.fetch('tool_services', {})

  service_keys.each_with_object({}) do |key, out|
    meta = services[key]
    out[key] = meta['description'] if meta
  end
end

.tool_services_for(account:) ⇒ Array<String>

Returns an array of tool service keys the user can access.
Admins get all tool services. Others are resolved from their roles.

Parameters:

  • account (Account)

    the current user's account

Returns:

  • (Array<String>)

    allowed tool service keys



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/services/assistant/data_domain_policy.rb', line 85

def tool_services_for(account:)
  all_services = config.fetch('tool_services', {}).keys
  return all_services if .is_admin?

  role_names = Array(.inherited_role_names).map { |r| r.to_s.downcase }
  mappings = config.fetch('tool_service_mappings', {})

  # Collect services from all the user's roles
  mapped = role_names.flat_map { |r| Array(mappings[r]) if mappings.key?(r) }.compact.flatten

  if role_names.any? { |r| mappings.key?(r) }
    # At least one role has an explicit mapping (even if empty [])
    mapped.uniq
  elsif .is_manager?
    # No explicit mapping — use fallback
    Array(config.fetch('default_manager_tool_services', []))
  else
    Array(config.fetch('default_employee_tool_services', []))
  end
end