parse-stack-next 5.5.4 → 5.5.6

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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -6
  3. data/README.md +26 -13
  4. data/bin/parse-console +9 -1
  5. data/docs/TEST_SERVER.md +115 -238
  6. data/docs/mcp_guide.md +1 -1
  7. data/docs/mongodb_index_optimization_guide.md +3 -2
  8. data/docs/usage_guide.md +1 -1
  9. data/docs/yard-template/default/fulldoc/html/css/common.css +52 -9
  10. data/docs/yard-template/default/fulldoc/html/css/full_list.css +40 -13
  11. data/lib/parse/agent/constraint_translator.rb +18 -18
  12. data/lib/parse/agent/errors.rb +29 -7
  13. data/lib/parse/agent/mcp_rack_app.rb +29 -11
  14. data/lib/parse/agent/metadata_dsl.rb +6 -6
  15. data/lib/parse/agent/tools.rb +250 -59
  16. data/lib/parse/agent.rb +42 -30
  17. data/lib/parse/api/aggregate.rb +3 -3
  18. data/lib/parse/api/cloud_functions.rb +19 -10
  19. data/lib/parse/api/objects.rb +8 -8
  20. data/lib/parse/api/users.rb +9 -9
  21. data/lib/parse/atlas_search/session.rb +34 -34
  22. data/lib/parse/atlas_search.rb +243 -110
  23. data/lib/parse/client/body_builder.rb +10 -10
  24. data/lib/parse/client/logging.rb +5 -2
  25. data/lib/parse/client/profiling.rb +5 -2
  26. data/lib/parse/client/protocol.rb +1 -1
  27. data/lib/parse/client/url_redaction.rb +94 -0
  28. data/lib/parse/client.rb +43 -28
  29. data/lib/parse/embeddings/image_fetch.rb +6 -1
  30. data/lib/parse/embeddings/voyage.rb +16 -17
  31. data/lib/parse/live_query/client.rb +7 -7
  32. data/lib/parse/live_query/subscription.rb +1 -1
  33. data/lib/parse/lock.rb +1 -1
  34. data/lib/parse/lock_backend.rb +118 -2
  35. data/lib/parse/model/acl.rb +24 -24
  36. data/lib/parse/model/classes/job_schedule.rb +8 -8
  37. data/lib/parse/model/classes/job_status.rb +9 -9
  38. data/lib/parse/model/classes/role.rb +49 -49
  39. data/lib/parse/model/classes/session.rb +2 -2
  40. data/lib/parse/model/classes/user.rb +66 -66
  41. data/lib/parse/model/core/builder.rb +7 -7
  42. data/lib/parse/model/core/create_lock.rb +1 -1
  43. data/lib/parse/model/core/properties.rb +4 -4
  44. data/lib/parse/model/file.rb +57 -16
  45. data/lib/parse/model/model.rb +19 -19
  46. data/lib/parse/model/object.rb +38 -38
  47. data/lib/parse/model/pointer.rb +4 -4
  48. data/lib/parse/model/push.rb +5 -5
  49. data/lib/parse/mongodb.rb +84 -26
  50. data/lib/parse/pipeline_security.rb +2 -2
  51. data/lib/parse/query/constraints.rb +38 -38
  52. data/lib/parse/query.rb +151 -75
  53. data/lib/parse/retrieval/reranker/cohere.rb +30 -0
  54. data/lib/parse/schema.rb +1 -1
  55. data/lib/parse/stack/version.rb +1 -1
  56. data/lib/parse/stack.rb +23 -10
  57. data/lib/parse/two_factor_auth/user_extension.rb +25 -25
  58. data/lib/parse/webhooks/payload.rb +35 -35
  59. data/lib/parse/webhooks/registration.rb +2 -2
  60. data/lib/parse/webhooks/replay_protection.rb +16 -16
  61. data/lib/parse/webhooks.rb +11 -11
  62. data/parse-stack-next.gemspec +19 -1
  63. metadata +2 -38
  64. data/.bundle/config +0 -5
  65. data/.env.sample +0 -138
  66. data/.env.test +0 -10
  67. data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
  68. data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
  69. data/.github/dependabot.yml +0 -13
  70. data/.github/workflows/codeql.yml +0 -44
  71. data/.github/workflows/docs.yml +0 -39
  72. data/.github/workflows/release.yml +0 -43
  73. data/.github/workflows/ruby.yml +0 -38
  74. data/.gitignore +0 -56
  75. data/.ruby-version +0 -1
  76. data/.solargraph.yml +0 -22
  77. data/.vscode/settings.json +0 -3
  78. data/.yardopts +0 -19
  79. data/Gemfile +0 -43
  80. data/Gemfile.lock +0 -198
  81. data/Makefile +0 -63
  82. data/Rakefile +0 -825
  83. data/config/parse-config.json +0 -12
  84. data/scripts/debug-ips.js +0 -35
  85. data/scripts/docker/Dockerfile.parse +0 -17
  86. data/scripts/docker/atlas-init.js +0 -284
  87. data/scripts/docker/docker-compose.atlas.yml +0 -80
  88. data/scripts/docker/docker-compose.test.yml +0 -159
  89. data/scripts/docker/docker-compose.verifyemail.yml +0 -4
  90. data/scripts/docker/mongo-init.js +0 -21
  91. data/scripts/docker/preflight.sh +0 -76
  92. data/scripts/eval_mcp_with_lm_studio.rb +0 -274
  93. data/scripts/start-parse.sh +0 -154
  94. data/scripts/start_mcp_server.rb +0 -78
  95. data/scripts/test_server_connection.rb +0 -82
  96. data/scripts/vector_prototype/create_vector_index.js +0 -105
  97. data/scripts/vector_prototype/fetch_embeddings.py +0 -241
  98. data/scripts/vector_prototype/fixture_manifest.json +0 -9
  99. data/scripts/vector_prototype/query_prototype.rb +0 -84
  100. data/scripts/vector_prototype/run.sh +0 -34
