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 |