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/URL path for config.

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.



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

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

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

.configObject

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



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

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

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



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

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.



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

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



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

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.



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

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.



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

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