@@ -72,9 +72,9 @@ module Parse
72
72
  ].freeze
73
73
 
74
74
  # Operators whose value carries an inner sub-query of the shape
75
- # +{className:, where:, key:}+. Each must be validated through
75
+ # `{className:, where:, key:}`. Each must be validated through
76
76
  # {Tools.assert_class_accessible!} so the LLM cannot reach into a
77
- # hidden class via the sub-query, and the inner +where+ must be
77
+ # hidden class via the sub-query, and the inner `where` must be
78
78
  # recursively re-translated so blocked operators inside it are
79
79
  # also caught.
80
80
  CROSS_CLASS_OPERATORS = %w[
@@ -260,11 +260,11 @@ module Parse
260
260
  end
261
261
 
262
262
  # Translate the value of a cross-class operator
263
- # (+$inQuery+/+$notInQuery+/+$select+/+$dontSelect+). The value
264
- # carries an embedded +className+ that must be validated against
265
- # the active accessibility policy, and an embedded +where+ that
263
+ # (`$inQuery`/`$notInQuery`/`$select`/`$dontSelect`). The value
264
+ # carries an embedded `className` that must be validated against
265
+ # the active accessibility policy, and an embedded `where` that
266
266
  # must be recursively translated so blocked operators (e.g.
267
- # +$where+ nested inside) cannot smuggle through.
267
+ # `$where` nested inside) cannot smuggle through.
268
268
  def translate_cross_class_value(op, val, depth:, agent: nil)
269
269
  return val unless val.is_a?(Hash)
270
270
  val = val.transform_keys(&:to_s)
@@ -321,20 +321,20 @@ module Parse
321
321
  translate_value(val, depth: depth, agent: agent)
322
322
  end
323
323
 
324
- # Validate the owning-object class named by a +$relatedTo+ constraint.
324
+ # Validate the owning-object class named by a `$relatedTo` constraint.
325
325
  #
326
- # +$relatedTo+ has the shape +{ object: <Pointer>, key: <relation field> }+.
327
- # Unlike +$inQuery+ / +$select+ it carries no +className+ / inner +where+,
326
+ # `$relatedTo` has the shape `{ object: <Pointer>, key: <relation field> }`.
327
+ # Unlike `$inQuery` / `$select` it carries no `className` / inner `where`,
328
328
  # so it is NOT a {CROSS_CLASS_OPERATORS} entry — but it DOES reach across
329
329
  # to a second class: the owning object whose relation is being read. Left
330
330
  # unvalidated, an agent narrowed to one class (or with a class globally
331
- # +agent_hidden+) could still name a relation anchored on an off-allowlist
332
- # class via the +object+ pointer. That is the SDK-surface analog of
333
- # GHSA-wmwx-jr2p-4j4r, where Parse Server's own +$relatedTo+ bypassed the
331
+ # `agent_hidden`) could still name a relation anchored on an off-allowlist
332
+ # class via the `object` pointer. That is the SDK-surface analog of
333
+ # GHSA-wmwx-jr2p-4j4r, where Parse Server's own `$relatedTo` bypassed the
334
334
  # owning object's ACL. Run the owning class through the same accessibility
335
335
  # policy as every other cross-class hop, then translate the value normally.
336
336
  #
337
- # Fails closed when the owning class cannot be resolved from +object+: an
337
+ # Fails closed when the owning class cannot be resolved from `object`: an
338
338
  # unresolvable pointer is exactly the shape that would otherwise slip the
339
339
  # check, so refuse the constraint rather than skip it.
340
340
  def translate_related_to_value(val, depth:, agent: nil)
@@ -351,10 +351,10 @@ module Parse
351
351
  translate_value(val, depth: depth, agent: agent)
352
352
  end
353
353
 
354
- # Extract the Parse class name of a +$relatedTo+ constraint's owning
355
- # object from its +object+ slot, which may be a Parse::Pointer, a Parse
356
- # pointer/relation hash (+{__type:, className:, objectId:}+, string or
357
- # symbol keys), or a storage-form string (+"ClassName$objectId"+).
354
+ # Extract the Parse class name of a `$relatedTo` constraint's owning
355
+ # object from its `object` slot, which may be a Parse::Pointer, a Parse
356
+ # pointer/relation hash (`{__type:, className:, objectId:}`, string or
357
+ # symbol keys), or a storage-form string (`"ClassName$objectId"`).
358
358
  # Returns nil when no class can be resolved so the caller can fail closed.
359
359
  def related_to_owning_class(val)
360
360
  return nil unless val.is_a?(Hash)
@@ -371,7 +371,7 @@ module Parse
371
371
  end
372
372
 
373
373
  # Hook into the agent-side accessibility check when the agent
374
- # module is loaded; in pure-unit contexts where +Parse::Agent::Tools+
374
+ # module is loaded; in pure-unit contexts where `Parse::Agent::Tools`
375
375
  # has not been loaded, default to a no-op rather than raising —
376
376
  # the strict check is enforced wherever the agent dispatches.
377
377
  def assert_embedded_class_accessible!(op, class_name, agent: nil)
@@ -121,22 +121,22 @@ module Parse
121
121
  end
122
122
  end
123
123
 
124
- # Raised inside the +call_method+ tool when the resolved
125
- # +ClassName.method_name+ is excluded by the agent instance's
126
- # +methods:+ filter. The execute() rescue maps this to a
127
- # +:tool_filtered+ error_code so consumers can distinguish "the
124
+ # Raised inside the `call_method` tool when the resolved
125
+ # `ClassName.method_name` is excluded by the agent instance's
126
+ # `methods:` filter. The execute() rescue maps this to a
127
+ # `:tool_filtered` error_code so consumers can distinguish "the
128
128
  # filter excluded this method" from "this method isn't declared
129
129
  # agent-callable" (a Parse::Error) or "the tier doesn't allow it"
130
- # (a +:permission_denied+).
130
+ # (a `:permission_denied`).
131
131
  class MethodFiltered < AgentError; end
132
132
 
133
133
  # Raised at semantic-search dispatch time when at least one class in
134
- # the model registry declares +agent_tenant_scope+ but the class
134
+ # the model registry declares `agent_tenant_scope` but the class
135
135
  # being searched does not. In a tenant-aware deployment an
136
136
  # un-scoped searchable surface would let an agent retrieve across
137
137
  # tenant boundaries, so the gate is a hard refusal, not a warning.
138
138
  # Enforced at dispatch (when all classes are loaded) rather than at
139
- # +agent_searchable+ declaration time so class-load order can't
139
+ # `agent_searchable` declaration time so class-load order can't
140
140
  # produce a false negative.
141
141
  class MissingTenantScope < AgentError; end
142
142
 
@@ -145,5 +145,27 @@ module Parse
145
145
  # `:refuse`. A SecurityError subclass so it routes through execute's
146
146
  # security rescue and is never swallowed.
147
147
  class PromptInjectionDetected < SecurityError; end
148
+
149
+ # Raised by Parse::Agent::Tools.invoke when a tool name is recognized
150
+ # (advertised in a permission tier / passes the gates) but has no
151
+ # handler in this SDK version. The built-in raw-CRUD write/admin tools
152
+ # (`create_object`, `update_object`, `delete_object`, `create_class`,
153
+ # `delete_class`) are declared in the write/admin tiers but ship
154
+ # without an implementation — invoking one previously raised a bare
155
+ # `NoMethodError` that collapsed to an opaque "internal error" on the
156
+ # wire. This turns that into a clear, typed refusal so the caller
157
+ # learns the tool is not implemented rather than that the SDK broke.
158
+ class NotImplemented < AgentError
159
+ attr_reader :tool_name
160
+
161
+ def initialize(tool_name, message = nil)
162
+ @tool_name = tool_name.to_s
163
+ super(message || "Tool '#{@tool_name}' is recognized but not implemented in this " \
164
+ "SDK version. The built-in raw create/update/delete tools are not " \
165
+ "available; expose the operation as an application method via " \
166
+ "`agent_method` + `call_method`, or register a handler with " \
167
+ "`Parse::Agent::Tools.register`.")
168
+ end
169
+ end
148
170
  end
149
171
  end
@@ -1183,10 +1183,21 @@ module Parse
1183
1183
  # Wire format for each SSE event (note: trailing blank line is required
1184
1184
  # by the SSE spec):
1185
1185
  #
1186
- # event: progress\n
1186
+ # event: message\n
1187
1187
  # data: <json>\n
1188
1188
  # \n
1189
1189
  #
1190
+ # EVERY frame — progress notifications, list-changed notifications,
1191
+ # and the final JSON-RPC response alike — carries the event name
1192
+ # `message`. MCP Streamable HTTP defines exactly one SSE event type
1193
+ # for JSON-RPC traffic; clients discriminate by inspecting the
1194
+ # envelope (`method` present => notification, `id` + `result`/`error`
1195
+ # => response), NOT by the SSE event name. Earlier releases emitted
1196
+ # `event: progress` and `event: response`, which real MCP clients
1197
+ # silently discard — they match only the default `message` type — so
1198
+ # the final response never arrived and the call appeared to hang.
1199
+ # Do not reintroduce custom event names.
1200
+ #
1190
1201
  # @api private
1191
1202
  class SSEBody
1192
1203
  # Sentinel pushed to the queue when the worker is done.
@@ -1546,6 +1557,9 @@ module Parse
1546
1557
  # The `total` field is omitted (rather than nil) so the wire
1547
1558
  # shape matches the spec's optional-field convention.
1548
1559
  #
1560
+ # Emitted as `event: message` — see the SSEBody class docs. The
1561
+ # payload's `method` is what marks it as progress.
1562
+ #
1549
1563
  # @param elapsed [Float] seconds elapsed since the stream started.
1550
1564
  # @return [String] SSE event string (includes trailing blank line).
1551
1565
  def build_progress_event(elapsed)
@@ -1557,15 +1571,14 @@ module Parse
1557
1571
  "progress" => elapsed,
1558
1572
  },
