Module: Parse::Agent::MetadataRegistry
- Extended by:
- MetadataRegistry
- Included in:
- MetadataRegistry
- Defined in:
- lib/parse/agent/metadata_registry.rb
Overview
Registry module that enriches server schemas with local model metadata. Merges class descriptions, property descriptions, and agent-allowed methods from registered Parse::Object models into the schema data returned by the agent.
Constant Summary collapse
- ALWAYS_KEEP_FIELDS =
Fields that always pass through the agent_fields allowlist filter. These carry semantic meaning the LLM needs even when not explicitly listed as analytics-relevant.
%w[objectId createdAt updatedAt].freeze
- NOISY_FIELD_METADATA =
Per-field metadata keys that bloat the agent schema response without helping analytics queries. Dropped before the schema reaches the LLM.
%w[indexed].freeze
Instance Method Summary collapse
-
#accessible?(class_name) ⇒ Boolean
Check whether a class name is accessible to agent tools.
-
#agent_methods(class_name, agent_permission: :readonly) ⇒ Hash<Symbol, Hash>
Get agent methods for a Parse class filtered by permission.
-
#allow_collscan?(class_name) ⇒ Boolean
Check whether COLLSCANs are explicitly permitted for the given class.
-
#canonical_filter(class_name) ⇒ Hash?
Look up the canonical “valid state” filter declared via ‘agent_canonical_filter` on the model class.
-
#class_description(class_name) ⇒ String?
Get the class description for a Parse class if registered.
-
#enriched_schema(class_name, server_schema, agent_permission: :readonly, edges: nil) ⇒ Hash
Enrich a server schema with local model metadata.
-
#enriched_schemas(server_schemas, agent_permission: :readonly) ⇒ Array<Hash>
Enrich multiple schemas at once.
-
#field_allowlist(class_name) ⇒ Array<String>?
Resolve the agent_fields allowlist for a Parse class name.
-
#filter_visible_schemas(schemas) ⇒ Array<Hash>
Filter schemas to only include visible classes.
- #finalize_join_projection(project, dropped, source) ⇒ Object private
-
#has_metadata?(class_name) ⇒ Boolean
Check if a model class has agent metadata.
-
#has_visible_classes? ⇒ Boolean
Check if any classes are registered as visible.
-
#hidden?(class_name) ⇒ Boolean
Check whether a class name is denied to agent tools.
-
#hidden_class_names ⇒ Array<String>
Class names (Parse class names) that are hidden from every agent tool.
-
#hidden_exception_for(class_name) ⇒ Symbol?
Look up the per-class hidden-exception scope (‘:master_key` or nil) for a Parse class name.
-
#hidden_name_set ⇒ Array<String>
All hidden-class name variants a caller might submit.
-
#hidden_name_variants_for(klass) ⇒ Array<String>
Compute the set of names a caller might use to reference ‘klass`.
-
#join_projection_fields(class_name) ⇒ Hash?
Resolve the wire-format projection set used when this class appears as an included pointer on another class’s query.
-
#property_descriptions(class_name) ⇒ Hash<Symbol, String>
Get property descriptions for a Parse class if registered.
-
#register_hidden_class(klass, except: nil) ⇒ Object
Register a class as hidden from agent tools (opt-in PII denial).
-
#register_tenant_scope(class_name, field, from:) ⇒ Object
Register a tenant scope rule for a class.
-
#register_tenant_scope_bypass(class_name, bypass_proc) ⇒ Object
Register a bypass proc for a class’s tenant scope.
-
#register_visible_class(klass) ⇒ Object
Register a class as visible to agents.
-
#resolve_tenant_scope(class_name, agent) ⇒ Hash?
Resolve the effective tenant scope for a class and agent.
-
#tenant_scope_bypassed?(class_name, agent) ⇒ Boolean
Check whether the given agent should bypass the tenant scope for a class.
-
#tenant_scope_rule(class_name) ⇒ Hash?
Return the tenant scope rule for a class name, or nil if none declared.
-
#unregister_hidden_class(klass) ⇒ Object
Reverse a prior ‘register_hidden_class` call.
-
#visible_class_names ⇒ Array<String>
Get visible class names (Parse class names).
-
#visible_classes ⇒ Array<Class>
Get all registered visible classes.
Instance Method Details
#accessible?(class_name) ⇒ Boolean
Check whether a class name is accessible to agent tools. Inverse of #hidden?. Use at tool-dispatch time to refuse access before any query hits Parse Server.
197 198 199 |
# File 'lib/parse/agent/metadata_registry.rb', line 197 def accessible?(class_name) !hidden?(class_name) end |
#agent_methods(class_name, agent_permission: :readonly) ⇒ Hash<Symbol, Hash>
Get agent methods for a Parse class filtered by permission.
520 521 522 523 524 |
# File 'lib/parse/agent/metadata_registry.rb', line 520 def agent_methods(class_name, agent_permission: :readonly) klass = find_model_class(class_name) return {} unless klass&.respond_to?(:agent_methods_for) klass.agent_methods_for() end |
#allow_collscan?(class_name) ⇒ Boolean
Check whether COLLSCANs are explicitly permitted for the given class. Returns true when the model declares ‘agent_allow_collscan true`, false otherwise (including when no model class is registered).
541 542 543 544 545 |
# File 'lib/parse/agent/metadata_registry.rb', line 541 def allow_collscan?(class_name) klass = find_model_class(class_name) return false unless klass&.respond_to?(:agent_allow_collscan?) klass.agent_allow_collscan? end |
#canonical_filter(class_name) ⇒ Hash?
Look up the canonical “valid state” filter declared via ‘agent_canonical_filter` on the model class. Returns nil when no filter is declared.
553 554 555 556 557 |
# File 'lib/parse/agent/metadata_registry.rb', line 553 def canonical_filter(class_name) klass = find_model_class(class_name) return nil unless klass&.respond_to?(:agent_canonical_filter_for_apply) klass.agent_canonical_filter_for_apply end |
#class_description(class_name) ⇒ String?
Get the class description for a Parse class if registered.
500 501 502 503 |
# File 'lib/parse/agent/metadata_registry.rb', line 500 def class_description(class_name) klass = find_model_class(class_name) klass&.respond_to?(:agent_description) ? klass.agent_description : nil end |
#enriched_schema(class_name, server_schema, agent_permission: :readonly, edges: nil) ⇒ Hash
Enrich a server schema with local model metadata.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/parse/agent/metadata_registry.rb', line 252 def enriched_schema(class_name, server_schema, agent_permission: :readonly, edges: nil) klass = find_model_class(class_name) return server_schema unless klass&.respond_to?(:has_agent_metadata?) && klass. schema = deep_dup(server_schema) # Add class description if klass.agent_description schema["description"] = klass.agent_description end # Add class-level analytics usage hint (distinct from description) if klass.respond_to?(:agent_usage) && klass.agent_usage schema["usage"] = klass.agent_usage end # Enrich fields with property descriptions if schema["fields"] && klass.property_descriptions.any? schema["fields"] = enrich_fields(schema["fields"], klass) end # Filter fields to the declared allowlist (plus always-on system fields). # When no allowlist is declared, leave the field set alone. # Delegates to field_allowlist so allowlist symbols declared as Ruby # property names (snake_case) are normalized to the wire-format column # names (camelCase or explicit `field:` alias) before comparing against # Parse Server's schema keys. Without this normalization a model with # `agent_fields :device_type` filters against `"device_type"`, but the # server schema carries `"deviceType"` and the field is silently # stripped. if schema["fields"] && (allowed = field_allowlist(class_name)) schema["fields"] = schema["fields"].select { |name, _| allowed.include?(name) } end # Strip noisy per-field metadata regardless of allowlist if schema["fields"] schema["fields"] = schema["fields"].transform_values do |config| next config unless config.is_a?(Hash) cleaned = config.reject { |k, _| NOISY_FIELD_METADATA.include?(k) } # Drop defaultValue if it's effectively empty (nil/empty string carry no signal) cleaned = cleaned.reject { |k, v| k == "defaultValue" && (v.nil? || v == "") } cleaned end end # Add agent-allowed methods (filtered by permission) available_methods = klass.agent_methods_for() if available_methods.any? schema["agent_methods"] = format_methods(available_methods) end # Surface the canonical "valid state" filter so an LLM that opts # out via `apply_canonical_filter: false` on a query can # reproduce the same predicate manually. The filter is applied # BY DEFAULT on `query_class`/`count_objects`/`aggregate`. canonical = klass.respond_to?(:agent_canonical_filter_for_apply) ? klass.agent_canonical_filter_for_apply : nil if canonical && canonical.any? schema["canonical_filter"] = canonical.dup end # Echo the wire-format `agent_fields` allowlist explicitly. The # registry already enforces the allowlist by stripping non-allowed # fields from `schema["fields"]`, but enforcement-by-omission left # an LLM guessing what it could write in `keys:` and led to # repeated refusals on storage-form column names (`_p_author`, # etc.). Listing the wire names alongside the trimmed fields hash # closes that gap. `ALWAYS_KEEP_FIELDS` (objectId/createdAt/ # updatedAt) is filtered out — those are always available and # would only noise up the echo. allowed = field_allowlist(class_name) if allowed && (allowed - ALWAYS_KEEP_FIELDS).any? schema["agent_fields"] = (allowed - ALWAYS_KEEP_FIELDS) end # Echo the narrower join projection (wire-format) when declared. # Tells the LLM "when I'm included as a pointer on another class's # query, you'll see these fields and nothing else" so it can plan # the include path without a follow-up `get_schema`. join_proj = join_projection_fields(class_name) if join_proj && (join_proj[:project] - ALWAYS_KEEP_FIELDS).any? schema["agent_join_fields"] = (join_proj[:project] - ALWAYS_KEEP_FIELDS) end # Embed this class's relationship edges (incoming/outgoing) so the LLM # sees pointer/relation context alongside fields. Keeps each schema # response self-contained without the cost of the full graph. per_class = Parse::Agent::RelationGraph.edges_for(class_name, edges) if per_class[:outgoing].any? || per_class[:incoming].any? schema["relations"] = { "outgoing" => per_class[:outgoing].map { |e| edge_summary(e) }, "incoming" => per_class[:incoming].map { |e| edge_summary(e) }, } end schema end |
#enriched_schemas(server_schemas, agent_permission: :readonly) ⇒ Array<Hash>
Enrich multiple schemas at once. Builds the relation graph exactly once and threads it through each per-schema enrichment so the combined call is O(classes) rather than O(classes^2).
489 490 491 492 493 494 |
# File 'lib/parse/agent/metadata_registry.rb', line 489 def enriched_schemas(server_schemas, agent_permission: :readonly) edges = Parse::Agent::RelationGraph.build server_schemas.map do |schema| enriched_schema(schema["className"], schema, agent_permission: , edges: edges) end end |
#field_allowlist(class_name) ⇒ Array<String>?
Resolve the agent_fields allowlist for a Parse class name. Returns an array of field-name strings including the always-keep system fields, or nil when the model has no allowlist declared (callers should treat nil as “no filtering — return everything”).
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/parse/agent/metadata_registry.rb', line 357 def field_allowlist(class_name) klass = find_model_class(class_name) return nil unless klass&.respond_to?(:agent_field_allowlist) allowlist = klass.agent_field_allowlist return nil if allowlist.empty? # Translate each allowlist entry to its wire-format column name. # Priority: the class's field_map (Ruby symbol -> wire symbol) so # explicit `field:` aliases (`property :external_id, field: "ExtId"`) # resolve to the actual column. Fallback: `String#columnize` so plain # snake_case Ruby names (`:device_type` -> `"deviceType"`) match # Parse Server's lowerCamelCase wire format. Without this translation # the allowlist filter was case-sensitive against snake_case strings # and silently stripped legitimate camelCase columns from schema # enrichment, `keys:` projection, and pipeline policy enforcement. fmap = klass.respond_to?(:field_map) ? klass.field_map : {} resolved = allowlist.map do |name| mapped = fmap[name.to_sym] # When field_map carries an explicit wire name (e.g. a `property # :external_id, field: :ExternalReferenceCode` alias), use it # verbatim — columnize would lowercase the first character and # break the alias. Without a mapping, columnize the Ruby symbol # to convert snake_case to lowerCamelCase wire format. mapped ? mapped.to_s : name.to_s.columnize end # Defense-in-depth: refuse to surface Parse Server internal columns # (`_hashed_password`, `_session_token`, `_rperm`/`_wperm`, etc.) on # the agent surface, regardless of whether a developer accidentally # mapped a `property :pw, field: :_hashed_password` and listed it in # `agent_fields`. The columnize fallback already strips the leading # underscore for snake_case entries; this drop targets the wire-name # path that bypasses columnize. resolved.reject! { |wire| Parse::PipelineSecurity::INTERNAL_FIELDS_DENYLIST.include?(wire) } resolved | ALWAYS_KEEP_FIELDS end |
#filter_visible_schemas(schemas) ⇒ Array<Hash>
Filter schemas to only include visible classes. If no classes are marked visible, returns all schemas.
226 227 228 229 230 231 |
# File 'lib/parse/agent/metadata_registry.rb', line 226 def filter_visible_schemas(schemas) return schemas unless has_visible_classes? visible_names = visible_class_names schemas.select { |s| visible_names.include?(s["className"]) } end |
#finalize_join_projection(project, dropped, source) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
475 476 477 478 479 480 |
# File 'lib/parse/agent/metadata_registry.rb', line 475 def finalize_join_projection(project, dropped, source) project = (project | ALWAYS_KEEP_FIELDS) project.reject! { |wire| Parse::PipelineSecurity::INTERNAL_FIELDS_DENYLIST.include?(wire) } dropped = dropped.reject { |wire| Parse::PipelineSecurity::INTERNAL_FIELDS_DENYLIST.include?(wire) } { project: project, dropped: dropped, source: source } end |
#has_metadata?(class_name) ⇒ Boolean
Check if a model class has agent metadata.
530 531 532 533 |
# File 'lib/parse/agent/metadata_registry.rb', line 530 def (class_name) klass = find_model_class(class_name) klass&.respond_to?(:has_agent_metadata?) && klass. end |
#has_visible_classes? ⇒ Boolean
Check if any classes are registered as visible.
217 218 219 |
# File 'lib/parse/agent/metadata_registry.rb', line 217 def has_visible_classes? @visible_mutex.synchronize { @visible_classes.any? } end |
#hidden?(class_name) ⇒ Boolean
Check whether a class name is denied to agent tools.
An LLM writing aggregations against Parse-on-Mongo will naturally type system classes by their alias form (‘“User”`, `“Role”`, `“Installation”`, `“Session”`) even though the canonical `parse_class` is the `_`-prefixed form (`“_User”`, etc.). Similarly, a class declared with `parse_class “Foo”` lives in the registry as `“Foo”` but a caller might pass the Ruby class name.
#hidden_name_variants_for expands each registered hidden class to every form a caller might submit; this predicate is a pure string match against that expanded set. Closes the oracle where an LLM could write ‘$lookup: { from: “User” }` and bypass an `agent_hidden`-on-`Parse::User` because the registry only knew `“_User”`.
134 135 136 137 |
# File 'lib/parse/agent/metadata_registry.rb', line 134 def hidden?(class_name) return false if class_name.nil? hidden_name_set.include?(class_name.to_s) end |
#hidden_class_names ⇒ Array<String>
Class names (Parse class names) that are hidden from every agent tool.
110 111 112 113 114 |
# File 'lib/parse/agent/metadata_registry.rb', line 110 def hidden_class_names @hidden_mutex.synchronize { @hidden_classes.dup }.map do |klass| klass.respond_to?(:parse_class) ? klass.parse_class : klass.name end end |
#hidden_exception_for(class_name) ⇒ Symbol?
Look up the per-class hidden-exception scope (‘:master_key` or nil) for a Parse class name. Returns nil when the class is not hidden at all OR when it is hidden with no exception. Caller must compare against the agent’s auth context to decide whether the exception applies.
96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/parse/agent/metadata_registry.rb', line 96 def hidden_exception_for(class_name) return nil if class_name.nil? target = class_name.to_s @hidden_mutex.synchronize do @hidden_classes.each do |klass| next unless hidden_name_variants_for(klass).include?(target) return @hidden_exceptions[klass] end end nil end |
#hidden_name_set ⇒ Array<String>
All hidden-class name variants a caller might submit. Includes the canonical ‘parse_class`, the un-prefixed alias when `parse_class` starts with `_` (system-class form), and the Ruby class name when it differs from `parse_class` (`parse_class “Foo”` override). The `hidden_name_variants_for` helper MUST NOT take `@hidden_mutex` —it’s called from inside the synchronize block here, and recursive locking would deadlock.
147 148 149 150 151 |
# File 'lib/parse/agent/metadata_registry.rb', line 147 def hidden_name_set @hidden_mutex.synchronize do @hidden_classes.flat_map { |klass| hidden_name_variants_for(klass) }.uniq end end |
#hidden_name_variants_for(klass) ⇒ Array<String>
Compute the set of names a caller might use to reference ‘klass`.
Variants emitted:
-
‘parse_class` (canonical, always).
-
‘parse_class` stripped of a leading `_` (system-class alias form; e.g. `_User` -> `User`).
-
Ruby class name when it differs from ‘parse_class`.
**Known limitation — collision direction is safe but technically over-broad.** If application code declares one class with ‘parse_class “_Foo”` and also a separate class with `parse_class “Foo”`, hiding the `_Foo` class implicitly causes `hidden?(“Foo”)` to return true as well, refusing reads on the un-prefixed sibling. The refusal direction is the safer one (false positive on the gate, not a leak), and the collision is contrived enough — `_`-prefixed parse_class names are reserved in practice for Parse’s own system classes — that we accept the trade-off. Applications that genuinely need both can either rename one, or call ‘agent_hidden` on both explicitly.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/parse/agent/metadata_registry.rb', line 176 def hidden_name_variants_for(klass) variants = [] if klass.respond_to?(:parse_class) && klass.parse_class pc = klass.parse_class.to_s variants << pc variants << pc.sub(/\A_/, "") if pc.start_with?("_") end if klass.respond_to?(:name) && klass.name && !klass.name.include?("::") && !variants.include?(klass.name) # Skip names containing `::` -- those are Ruby constant paths # (e.g. `"Parse::User"`) that no LLM would write in a `$lookup`, # and including them only adds noise to `hidden_name_set`. variants << klass.name end variants end |
#join_projection_fields(class_name) ⇒ Hash?
Resolve the wire-format projection set used when this class appears as an included pointer on another class’s query. Drives the auto-projection that turns ‘keys: [“user”] + include: [“user”]` into `keys: “user,user.firstName,user.email,…”` server-side.
Resolution order (first match wins):
1. `agent_join_fields` → those entries (wire-format).
2. `agent_fields` declared → `agent_fields - agent_large_fields`.
3. Only `agent_large_fields` declared → all `field_map` properties
minus the large set.
4. None of the above → nil (no auto-projection; caller gets the
full included record exactly as Parse Server returns it).
The returned array always includes ‘ALWAYS_KEEP_FIELDS` (objectId / createdAt / updatedAt). Internal Parse Server columns (`_hashed_password`, `_session_token`, `_rperm`, etc.) are filtered at the end as a defense-in-depth pass, identical to #field_allowlist, so an accidental `property :pw, field: :_hashed_password` cannot leak through the join surface.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/parse/agent/metadata_registry.rb', line 420 def join_projection_fields(class_name) klass = find_model_class(class_name) return nil unless klass fmap = klass.respond_to?(:field_map) ? klass.field_map : {} to_wire = ->(sym) { mapped = fmap[sym.to_sym] mapped ? mapped.to_s : sym.to_s.columnize } large_wire = if klass.respond_to?(:agent_large_field_list) klass.agent_large_field_list.map(&to_wire) else [] end join_list = klass.respond_to?(:agent_join_field_list) ? klass.agent_join_field_list : [] if join_list.any? project = join_list.map(&to_wire) source = :join_fields # dropped: large fields that are NOT in the join projection. # The caller asked us to project to a narrow set; report large # fields they didn't include so they can re-ask explicitly. dropped = large_wire - project return finalize_join_projection(project, dropped, source) end allow_list = klass.respond_to?(:agent_field_allowlist) ? klass.agent_field_allowlist : [] if allow_list.any? allow_wire = allow_list.map(&to_wire) project = allow_wire - large_wire # If everything in the allowlist is also large, fall through # rather than projecting to an empty set (would surface a useless # `{}` user object). unless project.empty? dropped = large_wire & allow_wire return finalize_join_projection(project, dropped, :allowlist_minus_large) end end if large_wire.any? # Strip mode: no positive allowlist, but we know which fields are # heavy. Project to (declared properties - large fields). Limited # to fields the Ruby model knows about — server-side columns not # declared as `property` won't come back, but that's an honest # trade-off (we can only project what we can name). known_wire = fmap.values.map(&:to_s) project = known_wire - large_wire return nil if project.empty? dropped = large_wire & known_wire return finalize_join_projection(project, dropped, :field_map_minus_large) end nil end |
#property_descriptions(class_name) ⇒ Hash<Symbol, String>
Get property descriptions for a Parse class if registered.
509 510 511 512 513 |
# File 'lib/parse/agent/metadata_registry.rb', line 509 def property_descriptions(class_name) klass = find_model_class(class_name) return {} unless klass&.respond_to?(:property_descriptions) klass.property_descriptions || {} end |
#register_hidden_class(klass, except: nil) ⇒ Object
Register a class as hidden from agent tools (opt-in PII denial).
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/parse/agent/metadata_registry.rb', line 64 def register_hidden_class(klass, except: nil) @hidden_mutex.synchronize do @hidden_classes << klass unless @hidden_classes.include?(klass) if except.nil? @hidden_exceptions.delete(klass) else @hidden_exceptions[klass] = except end end end |
#register_tenant_scope(class_name, field, from:) ⇒ Object
Register a tenant scope rule for a class.
568 569 570 571 572 |
# File 'lib/parse/agent/metadata_registry.rb', line 568 def register_tenant_scope(class_name, field, from:) @tenant_scope_mutex.synchronize do @tenant_scope_rules[class_name.to_s] = { field: field.to_sym, from: from } end end |
#register_tenant_scope_bypass(class_name, bypass_proc) ⇒ Object
Register a bypass proc for a class’s tenant scope.
578 579 580 581 582 |
# File 'lib/parse/agent/metadata_registry.rb', line 578 def register_tenant_scope_bypass(class_name, bypass_proc) @tenant_scope_bypass_mutex.synchronize do @tenant_scope_bypasses[class_name.to_s] = bypass_proc end end |
#register_visible_class(klass) ⇒ Object
Register a class as visible to agents.
49 50 51 52 53 |
# File 'lib/parse/agent/metadata_registry.rb', line 49 def register_visible_class(klass) @visible_mutex.synchronize do @visible_classes << klass unless @visible_classes.include?(klass) end end |
#resolve_tenant_scope(class_name, agent) ⇒ Hash?
Resolve the effective tenant scope for a class and agent.
Returns nil when:
- No agent_tenant_scope is declared for this class (back-compat pass-through).
- The bypass condition is satisfied (admin agents, etc.).
Returns { field: Symbol, value: Object } when a scope should be enforced.
Raises Parse::Agent::AccessDenied when:
- A scope rule is declared and the bypass is not satisfied, but the
agent's scope value (from: proc) returns nil — meaning the agent
has no tenant binding and must not touch this class.
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 |
# File 'lib/parse/agent/metadata_registry.rb', line 626 def resolve_tenant_scope(class_name, agent) rule = tenant_scope_rule(class_name) return nil unless rule return nil if tenant_scope_bypassed?(class_name, agent) value = rule[:from].call(agent) if value.nil? raise Parse::Agent::AccessDenied.new( class_name, "Agent has no tenant binding for class '#{class_name}' which requires tenant scoping", ) end { field: rule[:field], value: value } end |
#tenant_scope_bypassed?(class_name, agent) ⇒ Boolean
Check whether the given agent should bypass the tenant scope for a class. Returns false when no bypass is registered or when the bypass proc raises.
598 599 600 601 602 603 604 605 606 607 |
# File 'lib/parse/agent/metadata_registry.rb', line 598 def tenant_scope_bypassed?(class_name, agent) bypass = @tenant_scope_bypass_mutex.synchronize { @tenant_scope_bypasses[class_name.to_s] } return false unless bypass begin !!bypass.call(agent) rescue StandardError # A bypass proc that raises is treated as not-bypassed (fail closed). false end end |
#tenant_scope_rule(class_name) ⇒ Hash?
Return the tenant scope rule for a class name, or nil if none declared.
588 589 590 |
# File 'lib/parse/agent/metadata_registry.rb', line 588 def tenant_scope_rule(class_name) @tenant_scope_mutex.synchronize { @tenant_scope_rules[class_name.to_s] } end |
#unregister_hidden_class(klass) ⇒ Object
Reverse a prior ‘register_hidden_class` call. Used by `agent_unhidden` to re-expose a class that was marked hidden by an upstream declaration (typically a parse-stack built-in like `Parse::Product` or a base class in an application’s own model hierarchy). Removing the class from the registry is what actually allows ‘query_class` / `aggregate` / schema enumeration etc. to address it again — the per-class `@agent_hidden` ivar alone is not consulted by the tool surface.
83 84 85 86 87 88 |
# File 'lib/parse/agent/metadata_registry.rb', line 83 def unregister_hidden_class(klass) @hidden_mutex.synchronize do @hidden_classes.delete(klass) @hidden_exceptions.delete(klass) end end |
#visible_class_names ⇒ Array<String>
Get visible class names (Parse class names).
209 210 211 212 213 |
# File 'lib/parse/agent/metadata_registry.rb', line 209 def visible_class_names visible_classes.map do |klass| klass.respond_to?(:parse_class) ? klass.parse_class : klass.name end end |
#visible_classes ⇒ Array<Class>
Get all registered visible classes.
203 204 205 |
# File 'lib/parse/agent/metadata_registry.rb', line 203 def visible_classes @visible_mutex.synchronize { @visible_classes.dup } end |