parse-stack-next 5.5.6 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +634 -0
- data/README.md +378 -6
- data/docs/caching.md +748 -0
- data/examples/basic_client.rb +3 -3
- data/examples/basic_server.rb +3 -3
- data/examples/live_query_listener.rb +12 -12
- data/examples/rag_chatbot.rb +14 -14
- data/examples/transaction_example.rb +44 -45
- data/examples/webhook_server.rb +3 -3
- data/lib/parse/access.rb +493 -0
- data/lib/parse/acl_scope.rb +125 -52
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/cancellation_token.rb +3 -3
- data/lib/parse/agent/constraint_translator.rb +4 -4
- data/lib/parse/agent/describe.rb +34 -34
- data/lib/parse/agent/errors.rb +9 -9
- data/lib/parse/agent/mcp_client.rb +61 -59
- data/lib/parse/agent/mcp_dispatcher.rb +89 -101
- data/lib/parse/agent/mcp_rack_app.rb +92 -93
- data/lib/parse/agent/mcp_server.rb +1 -1
- data/lib/parse/agent/mcp_subscriptions.rb +22 -22
- data/lib/parse/agent/metadata_audit.rb +1 -2
- data/lib/parse/agent/metadata_dsl.rb +17 -7
- data/lib/parse/agent/metadata_registry.rb +12 -12
- data/lib/parse/agent/prompt_hardening.rb +4 -4
- data/lib/parse/agent/prompts.rb +10 -10
- data/lib/parse/agent/result_formatter.rb +4 -4
- data/lib/parse/agent/tools.rb +493 -400
- data/lib/parse/agent.rb +157 -116
- data/lib/parse/api/hooks.rb +1 -1
- data/lib/parse/api/server.rb +2 -2
- data/lib/parse/api/users.rb +2 -2
- data/lib/parse/atlas_search/index_manager.rb +1 -1
- data/lib/parse/atlas_search/session.rb +40 -218
- data/lib/parse/atlas_search.rb +181 -60
- data/lib/parse/authorization.rb +466 -0
- data/lib/parse/cache/invalidation.rb +167 -0
- data/lib/parse/cache/keyspace.rb +306 -0
- data/lib/parse/cache/moneta_surface.rb +126 -0
- data/lib/parse/cache/pool.rb +48 -5
- data/lib/parse/cache/redis.rb +425 -10
- data/lib/parse/cache/scoped_view.rb +443 -0
- data/lib/parse/cache/sub_cache.rb +264 -0
- data/lib/parse/cache/upstream_roles.rb +230 -0
- data/lib/parse/client/authentication.rb +1 -1
- data/lib/parse/client/body_builder.rb +1 -2
- data/lib/parse/client/caching.rb +84 -5
- data/lib/parse/client.rb +327 -189
- data/lib/parse/clp_scope.rb +225 -28
- data/lib/parse/console.rb +3 -3
- data/lib/parse/embeddings/batch_embedder.rb +3 -3
- data/lib/parse/embeddings/binding_audit.rb +211 -0
- data/lib/parse/embeddings/cache.rb +17 -17
- data/lib/parse/embeddings/cohere.rb +21 -22
- data/lib/parse/embeddings/image_fetch.rb +22 -21
- data/lib/parse/embeddings/jina.rb +21 -21
- data/lib/parse/embeddings/local_http.rb +6 -7
- data/lib/parse/embeddings/media_file.rb +137 -0
- data/lib/parse/embeddings/openai.rb +5 -5
- data/lib/parse/embeddings/provider.rb +40 -1
- data/lib/parse/embeddings/qwen.rb +10 -10
- data/lib/parse/embeddings/spend_cap.rb +2 -3
- data/lib/parse/embeddings/streaming_body.rb +170 -0
- data/lib/parse/embeddings/video_source.rb +121 -0
- data/lib/parse/embeddings/voyage.rb +554 -143
- data/lib/parse/embeddings.rb +83 -27
- data/lib/parse/graphql/type_generator.rb +1 -1
- data/lib/parse/graphql.rb +5 -5
- data/lib/parse/live_query/client.rb +1 -1
- data/lib/parse/live_query.rb +1 -1
- data/lib/parse/lock.rb +15 -18
- data/lib/parse/lock_backend.rb +1 -1
- data/lib/parse/lookup_rewriter.rb +6 -6
- data/lib/parse/model/classes/role.rb +227 -28
- data/lib/parse/model/classes/user.rb +60 -1
- data/lib/parse/model/clp.rb +4 -4
- data/lib/parse/model/core/actions.rb +97 -15
- data/lib/parse/model/core/create_lock.rb +0 -2
- data/lib/parse/model/core/describe.rb +57 -57
- data/lib/parse/model/core/embed_managed.rb +43 -19
- data/lib/parse/model/core/fetching.rb +2 -0
- data/lib/parse/model/core/indexing.rb +14 -14
- data/lib/parse/model/core/parse_reference.rb +1 -1
- data/lib/parse/model/core/properties.rb +39 -3
- data/lib/parse/model/core/querying.rb +1 -1
- data/lib/parse/model/core/schema.rb +2 -2
- data/lib/parse/model/core/search_indexing.rb +2 -2
- data/lib/parse/model/core/vector_searchable.rb +34 -8
- data/lib/parse/model/file.rb +21 -15
- data/lib/parse/model/geojson.rb +2 -2
- data/lib/parse/model/geopoint.rb +1 -0
- data/lib/parse/model/object.rb +132 -66
- data/lib/parse/model/pointer.rb +2 -0
- data/lib/parse/model/polygon.rb +3 -6
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/model/vector.rb +3 -1
- data/lib/parse/mongodb.rb +512 -282
- data/lib/parse/pipeline_security.rb +3 -0
- data/lib/parse/query/constraints.rb +46 -46
- data/lib/parse/query.rb +146 -59
- data/lib/parse/retrieval/agent_tool.rb +34 -20
- data/lib/parse/retrieval/chunk.rb +1 -0
- data/lib/parse/retrieval/reranker/cohere.rb +11 -11
- data/lib/parse/retrieval/reranker.rb +3 -4
- data/lib/parse/retrieval/retriever.rb +4 -6
- data/lib/parse/schema/index_migrator.rb +26 -26
- data/lib/parse/schema/search_index_migrator.rb +19 -19
- data/lib/parse/stack/tasks.rb +6 -6
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +10 -10
- data/lib/parse/vector_search/hybrid.rb +166 -33
- data/lib/parse/vector_search.rb +141 -13
- data/lib/parse/webhooks/payload.rb +1 -0
- data/lib/parse/webhooks/registration.rb +4 -4
- data/lib/parse/webhooks/trigger_audit.rb +50 -49
- data/lib/parse/webhooks.rb +22 -4
- data/parse-stack-next.gemspec +6 -6
- metadata +14 -1
data/lib/parse/agent.rb
CHANGED
|
@@ -714,7 +714,7 @@ module Parse
|
|
|
714
714
|
# only WRITE_TOOLS leaves the raw tools off, so a deployment can
|
|
715
715
|
# permit "set_client_description" (an agent_method) while keeping
|
|
716
716
|
# "create_object" disabled.
|
|
717
|
-
WRITE_GATED_TOOLS
|
|
717
|
+
WRITE_GATED_TOOLS = %i[create_object update_object delete_object].freeze
|
|
718
718
|
SCHEMA_GATED_TOOLS = %i[create_class delete_class].freeze
|
|
719
719
|
|
|
720
720
|
# Built-in tools that are safe to dispatch when the agent runs on a
|
|
@@ -1024,6 +1024,7 @@ module Parse
|
|
|
1024
1024
|
def approval_gate
|
|
1025
1025
|
@approval_gate ||= Parse::Agent::NullGate.new
|
|
1026
1026
|
end
|
|
1027
|
+
|
|
1027
1028
|
attr_writer :approval_gate
|
|
1028
1029
|
|
|
1029
1030
|
# @return [Boolean] true if the active cancellation token has been
|
|
@@ -1084,15 +1085,38 @@ module Parse
|
|
|
1084
1085
|
#
|
|
1085
1086
|
# @return [Hash]
|
|
1086
1087
|
def acl_scope_kwargs
|
|
1087
|
-
if @session_token && !@session_token.to_s.empty?
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1088
|
+
scope = if @session_token && !@session_token.to_s.empty?
|
|
1089
|
+
{ session_token: @session_token }
|
|
1090
|
+
elsif @acl_user_scope
|
|
1091
|
+
{ acl_user: @acl_user_scope }
|
|
1092
|
+
elsif @acl_role_scope
|
|
1093
|
+
{ acl_role: @acl_role_scope }
|
|
1094
|
+
else
|
|
1095
|
+
{ master: true }
|
|
1096
|
+
end
|
|
1097
|
+
scope
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
# {#acl_scope_kwargs} plus the agent's own client.
|
|
1101
|
+
#
|
|
1102
|
+
# Separate from {#acl_scope_kwargs} rather than folded into it, because
|
|
1103
|
+
# that hash is public and documented for `agent_method` bodies to splat
|
|
1104
|
+
# into calls of their own. Those calls can be anything, including REST
|
|
1105
|
+
# paths and user-written helpers, and a stray `client:` would be an
|
|
1106
|
+
# unknown-keyword error there. Only the direct paths that understand
|
|
1107
|
+
# `client:` as an auth kwarg use this variant.
|
|
1108
|
+
#
|
|
1109
|
+
# It matters because an agent built on a secondary client would otherwise
|
|
1110
|
+
# resolve its token, walk its role graph, and read its rows against the
|
|
1111
|
+
# default application. Consumers are `Parse::MongoDB.aggregate`, the
|
|
1112
|
+
# `Parse::Query` direct methods, and `Parse::AtlasSearch`'s own scope
|
|
1113
|
+
# resolution, all of which consume `client:` before it reaches a driver.
|
|
1114
|
+
#
|
|
1115
|
+
# @return [Hash]
|
|
1116
|
+
def direct_auth_kwargs
|
|
1117
|
+
kwargs = acl_scope_kwargs
|
|
1118
|
+
kwargs = kwargs.merge(client: @client) if @client
|
|
1119
|
+
kwargs
|
|
1096
1120
|
end
|
|
1097
1121
|
|
|
1098
1122
|
# The agent's resolved identity claim set — the
|
|
@@ -1201,11 +1225,10 @@ module Parse
|
|
|
1201
1225
|
def refresh_scope!
|
|
1202
1226
|
return @acl_scope if @session_token
|
|
1203
1227
|
return nil if @acl_user_scope.nil? && @acl_role_scope.nil?
|
|
1204
|
-
resolved =
|
|
1205
|
-
|
|
1206
|
-
Parse::ACLScope.resolve_for_user(@acl_user_scope)
|
|
1228
|
+
resolved = if @acl_user_scope
|
|
1229
|
+
Parse::ACLScope.resolve_for_user(@acl_user_scope, client: @client)
|
|
1207
1230
|
else
|
|
1208
|
-
Parse::ACLScope.resolve_for_role(@acl_role_scope)
|
|
1231
|
+
Parse::ACLScope.resolve_for_role(@acl_role_scope, client: @client)
|
|
1209
1232
|
end
|
|
1210
1233
|
@acl_scope = resolved&.freeze
|
|
1211
1234
|
@auth_context = nil # invalidate memoized auth_context — user_id may have changed
|
|
@@ -1232,16 +1255,16 @@ module Parse
|
|
|
1232
1255
|
# @return [self]
|
|
1233
1256
|
def impersonate(user, mint: false, label: nil)
|
|
1234
1257
|
token = resolve_impersonation_token!(user, mint: mint)
|
|
1235
|
-
@session_token
|
|
1258
|
+
@session_token = token
|
|
1236
1259
|
@acl_user_scope = nil
|
|
1237
1260
|
@acl_role_scope = nil
|
|
1238
1261
|
@impersonation_label = sanitize_impersonation_label(label) if label
|
|
1239
1262
|
# Drop memoized scope/auth so the next call resolves under the new
|
|
1240
1263
|
# token (session-token validity is checked per-call by Parse Server).
|
|
1241
|
-
@acl_scope
|
|
1264
|
+
@acl_scope = nil
|
|
1242
1265
|
@auth_context = nil
|
|
1243
1266
|
no_master_key = @client.respond_to?(:master_key) && @client.master_key.nil?
|
|
1244
|
-
@client_mode
|
|
1267
|
+
@client_mode = no_master_key && !@session_token.to_s.empty?
|
|
1245
1268
|
self
|
|
1246
1269
|
end
|
|
1247
1270
|
|
|
@@ -1481,11 +1504,16 @@ module Parse
|
|
|
1481
1504
|
# end,
|
|
1482
1505
|
# )
|
|
1483
1506
|
#
|
|
1507
|
+
# Default for the `client:` keyword, distinguishing "not specified" from
|
|
1508
|
+
# an explicit `client: :default`. A plain `:default` cannot do that job:
|
|
1509
|
+
# it is also a legitimate value a caller may pass on purpose.
|
|
1510
|
+
INHERIT_CLIENT = Object.new.freeze
|
|
1511
|
+
|
|
1484
1512
|
def initialize(permissions: :readonly, session_token: nil,
|
|
1485
1513
|
acl_user: nil, acl_role: nil,
|
|
1486
1514
|
impersonate_user: nil, impersonate_mint: false,
|
|
1487
1515
|
impersonation_label: nil,
|
|
1488
|
-
client:
|
|
1516
|
+
client: INHERIT_CLIENT,
|
|
1489
1517
|
tenant_id: nil,
|
|
1490
1518
|
rate_limit: DEFAULT_RATE_LIMIT, rate_window: DEFAULT_RATE_WINDOW,
|
|
1491
1519
|
rate_limiter: nil,
|
|
@@ -1503,9 +1531,9 @@ module Parse
|
|
|
1503
1531
|
permission: nil,
|
|
1504
1532
|
impersonation_user: nil, impersonation_mint: nil,
|
|
1505
1533
|
impersonate_label: nil)
|
|
1506
|
-
permissions
|
|
1507
|
-
impersonate_user
|
|
1508
|
-
impersonate_mint
|
|
1534
|
+
permissions = permission unless permission.nil?
|
|
1535
|
+
impersonate_user ||= impersonation_user
|
|
1536
|
+
impersonate_mint = impersonation_mint unless impersonation_mint.nil?
|
|
1509
1537
|
impersonation_label ||= impersonate_label
|
|
1510
1538
|
# SECURITY: Mutually exclusive identity inputs. `acl_user:` and
|
|
1511
1539
|
# `acl_role:` are unverified constructor assertions (the SDK does
|
|
@@ -1552,6 +1580,20 @@ module Parse
|
|
|
1552
1580
|
end
|
|
1553
1581
|
|
|
1554
1582
|
@permissions = permissions
|
|
1583
|
+
# A sub-agent inherits its parent's client unless one was named
|
|
1584
|
+
# explicitly. Falling back to the default meant a child of an agent
|
|
1585
|
+
# bound to a secondary application silently resolved and read against
|
|
1586
|
+
# the default one, and Parse::MongoDB's binding guard would see the
|
|
1587
|
+
# default and wave it through.
|
|
1588
|
+
#
|
|
1589
|
+
# The check is on {INHERIT_CLIENT}, not on `:default`. Those are not
|
|
1590
|
+
# the same thing: `Parse::Agent.new(parent: b_agent, client: :default)`
|
|
1591
|
+
# is an explicit instruction to use the default client, and treating it
|
|
1592
|
+
# as "unspecified" would silently substitute the parent's and ignore
|
|
1593
|
+
# what the caller asked for.
|
|
1594
|
+
if client.equal?(INHERIT_CLIENT)
|
|
1595
|
+
client = (parent.client if parent.respond_to?(:client) && parent.client) || :default
|
|
1596
|
+
end
|
|
1555
1597
|
@client = client.is_a?(Parse::Client) ? client : Parse::Client.client(client)
|
|
1556
1598
|
@operation_log = []
|
|
1557
1599
|
@max_log_size = max_log_size
|
|
@@ -1586,8 +1628,8 @@ module Parse
|
|
|
1586
1628
|
raise RecursionLimitExceeded.new(depth: parent.recursion_depth)
|
|
1587
1629
|
end
|
|
1588
1630
|
@recursion_depth = inherited_depth
|
|
1589
|
-
@agent_depth
|
|
1590
|
-
rate_limiter
|
|
1631
|
+
@agent_depth = parent.agent_depth + 1
|
|
1632
|
+
rate_limiter ||= parent.rate_limiter
|
|
1591
1633
|
@parent_agent_id = parent.agent_id
|
|
1592
1634
|
@inherited_correlation_id = parent.correlation_id
|
|
1593
1635
|
|
|
@@ -1611,10 +1653,12 @@ module Parse
|
|
|
1611
1653
|
# produces a `:readonly` sub-agent — the safe default. To
|
|
1612
1654
|
# maintain parity at the call site, pass `permissions:
|
|
1613
1655
|
# parent.permissions`; the clamp check below validates that the
|
|
1614
|
-
# resolved tier does not exceed the parent's. `client:`
|
|
1615
|
-
#
|
|
1616
|
-
#
|
|
1617
|
-
#
|
|
1656
|
+
# resolved tier does not exceed the parent's. `client:` IS
|
|
1657
|
+
# inherited, unlike permissions: a sub-agent that quietly resolved
|
|
1658
|
+
# and read against the default application while its parent was
|
|
1659
|
+
# bound to another is a correctness bug, not a safe default, and
|
|
1660
|
+
# there is no security argument for narrowing which application a
|
|
1661
|
+
# child addresses.
|
|
1618
1662
|
# Inherit auth scope from the parent only when the child supplied
|
|
1619
1663
|
# NO identity at all. Three reasons:
|
|
1620
1664
|
#
|
|
@@ -1674,7 +1718,7 @@ module Parse
|
|
|
1674
1718
|
# tools emit progress over the same SSE stream the parent's
|
|
1675
1719
|
# client is observing.
|
|
1676
1720
|
@cancellation_token = parent.cancellation_token
|
|
1677
|
-
@progress_callback
|
|
1721
|
+
@progress_callback = parent.progress_callback
|
|
1678
1722
|
|
|
1679
1723
|
# Clamp the sub-agent's permission tier at the parent's. The
|
|
1680
1724
|
# default :readonly is always ≤ any parent tier, so this fires
|
|
@@ -1684,7 +1728,7 @@ module Parse
|
|
|
1684
1728
|
# permissions: :admin)` and silently elevate above what the
|
|
1685
1729
|
# parent's session was scoped to do.
|
|
1686
1730
|
parent_tier = PERMISSION_HIERARCHY[parent.permissions] || 0
|
|
1687
|
-
child_tier
|
|
1731
|
+
child_tier = PERMISSION_HIERARCHY[permissions] || 0
|
|
1688
1732
|
if child_tier > parent_tier
|
|
1689
1733
|
raise ArgumentError,
|
|
1690
1734
|
"sub-agent permissions: #{permissions.inspect} exceeds parent's " \
|
|
@@ -1695,7 +1739,7 @@ module Parse
|
|
|
1695
1739
|
end
|
|
1696
1740
|
else
|
|
1697
1741
|
@recursion_depth = (recursion_depth || Parse::Agent.default_recursion_depth).to_i
|
|
1698
|
-
@agent_depth
|
|
1742
|
+
@agent_depth = 0
|
|
1699
1743
|
@parent_agent_id = nil
|
|
1700
1744
|
@inherited_correlation_id = nil
|
|
1701
1745
|
end
|
|
@@ -1707,7 +1751,7 @@ module Parse
|
|
|
1707
1751
|
# session-token path (client-mode detection, eager scope
|
|
1708
1752
|
# resolution, request routing) unchanged. Fail-closed: raises when
|
|
1709
1753
|
# the client has no master key or no session can be resolved.
|
|
1710
|
-
@impersonation_label
|
|
1754
|
+
@impersonation_label = sanitize_impersonation_label(impersonation_label)
|
|
1711
1755
|
@impersonated_user_id = nil
|
|
1712
1756
|
if impersonate_user
|
|
1713
1757
|
session_token = resolve_impersonation_token!(impersonate_user, mint: impersonate_mint)
|
|
@@ -1717,11 +1761,11 @@ module Parse
|
|
|
1717
1761
|
# above resolves before the ivars are set. Without this ordering,
|
|
1718
1762
|
# `@session_token = session_token` would assign the constructor's
|
|
1719
1763
|
# nil default, and the inheritance would be a no-op.
|
|
1720
|
-
@session_token
|
|
1721
|
-
@acl_user_scope
|
|
1722
|
-
@acl_role_scope
|
|
1723
|
-
@tenant_id
|
|
1724
|
-
@master_atlas
|
|
1764
|
+
@session_token = session_token
|
|
1765
|
+
@acl_user_scope = acl_user
|
|
1766
|
+
@acl_role_scope = acl_role
|
|
1767
|
+
@tenant_id = tenant_id
|
|
1768
|
+
@master_atlas = master_atlas == true
|
|
1725
1769
|
|
|
1726
1770
|
# Client-mode detection. An agent runs in CLIENT MODE when its
|
|
1727
1771
|
# underlying Parse::Client has no master_key AND it was constructed
|
|
@@ -1772,8 +1816,7 @@ module Parse
|
|
|
1772
1816
|
# the safe path (omit kwarg) is trivially correct.
|
|
1773
1817
|
if parent
|
|
1774
1818
|
parent_allows = parent.respond_to?(:allow_mutations?) ? parent.allow_mutations? : true
|
|
1775
|
-
resolved_allow_mutations =
|
|
1776
|
-
if allow_mutations.nil?
|
|
1819
|
+
resolved_allow_mutations = if allow_mutations.nil?
|
|
1777
1820
|
parent_allows
|
|
1778
1821
|
else
|
|
1779
1822
|
allow_mutations == true
|
|
@@ -1787,8 +1830,7 @@ module Parse
|
|
|
1787
1830
|
end
|
|
1788
1831
|
@allow_mutations = resolved_allow_mutations
|
|
1789
1832
|
else
|
|
1790
|
-
@allow_mutations =
|
|
1791
|
-
if allow_mutations.nil?
|
|
1833
|
+
@allow_mutations = if allow_mutations.nil?
|
|
1792
1834
|
!@client_mode
|
|
1793
1835
|
else
|
|
1794
1836
|
allow_mutations == true
|
|
@@ -1812,8 +1854,7 @@ module Parse
|
|
|
1812
1854
|
# than at first tool call, and makes the subset check below
|
|
1813
1855
|
# uniform across modes. Long-lived agents can re-resolve via
|
|
1814
1856
|
# {#refresh_scope!}.
|
|
1815
|
-
@acl_scope =
|
|
1816
|
-
if @session_token
|
|
1857
|
+
@acl_scope = if @session_token
|
|
1817
1858
|
# Best-effort eager resolution. If Parse Server's /users/me is
|
|
1818
1859
|
# unreachable at construction time (network blip, test env, MCP
|
|
1819
1860
|
# bootstrap-before-server-ready), leave @acl_scope nil and let
|
|
@@ -1824,15 +1865,15 @@ module Parse
|
|
|
1824
1865
|
# per-call validation will surface auth errors at the
|
|
1825
1866
|
# actual usage site where the operator can act on them.
|
|
1826
1867
|
begin
|
|
1827
|
-
opts = { session_token: @session_token }
|
|
1868
|
+
opts = { session_token: @session_token, client: @client }.compact
|
|
1828
1869
|
Parse::ACLScope.resolve!(opts, method_name: :agent_init)
|
|
1829
1870
|
rescue StandardError
|
|
1830
1871
|
nil
|
|
1831
1872
|
end
|
|
1832
1873
|
elsif @acl_user_scope
|
|
1833
|
-
Parse::ACLScope.resolve_for_user(@acl_user_scope)
|
|
1874
|
+
Parse::ACLScope.resolve_for_user(@acl_user_scope, client: @client)
|
|
1834
1875
|
elsif @acl_role_scope
|
|
1835
|
-
Parse::ACLScope.resolve_for_role(@acl_role_scope)
|
|
1876
|
+
Parse::ACLScope.resolve_for_role(@acl_role_scope, client: @client)
|
|
1836
1877
|
else
|
|
1837
1878
|
nil
|
|
1838
1879
|
end
|
|
@@ -1948,10 +1989,10 @@ module Parse
|
|
|
1948
1989
|
# Normalize the `tools:`, `methods:`, and `classes:` filters. Errors
|
|
1949
1990
|
# raise ArgumentError (bad shape) or, when strict mode is on,
|
|
1950
1991
|
# ArgumentError (unknown tool / class name).
|
|
1951
|
-
@tool_filter_only,
|
|
1992
|
+
@tool_filter_only, @tool_filter_except = normalize_tool_filter(tools)
|
|
1952
1993
|
@method_filter_only, @method_filter_except = normalize_method_filter(methods)
|
|
1953
|
-
@class_filter_only,
|
|
1954
|
-
@filters
|
|
1994
|
+
@class_filter_only, @class_filter_except = normalize_class_filter(classes)
|
|
1995
|
+
@filters = normalize_query_filters(filters)
|
|
1955
1996
|
|
|
1956
1997
|
# Sub-agent class-filter inheritance. Unlike `tools:` (which overrides
|
|
1957
1998
|
# outright), `classes:` clamps to the parent's effective set so a
|
|
@@ -1960,7 +2001,7 @@ module Parse
|
|
|
1960
2001
|
# parent's effective set raises at construction — empty-onlyset means
|
|
1961
2002
|
# "address no classes," which is almost certainly a typo, not intent.
|
|
1962
2003
|
if parent
|
|
1963
|
-
parent_only
|
|
2004
|
+
parent_only = parent.instance_variable_get(:@class_filter_only)
|
|
1964
2005
|
parent_except = parent.instance_variable_get(:@class_filter_except)
|
|
1965
2006
|
if parent_only && @class_filter_only
|
|
1966
2007
|
intersection = Set.new(@class_filter_only) & parent_only
|
|
@@ -2107,9 +2148,9 @@ module Parse
|
|
|
2107
2148
|
# @return [Array<Symbol>] list of allowed tool names
|
|
2108
2149
|
def allowed_tools
|
|
2109
2150
|
registered = Parse::Agent::Tools.registered_tools_for(@permissions)
|
|
2110
|
-
permitted
|
|
2151
|
+
permitted = (tier_builtin_set + registered).uniq
|
|
2111
2152
|
|
|
2112
|
-
permitted = permitted & @tool_filter_only.to_a
|
|
2153
|
+
permitted = permitted & @tool_filter_only.to_a if @tool_filter_only
|
|
2113
2154
|
permitted = permitted - @tool_filter_except.to_a if @tool_filter_except
|
|
2114
2155
|
|
|
2115
2156
|
if @client_mode
|
|
@@ -2160,7 +2201,7 @@ module Parse
|
|
|
2160
2201
|
return false if @method_filter_only.nil? && @method_filter_except.nil?
|
|
2161
2202
|
|
|
2162
2203
|
method_sym = method_name.to_sym
|
|
2163
|
-
qualified
|
|
2204
|
+
qualified = "#{class_name}.#{method_name}"
|
|
2164
2205
|
|
|
2165
2206
|
if @method_filter_only
|
|
2166
2207
|
permitted = @method_filter_only.include?(method_sym) ||
|
|
@@ -2230,7 +2271,7 @@ module Parse
|
|
|
2230
2271
|
# limit"). Borrow the configured limit/window when the injected
|
|
2231
2272
|
# limiter exposes them; otherwise fall back to non-zero defaults.
|
|
2232
2273
|
retry_after = (1.0 + rand * 4.0).round(2)
|
|
2233
|
-
l = @rate_limiter.respond_to?(:limit)
|
|
2274
|
+
l = @rate_limiter.respond_to?(:limit) ? @rate_limiter.limit : RateLimiter::DEFAULT_LIMIT
|
|
2234
2275
|
w = @rate_limiter.respond_to?(:window) ? @rate_limiter.window : RateLimiter::DEFAULT_WINDOW
|
|
2235
2276
|
raise RateLimitExceeded.new(retry_after: retry_after, limit: l, window: w)
|
|
2236
2277
|
end
|
|
@@ -2325,10 +2366,10 @@ module Parse
|
|
|
2325
2366
|
!(Parse::Agent.write_tools_enabled? && Parse::Agent.raw_crud_enabled? && @allow_mutations)
|
|
2326
2367
|
missing = []
|
|
2327
2368
|
missing << "PARSE_AGENT_ALLOW_WRITE_TOOLS=true" unless Parse::Agent.write_tools_enabled?
|
|
2328
|
-
missing << "PARSE_AGENT_ALLOW_RAW_CRUD=true"
|
|
2369
|
+
missing << "PARSE_AGENT_ALLOW_RAW_CRUD=true" unless Parse::Agent.raw_crud_enabled?
|
|
2329
2370
|
missing << "allow_mutations: true (per-agent kwarg)" unless @allow_mutations
|
|
2330
2371
|
return error_response(
|
|
2331
|
-
"Raw CRUD tool '#{tool_name}' is disabled. Required: #{missing.join(
|
|
2372
|
+
"Raw CRUD tool '#{tool_name}' is disabled. Required: #{missing.join(" AND ")}. " \
|
|
2332
2373
|
"Prefer declaring an agent_method on the target class for an intent-based " \
|
|
2333
2374
|
"write path that requires only PARSE_AGENT_ALLOW_WRITE_TOOLS.",
|
|
2334
2375
|
error_code: :access_denied,
|
|
@@ -2340,7 +2381,7 @@ module Parse
|
|
|
2340
2381
|
missing << "PARSE_AGENT_ALLOW_SCHEMA_OPS=true" unless Parse::Agent.schema_ops_enabled?
|
|
2341
2382
|
missing << "PARSE_AGENT_ALLOW_RAW_SCHEMA=true" unless Parse::Agent.raw_schema_enabled?
|
|
2342
2383
|
return error_response(
|
|
2343
|
-
"Raw schema-mutating tool '#{tool_name}' is disabled. Required: #{missing.join(
|
|
2384
|
+
"Raw schema-mutating tool '#{tool_name}' is disabled. Required: #{missing.join(" AND ")}. " \
|
|
2344
2385
|
"These tools mutate the entire Parse schema; consider whether an explicit operator " \
|
|
2345
2386
|
"process is a better fit than agent access.",
|
|
2346
2387
|
error_code: :access_denied,
|
|
@@ -2356,7 +2397,7 @@ module Parse
|
|
|
2356
2397
|
unless approval_tiers.empty?
|
|
2357
2398
|
eff_perm = effective_permission_for(tool_name, kwargs)
|
|
2358
2399
|
if approval_tiers.include?(eff_perm)
|
|
2359
|
-
preview
|
|
2400
|
+
preview = build_approval_preview(tool_name, kwargs)
|
|
2360
2401
|
decision = approval_gate.review(
|
|
2361
2402
|
tool_name: tool_name,
|
|
2362
2403
|
effective_permission: eff_perm,
|
|
@@ -2365,9 +2406,9 @@ module Parse
|
|
|
2365
2406
|
)
|
|
2366
2407
|
unless decision.approved?
|
|
2367
2408
|
return error_response(
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2409
|
+
decision.reason || "Operation '#{tool_name}' requires approval and was not approved.",
|
|
2410
|
+
error_code: :approval_denied,
|
|
2411
|
+
)
|
|
2371
2412
|
end
|
|
2372
2413
|
end
|
|
2373
2414
|
end
|
|
@@ -2388,9 +2429,9 @@ module Parse
|
|
|
2388
2429
|
agent_id: agent_id,
|
|
2389
2430
|
agent_depth: @agent_depth,
|
|
2390
2431
|
}
|
|
2391
|
-
payload[:correlation_id]
|
|
2392
|
-
payload[:parent_agent_id]
|
|
2393
|
-
payload[:impersonation_label]
|
|
2432
|
+
payload[:correlation_id] = @correlation_id if @correlation_id
|
|
2433
|
+
payload[:parent_agent_id] = @parent_agent_id if @parent_agent_id
|
|
2434
|
+
payload[:impersonation_label] = @impersonation_label if @impersonation_label
|
|
2394
2435
|
payload[:impersonated_user_id] = @impersonated_user_id if @impersonated_user_id
|
|
2395
2436
|
|
|
2396
2437
|
# Audit surface — narrowing filters in effect for this call. SOC and
|
|
@@ -2400,12 +2441,12 @@ module Parse
|
|
|
2400
2441
|
# the underlying frozen Sets) for stable JSON serialization. Omitted
|
|
2401
2442
|
# entirely when no filter was declared so the payload stays minimal
|
|
2402
2443
|
# for the common unscoped-agent case.
|
|
2403
|
-
payload[:classes_only]
|
|
2404
|
-
payload[:classes_except]
|
|
2405
|
-
payload[:tools_only]
|
|
2406
|
-
payload[:tools_except]
|
|
2407
|
-
payload[:methods_only]
|
|
2408
|
-
payload[:methods_except]
|
|
2444
|
+
payload[:classes_only] = @class_filter_only.to_a.sort if @class_filter_only
|
|
2445
|
+
payload[:classes_except] = @class_filter_except.to_a.sort if @class_filter_except
|
|
2446
|
+
payload[:tools_only] = @tool_filter_only.to_a.sort if @tool_filter_only
|
|
2447
|
+
payload[:tools_except] = @tool_filter_except.to_a.sort if @tool_filter_except
|
|
2448
|
+
payload[:methods_only] = @method_filter_only.to_a.map(&:to_s).sort if @method_filter_only
|
|
2449
|
+
payload[:methods_except] = @method_filter_except.to_a.map(&:to_s).sort if @method_filter_except
|
|
2409
2450
|
# Per-agent per-class filters — emit class-name → field-name list,
|
|
2410
2451
|
# NOT the constraint values. Filter values can contain user-identifying
|
|
2411
2452
|
# data (`{ user_id: "abc123" }`, `{ org_id: tenant_uuid }`) that
|
|
@@ -2428,7 +2469,7 @@ module Parse
|
|
|
2428
2469
|
# Checkpoint #2, which runs after the tool has executed, DOES
|
|
2429
2470
|
# fire the notification with success: false, error_code: :cancelled.
|
|
2430
2471
|
if cancelled?
|
|
2431
|
-
payload[:success]
|
|
2472
|
+
payload[:success] = false
|
|
2432
2473
|
payload[:error_code] = :cancelled
|
|
2433
2474
|
return cancelled_response
|
|
2434
2475
|
end
|
|
@@ -2476,7 +2517,7 @@ module Parse
|
|
|
2476
2517
|
# of `agent.execute` and then crashes the dispatcher when it
|
|
2477
2518
|
# inspects `result[:cancelled]`.
|
|
2478
2519
|
if cancelled?
|
|
2479
|
-
payload[:success]
|
|
2520
|
+
payload[:success] = false
|
|
2480
2521
|
payload[:error_code] = :cancelled
|
|
2481
2522
|
response = cancelled_response
|
|
2482
2523
|
trigger_callbacks(:after_tool_call, tool_name, kwargs, response)
|
|
@@ -2506,9 +2547,9 @@ module Parse
|
|
|
2506
2547
|
Parse::Agent::PromptInjectionDetected => e
|
|
2507
2548
|
log_security_event(tool_name, kwargs, e)
|
|
2508
2549
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2509
|
-
payload[:success]
|
|
2550
|
+
payload[:success] = false
|
|
2510
2551
|
payload[:error_class] = e.class.name
|
|
2511
|
-
payload[:error_code]
|
|
2552
|
+
payload[:error_code] = :security_blocked
|
|
2512
2553
|
raise # Re-raise security errors to caller
|
|
2513
2554
|
|
|
2514
2555
|
# Method excluded by the agent instance's `methods:` filter.
|
|
@@ -2520,9 +2561,9 @@ module Parse
|
|
|
2520
2561
|
# filter denial path.
|
|
2521
2562
|
rescue Parse::Agent::MethodFiltered => e
|
|
2522
2563
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2523
|
-
payload[:success]
|
|
2564
|
+
payload[:success] = false
|
|
2524
2565
|
payload[:error_class] = e.class.name
|
|
2525
|
-
payload[:error_code]
|
|
2566
|
+
payload[:error_code] = :tool_filtered
|
|
2526
2567
|
response = error_response(e.message, error_code: :tool_filtered)
|
|
2527
2568
|
|
|
2528
2569
|
# Access-denied errors raised by Tools.assert_class_accessible! when
|
|
@@ -2532,9 +2573,9 @@ module Parse
|
|
|
2532
2573
|
# internal state.
|
|
2533
2574
|
rescue Parse::Agent::AccessDenied => e
|
|
2534
2575
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2535
|
-
payload[:success]
|
|
2576
|
+
payload[:success] = false
|
|
2536
2577
|
payload[:error_class] = e.class.name
|
|
2537
|
-
payload[:error_code]
|
|
2578
|
+
payload[:error_code] = :access_denied
|
|
2538
2579
|
# Surface the AccessDenied subcode (`:hidden_class`,
|
|
2539
2580
|
# `:class_filter`, `:field_denied`, `:storage_form_field_ref`)
|
|
2540
2581
|
# in the audit payload so SOC tooling can distinguish operator
|
|
@@ -2550,58 +2591,58 @@ module Parse
|
|
|
2550
2591
|
# branch on "tool doesn't exist here" vs a real failure.
|
|
2551
2592
|
rescue Parse::Agent::NotImplemented => e
|
|
2552
2593
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2553
|
-
payload[:success]
|
|
2594
|
+
payload[:success] = false
|
|
2554
2595
|
payload[:error_class] = e.class.name
|
|
2555
|
-
payload[:error_code]
|
|
2596
|
+
payload[:error_code] = :not_implemented
|
|
2556
2597
|
response = error_response(e.message, error_code: :not_implemented)
|
|
2557
2598
|
|
|
2558
2599
|
# Validation errors (e.g. from registered tool handlers or get_objects)
|
|
2559
2600
|
rescue Parse::Agent::ValidationError => e
|
|
2560
2601
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2561
|
-
payload[:success]
|
|
2602
|
+
payload[:success] = false
|
|
2562
2603
|
payload[:error_class] = e.class.name
|
|
2563
|
-
payload[:error_code]
|
|
2604
|
+
payload[:error_code] = :invalid_argument
|
|
2564
2605
|
response = error_response("Invalid arguments: #{e.message}", error_code: :invalid_argument)
|
|
2565
2606
|
|
|
2566
2607
|
# Validation errors - return structured error response
|
|
2567
2608
|
rescue ConstraintTranslator::InvalidOperatorError => e
|
|
2568
2609
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2569
|
-
payload[:success]
|
|
2610
|
+
payload[:success] = false
|
|
2570
2611
|
payload[:error_class] = e.class.name
|
|
2571
|
-
payload[:error_code]
|
|
2612
|
+
payload[:error_code] = :invalid_query
|
|
2572
2613
|
response = error_response(e.message, error_code: :invalid_query)
|
|
2573
2614
|
|
|
2574
2615
|
# Timeout errors
|
|
2575
2616
|
rescue ToolTimeoutError => e
|
|
2576
2617
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2577
|
-
payload[:success]
|
|
2618
|
+
payload[:success] = false
|
|
2578
2619
|
payload[:error_class] = e.class.name
|
|
2579
|
-
payload[:error_code]
|
|
2620
|
+
payload[:error_code] = :timeout
|
|
2580
2621
|
response = error_response(e.message, error_code: :timeout)
|
|
2581
2622
|
|
|
2582
2623
|
# Rate limit errors (raised by the built-in limiter or by external
|
|
2583
2624
|
# injected limiters that re-raise the same constant).
|
|
2584
2625
|
rescue RateLimitExceeded => e
|
|
2585
2626
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2586
|
-
payload[:success]
|
|
2627
|
+
payload[:success] = false
|
|
2587
2628
|
payload[:error_class] = e.class.name
|
|
2588
|
-
payload[:error_code]
|
|
2629
|
+
payload[:error_code] = :rate_limited
|
|
2589
2630
|
response = error_response(e.message, error_code: :rate_limited, retry_after: e.retry_after)
|
|
2590
2631
|
|
|
2591
2632
|
# Invalid arguments
|
|
2592
2633
|
rescue ArgumentError => e
|
|
2593
2634
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2594
|
-
payload[:success]
|
|
2635
|
+
payload[:success] = false
|
|
2595
2636
|
payload[:error_class] = e.class.name
|
|
2596
|
-
payload[:error_code]
|
|
2637
|
+
payload[:error_code] = :invalid_argument
|
|
2597
2638
|
response = error_response("Invalid arguments: #{e.message}", error_code: :invalid_argument)
|
|
2598
2639
|
|
|
2599
2640
|
# Parse API errors
|
|
2600
2641
|
rescue Parse::Error => e
|
|
2601
2642
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2602
|
-
payload[:success]
|
|
2643
|
+
payload[:success] = false
|
|
2603
2644
|
payload[:error_class] = e.class.name
|
|
2604
|
-
payload[:error_code]
|
|
2645
|
+
payload[:error_code] = :parse_error
|
|
2605
2646
|
response = error_response("Parse error: #{e.message}", error_code: :parse_error)
|
|
2606
2647
|
|
|
2607
2648
|
# Pointer-shape mismatch in `$in`/`$nin` array against a pointer
|
|
@@ -2615,9 +2656,9 @@ module Parse
|
|
|
2615
2656
|
# to "internal error".
|
|
2616
2657
|
rescue Parse::Query::PointerShapeError => e
|
|
2617
2658
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2618
|
-
payload[:success]
|
|
2659
|
+
payload[:success] = false
|
|
2619
2660
|
payload[:error_class] = e.class.name
|
|
2620
|
-
payload[:error_code]
|
|
2661
|
+
payload[:error_code] = :pointer_shape_mismatch
|
|
2621
2662
|
response = error_response(e.message, error_code: :pointer_shape_mismatch)
|
|
2622
2663
|
|
|
2623
2664
|
# MongoDB-level query timeout (maxTimeMS exceeded, code 50).
|
|
@@ -2633,9 +2674,9 @@ module Parse
|
|
|
2633
2674
|
# response is returned rather than the opaque internal_error path.
|
|
2634
2675
|
rescue Parse::MongoDB::ExecutionTimeout => e
|
|
2635
2676
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2636
|
-
payload[:success]
|
|
2677
|
+
payload[:success] = false
|
|
2637
2678
|
payload[:error_class] = e.class.name
|
|
2638
|
-
payload[:error_code]
|
|
2679
|
+
payload[:error_code] = :timeout
|
|
2639
2680
|
response = error_response(
|
|
2640
2681
|
"Query timed out at the database (max_time_ms=#{e.max_time_ms}ms). " \
|
|
2641
2682
|
"Narrow the filter, add an index, or call explain_query to inspect the plan.",
|
|
@@ -2658,9 +2699,9 @@ module Parse
|
|
|
2658
2699
|
warn "[Parse::Agent] Unexpected error in #{tool_name}: #{e.class} - #{e.message}"
|
|
2659
2700
|
warn e.backtrace.first(5).join("\n") if e.backtrace
|
|
2660
2701
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2661
|
-
payload[:success]
|
|
2702
|
+
payload[:success] = false
|
|
2662
2703
|
payload[:error_class] = e.class.name
|
|
2663
|
-
payload[:error_code]
|
|
2704
|
+
payload[:error_code] = :internal_error
|
|
2664
2705
|
response = error_response("#{tool_name} failed: internal error", error_code: :internal_error)
|
|
2665
2706
|
ensure
|
|
2666
2707
|
# Attribute embedding cost to this tool span and restore the
|
|
@@ -2668,7 +2709,7 @@ module Parse
|
|
|
2668
2709
|
# when no embed happened, matching the minimal-payload discipline.
|
|
2669
2710
|
embed_frame = Parse::Agent.embed_accumulator_end!(embed_frame_saved)
|
|
2670
2711
|
if embed_frame && embed_frame[:calls] > 0
|
|
2671
|
-
payload[:embed_calls]
|
|
2712
|
+
payload[:embed_calls] = embed_frame[:calls]
|
|
2672
2713
|
payload[:embed_tokens] = embed_frame[:tokens]
|
|
2673
2714
|
cost = Parse::Agent.embed_cost_usd(embed_frame[:tokens])
|
|
2674
2715
|
payload[:embed_cost_usd] = cost if cost
|
|
@@ -3212,7 +3253,7 @@ module Parse
|
|
|
3212
3253
|
|
|
3213
3254
|
tools = expand_tool_profile(tools)
|
|
3214
3255
|
only_list, except_list = extract_filter_lists(:tools, tools)
|
|
3215
|
-
only_set
|
|
3256
|
+
only_set = only_list && Set.new(Array(only_list).map(&:to_sym)).freeze
|
|
3216
3257
|
except_set = except_list && Set.new(Array(except_list).map(&:to_sym)).freeze
|
|
3217
3258
|
|
|
3218
3259
|
# "Known" tools include the global registry plus every tool in
|
|
@@ -3281,7 +3322,7 @@ module Parse
|
|
|
3281
3322
|
return [nil, nil] if methods.nil?
|
|
3282
3323
|
|
|
3283
3324
|
only_list, except_list = extract_filter_lists(:methods, methods)
|
|
3284
|
-
only_set
|
|
3325
|
+
only_set = only_list && Set.new(Array(only_list).map(&method(:normalize_method_filter_entry))).freeze
|
|
3285
3326
|
except_set = except_list && Set.new(Array(except_list).map(&method(:normalize_method_filter_entry))).freeze
|
|
3286
3327
|
[only_set, except_set]
|
|
3287
3328
|
end
|
|
@@ -3321,10 +3362,10 @@ module Parse
|
|
|
3321
3362
|
|
|
3322
3363
|
only_list, except_list = extract_filter_lists(:classes, classes)
|
|
3323
3364
|
|
|
3324
|
-
only_entries
|
|
3365
|
+
only_entries = only_list && resolve_class_filter_entries(only_list, validate: true)
|
|
3325
3366
|
except_entries = except_list && resolve_class_filter_entries(except_list, validate: false)
|
|
3326
3367
|
|
|
3327
|
-
only_set
|
|
3368
|
+
only_set = only_entries && Set.new(only_entries).freeze
|
|
3328
3369
|
except_set = except_entries && Set.new(except_entries).freeze
|
|
3329
3370
|
[only_set, except_set]
|
|
3330
3371
|
end
|
|
@@ -3590,9 +3631,9 @@ module Parse
|
|
|
3590
3631
|
"#{kwarg_name}: accepts only :only and :except keys " \
|
|
3591
3632
|
"(got unexpected #{bad_keys.inspect})"
|
|
3592
3633
|
end
|
|
3593
|
-
only
|
|
3634
|
+
only = value[:only] || value["only"]
|
|
3594
3635
|
except = value[:except] || value["except"]
|
|
3595
|
-
unless only.nil?
|
|
3636
|
+
unless only.nil? || only.is_a?(Array)
|
|
3596
3637
|
raise ArgumentError, "#{kwarg_name}: :only must be an Array (got #{only.class})"
|
|
3597
3638
|
end
|
|
3598
3639
|
unless except.nil? || except.is_a?(Array)
|
|
@@ -3787,8 +3828,8 @@ module Parse
|
|
|
3787
3828
|
identity: @acl_scope&.user_id }
|
|
3788
3829
|
elsif @acl_user_scope
|
|
3789
3830
|
{ type: :acl_user, using_master_key: false,
|
|
3790
|
-
|
|
3791
|
-
|
|
3831
|
+
identity: (@acl_scope&.user_id ||
|
|
3832
|
+
(@acl_user_scope.respond_to?(:id) ? @acl_user_scope.id : nil)) }
|
|
3792
3833
|
elsif @acl_role_scope
|
|
3793
3834
|
role_name = case @acl_role_scope
|
|
3794
3835
|
when Parse::Role then @acl_role_scope.name
|
|
@@ -3910,8 +3951,8 @@ module Parse
|
|
|
3910
3951
|
# multi-client / multi-tenant setup those can point at different apps,
|
|
3911
3952
|
# and the existing-session lookup must hit the same app we minted into.
|
|
3912
3953
|
existing = Parse::Session.for_user(pointer)
|
|
3913
|
-
|
|
3914
|
-
|
|
3954
|
+
.where(:expires_at.gte => Time.now)
|
|
3955
|
+
.order(:updated_at.desc)
|
|
3915
3956
|
existing.client = @client
|
|
3916
3957
|
existing = existing.first
|
|
3917
3958
|
token = existing&.session_token
|
|
@@ -3949,8 +3990,8 @@ module Parse
|
|
|
3949
3990
|
minted = resp.result["sessionToken"] || resp.result[:sessionToken]
|
|
3950
3991
|
if minted.nil? || minted.to_s.empty?
|
|
3951
3992
|
refreshed = Parse::Session.for_user(pointer)
|
|
3952
|
-
|
|
3953
|
-
|
|
3993
|
+
.where(:expires_at.gte => Time.now)
|
|
3994
|
+
.order(:updated_at.desc)
|
|
3954
3995
|
refreshed.client = @client
|
|
3955
3996
|
refreshed = refreshed.first
|
|
3956
3997
|
minted = refreshed&.session_token
|
|
@@ -4008,7 +4049,7 @@ module Parse
|
|
|
4008
4049
|
# @return [Symbol] :readonly / :write / :admin (/ :unknown)
|
|
4009
4050
|
def effective_permission_for(tool_name, kwargs)
|
|
4010
4051
|
if tool_name.to_sym == :call_method
|
|
4011
|
-
class_name
|
|
4052
|
+
class_name = kwargs[:class_name] || kwargs["class_name"]
|
|
4012
4053
|
method_name = kwargs[:method_name] || kwargs["method_name"]
|
|
4013
4054
|
if class_name && method_name
|
|
4014
4055
|
klass = (Parse::Model.find_class(class_name.to_s) rescue nil)
|
|
@@ -4060,9 +4101,9 @@ module Parse
|
|
|
4060
4101
|
append_log(entry)
|
|
4061
4102
|
|
|
4062
4103
|
response = { success: false, error: message }
|
|
4063
|
-
response[:error_code]
|
|
4104
|
+
response[:error_code] = error_code if error_code
|
|
4064
4105
|
response[:retry_after] = retry_after if retry_after
|
|
4065
|
-
response[:details]
|
|
4106
|
+
response[:details] = details if details.is_a?(Hash) && details.any?
|
|
4066
4107
|
response
|
|
4067
4108
|
end
|
|
4068
4109
|
|
|
@@ -4074,10 +4115,10 @@ module Parse
|
|
|
4074
4115
|
reason = @cancellation_token&.reason
|
|
4075
4116
|
message = reason ? "Cancelled by client (#{reason})" : "Cancelled by client"
|
|
4076
4117
|
{
|
|
4077
|
-
success:
|
|
4078
|
-
error:
|
|
4118
|
+
success: false,
|
|
4119
|
+
error: message,
|
|
4079
4120
|
error_code: :cancelled,
|
|
4080
|
-
cancelled:
|
|
4121
|
+
cancelled: true,
|
|
4081
4122
|
}
|
|
4082
4123
|
end
|
|
4083
4124
|
end
|