1559
1573
  })
1560
- "event: progress\ndata: #{data}\n\n"
1574
+ "event: message\ndata: #{data}\n\n"
1561
1575
  end
1562
1576
 
1563
1577
  # Format a `notifications/tools/list_changed` or
1564
1578
  # `notifications/prompts/list_changed` SSE event. Both
1565
1579
  # notifications have no `params` — the wire shape is just the
1566
- # JSON-RPC envelope with `method` set. SSE event name is
1567
- # "message" since this is not a progress notification (the
1568
- # progress event name is reserved for progress notifications).
1580
+ # JSON-RPC envelope with `method` set. Emitted as `event: message`,
1581
+ # like every other frame on this stream.
1569
1582
  #
1570
1583
  # @param method [String] full MCP method string.
1571
1584
  # @return [String] SSE event string (includes trailing blank line).
@@ -1599,7 +1612,7 @@ module Parse
1599
1612
  "method" => "notifications/progress",
1600
1613
  "params" => params,
1601
1614
  })
1602
- "event: progress\ndata: #{data}\n\n"
1615
+ "event: message\ndata: #{data}\n\n"
1603
1616
  end
1604
1617
 
1605
1618
  # Build the callback the dispatcher block passes into
@@ -1631,12 +1644,17 @@ module Parse
1631
1644
  end
