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 =
Rails.root.join('config/analytics/data_domains.yml').freeze

Class Method Summary collapse

Class Method Details

.allowed_objects_for(account:) ⇒ Object

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



28
29
30
31
32
33
# File 'app/services/assistant/data_domain_policy.rb', line 28

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

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

.configObject

── Configuration loading ──────────────────────────────────────────



108
109
110
111
112
113
114
# File 'app/services/assistant/data_domain_policy.rb', line 108

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

.domain_descriptions_for(account:) ⇒ Object

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



58
59
60
61
62
63
64
65
66
# File 'app/services/assistant/data_domain_policy.rb', line 58

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:) ⇒ Object

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



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/services/assistant/data_domain_policy.rb', line 36

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!Object



128
129
130
# File 'app/services/assistant/data_domain_policy.rb', line 128

def reset_config!
  @config = nil
end

.tool_service_descriptions_for(account:) ⇒ Object

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



96
97
98
99
100
101
102
103
104
# File 'app/services/assistant/data_domain_policy.rb', line 96

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:) ⇒ Object

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



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'app/services/assistant/data_domain_policy.rb', line 72

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
  else
    # No explicit mapping — use fallback
    if .is_manager?
      Array(config.fetch('default_manager_tool_services', []))
    else
      Array(config.fetch('default_employee_tool_services', []))
    end
  end
end