Module: Parse::Agent::Describe
- Included in:
- Parse::Agent
- Defined in:
- lib/parse/agent/describe.rb
Overview
Developer-facing introspection mixin. Mixed into Parse::Agent via ‘include Describe` so `agent.describe`, `agent.describe_for(class_name)`, and `agent.would_permit?(…)` are instance methods on every agent.
SECURITY POSTURE — this is operator-side observability, NOT data exposed to the LLM. The operator wrote every rule the helper echoes back; showing them their own configuration is just transparency. The output is NOT included in any tool response, MCP ‘tools/list`, or `parse.agent.tool_call` notification payload by default. If a deployment chooses to surface the output (e.g. via a debug HTTP endpoint), it should be auth-gated on the same boundary that authenticates the operator console.
The ‘session_token` value is NEVER returned verbatim. #auth_descriptor emits a stable SHA256-truncated fingerprint so two `describe` calls on the same session correlate, but the raw bearer token never leaves the method. Master-key mode is identified by the `:master_key` symbol only.
Instance Method Summary collapse
-
#describe(pretty: false) ⇒ Hash, String
Full introspection Hash for the agent.
-
#describe_for(class_name) ⇒ Hash
Per-class breakdown for a single Parse class.
-
#would_permit?(tool_name, class_name: nil, op: nil, method_name: nil, **_kwargs) ⇒ Hash
Dispatch-gate simulator.
Instance Method Details
#describe(pretty: false) ⇒ Hash, String
Full introspection Hash for the agent. Lists every layer that gates what the agent can see and do, plus per-class metadata for the classes the agent explicitly references.
33 34 35 36 |
# File 'lib/parse/agent/describe.rb', line 33 def describe(pretty: false) data = describe_hash pretty ? describe_pretty(data) : data end |
#describe_for(class_name) ⇒ Hash
Per-class breakdown for a single Parse class. Includes the agent’s effective reach for the class (visible? class-filter permitted? canonical filter? per-agent filter? tenant-scoped?) plus the class-level metadata declared via ‘agent_fields` / `agent_methods` / `agent_large_fields`. Useful when an agent has 30 visible classes and a developer is debugging one specific refusal.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/parse/agent/describe.rb', line 47 def describe_for(class_name) cn = if class_name.is_a?(Class) && class_name.respond_to?(:parse_class) class_name.parse_class else class_name.to_s end { class_name: cn, accessible: describe_class_accessibility(cn), agent_fields: class_field_allowlist(cn), agent_canonical_filter: Parse::Agent::MetadataRegistry.canonical_filter(cn), per_agent_filter: respond_to?(:filter_for) ? filter_for(cn) : nil, tenant_scope: class_tenant_scope(cn), large_fields: class_large_fields(cn), agent_methods: class_agent_method_names(cn), } end |
#would_permit?(tool_name, class_name: nil, op: nil, method_name: nil, **_kwargs) ⇒ Hash
Dispatch-gate simulator. Runs every accessibility check that the tool dispatcher would run, without actually invoking the tool. Lets a developer answer “why is this agent refusing this call?” in one line, without parsing the audit payload or tracing through the tool implementation.
TRACK-AGENT-8: mirrors the REAL dispatch gates in Parse::Agent#execute and Tools.assert_class_accessible!. The simulator now checks:
* tool filter (`tools:` kwarg / `tool_filter_*` sets) and
permission-tier membership
* env-gate (`PARSE_AGENT_ALLOW_WRITE_TOOLS` /
`PARSE_AGENT_ALLOW_RAW_CRUD` for write tools;
`PARSE_AGENT_ALLOW_SCHEMA_OPS` /
`PARSE_AGENT_ALLOW_RAW_SCHEMA` for schema tools)
* `class_name` accessibility, including hidden-class +
master-key-except, per-agent class allowlist, AND the
CLP `op:` gate (forwarded when an `op:` is supplied)
* `master_atlas?` opt-in gate for `atlas_faceted_search`
* `method_filtered?` for `call_method` when a
`method_name:` is supplied
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/parse/agent/describe.rb', line 99 def would_permit?(tool_name, class_name: nil, op: nil, method_name: nil, **_kwargs) tool_sym = tool_name.to_sym # Tool filter — present at the per-instance layer. Preserve # the historical `:tool_filtered` reason regardless of whether # the denial came from tier or instance filter, since the # describe consumer reads it as "this tool will be refused" # rather than as the dispatcher's split error_code. unless allowed_tools.include?(tool_sym) return { allowed: false, reason: :tool_filtered, denied_at: :allowed_tools } end # Env-gate for raw CRUD / schema-mutating tools. Mirrors the # gate in Parse::Agent#execute at line 1639-1662. if Parse::Agent::WRITE_GATED_TOOLS.include?(tool_sym) && !(Parse::Agent.write_tools_enabled? && Parse::Agent.raw_crud_enabled?) return { allowed: false, reason: :write_env_gate_disabled, denied_at: :write_env_gate } end if Parse::Agent::SCHEMA_GATED_TOOLS.include?(tool_sym) && !(Parse::Agent.schema_ops_enabled? && Parse::Agent.raw_schema_enabled?) return { allowed: false, reason: :schema_env_gate_disabled, denied_at: :schema_env_gate } end # atlas_faceted_search opt-in (master_atlas: true required — # see tools.rb:atlas_faceted_search). Mirrors the explicit # opt-in inside the tool body so the simulator doesn't # over-report :permitted for a session-bound agent. if tool_sym == :atlas_faceted_search && !(respond_to?(:master_atlas?) && master_atlas?) return { allowed: false, reason: :master_atlas_required, denied_at: :master_atlas_gate } end # Class access gate — when the tool takes a class_name argument. # Includes CLP `op:` check when the caller supplied one, # mirroring assert_class_accessible!'s signature. if class_name cn = class_name.is_a?(Class) && class_name.respond_to?(:parse_class) ? class_name.parse_class : class_name.to_s begin Parse::Agent::Tools.assert_class_accessible!(cn, agent: self, op: op) rescue Parse::Agent::AccessDenied => e kind = e.respond_to?(:kind) && e.kind ? e.kind : :access_denied return { allowed: false, reason: kind, denied_at: :assert_class_accessible! } rescue Parse::Agent::ValidationError return { allowed: false, reason: :invalid_argument, denied_at: :assert_class_accessible! } end end # method_filtered? — mirror the call_method gate at tools.rb:3948. # Only fires when the caller supplied a method_name AND the # tool is call_method (the method-filter only narrows that tool). if tool_sym == :call_method && method_name && class_name cn = class_name.is_a?(Class) && class_name.respond_to?(:parse_class) ? class_name.parse_class : class_name.to_s if respond_to?(:method_filtered?) && method_filtered?(method_name.to_sym, class_name: cn) return { allowed: false, reason: :method_filtered, denied_at: :method_filtered } end end { allowed: true } end |