1632
1645
  end
1633
1646
 
1634
- # Format the final `response` SSE event.
1647
+ # Format the final JSON-RPC response SSE event.
1648
+ #
1649
+ # Emitted as `event: message` (NOT `event: response`) — an MCP
1650
+ # client matching only the default `message` type would otherwise
1651
+ # discard the response and block until its own timeout. The
1652
+ # envelope's `id` + `result`/`error` is what marks it final.
1635
1653
  #
1636
1654
  # @param body [Hash] JSON-RPC response envelope.
1637
1655
  # @return [String] SSE event string (includes trailing blank line).
1638
1656
  def build_response_event(body)
1639
- "event: response\ndata: #{JSON.generate(body)}\n\n"
1657
+ "event: message\ndata: #{JSON.generate(body)}\n\n"
1640
1658
  end
1641
1659
 
1642
1660
  # Build an internal-error JSON-RPC envelope (id may be nil at this layer).
@@ -1755,9 +1773,9 @@ module Parse
1755
1773
  end
1756
1774
  end
1757
1775
 
1758
- # SSE wire form for a server→client notification. Event name "message"
1759
- # (not "progress"/"response", which are reserved for the request-scoped
1760
- # SSE path).
1776
+ # SSE wire form for a server→client notification. Event name
1777
+ # "message" the single event type MCP Streamable HTTP defines for
1778
+ # JSON-RPC traffic, matching the request-scoped SSE path.
1761
1779
  def format_event(notification)
1762
1780
  "event: message\ndata: #{JSON.generate(notification)}\n\n"
1763
1781
  end
@@ -450,15 +450,15 @@ module Parse
450
450
  # preview-only execution. When false (default), passing dry_run: true in
451
451
  # arguments is refused at dispatch time with :invalid_argument.
452
452
  # @param permitted_keys [Array<Symbol,String>, nil] when provided,
453
- # +call_method+ refuses any +arguments+ key not in this list.
453
+ # `call_method` refuses any `arguments` key not in this list.
454
454
  # Without this, an LLM (or a prompt-injection payload) can
455
- # pass arbitrary keys through a method that splats with +**+,
456
- # reaching protected columns like +_hashed_password+ or +ACL+.
457
- # Highly recommended on any +agent_write+/+agent_admin+ method
455
+ # pass arbitrary keys through a method that splats with `**`,
456
+ # reaching protected columns like `_hashed_password` or `ACL`.
457
+ # Highly recommended on any `agent_write`/`agent_admin` method
458
458
  # that takes a kwargs splat.
459
459
  # @param parameters [Hash, nil] when provided, a JSON Schema (as a
460
- # Ruby Hash) describing the +arguments+ object. Surfaced in
461
- # +tools/list+ so the LLM submits properly-shaped inputs and
460
+ # Ruby Hash) describing the `arguments` object. Surfaced in
461
+ # `tools/list` so the LLM submits properly-shaped inputs and
462
462
  # stricter MCP clients can validate before dispatch.
463
463
  # @return [Hash] the method metadata
464
464
  def agent_method(method_name, description = nil, permission: :readonly,