parse-stack-next 5.5.3 → 5.5.5

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -4
  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/metadata_dsl.rb +6 -6
  14. data/lib/parse/agent/tools.rb +250 -59
  15. data/lib/parse/agent.rb +42 -30
  16. data/lib/parse/api/aggregate.rb +3 -3
  17. data/lib/parse/api/cloud_functions.rb +19 -10
  18. data/lib/parse/api/objects.rb +8 -8
  19. data/lib/parse/api/users.rb +9 -9
  20. data/lib/parse/atlas_search/session.rb +34 -34
  21. data/lib/parse/atlas_search.rb +243 -110
  22. data/lib/parse/client/body_builder.rb +10 -10
  23. data/lib/parse/client/logging.rb +5 -2
  24. data/lib/parse/client/profiling.rb +5 -2
  25. data/lib/parse/client/protocol.rb +1 -1
  26. data/lib/parse/client/url_redaction.rb +94 -0
  27. data/lib/parse/client.rb +43 -28
  28. data/lib/parse/embeddings/image_fetch.rb +6 -1
  29. data/lib/parse/embeddings/voyage.rb +16 -17
  30. data/lib/parse/live_query/client.rb +7 -7
  31. data/lib/parse/live_query/subscription.rb +1 -1
  32. data/lib/parse/lock.rb +1 -1
  33. data/lib/parse/lock_backend.rb +118 -2
  34. data/lib/parse/model/acl.rb +24 -24
  35. data/lib/parse/model/classes/job_schedule.rb +8 -8
  36. data/lib/parse/model/classes/job_status.rb +9 -9
  37. data/lib/parse/model/classes/role.rb +49 -49
  38. data/lib/parse/model/classes/session.rb +2 -2
  39. data/lib/parse/model/classes/user.rb +66 -66
  40. data/lib/parse/model/core/builder.rb +7 -7
  41. data/lib/parse/model/core/create_lock.rb +1 -1
  42. data/lib/parse/model/core/properties.rb +4 -4
  43. data/lib/parse/model/file.rb +57 -16
  44. data/lib/parse/model/model.rb +19 -19
  45. data/lib/parse/model/object.rb +38 -38
  46. data/lib/parse/model/pointer.rb +4 -4
  47. data/lib/parse/model/push.rb +5 -5
  48. data/lib/parse/mongodb.rb +84 -26
  49. data/lib/parse/pipeline_security.rb +2 -2
  50. data/lib/parse/query/constraints.rb +38 -38
  51. data/lib/parse/query.rb +151 -75
  52. data/lib/parse/retrieval/reranker/cohere.rb +30 -0
  53. data/lib/parse/schema.rb +1 -1
  54. data/lib/parse/stack/version.rb +1 -1
  55. data/lib/parse/stack.rb +23 -10
  56. data/lib/parse/two_factor_auth/user_extension.rb +25 -25
  57. data/lib/parse/webhooks/payload.rb +35 -35
  58. data/lib/parse/webhooks/registration.rb +2 -2
  59. data/lib/parse/webhooks/replay_protection.rb +16 -16
  60. data/lib/parse/webhooks.rb +11 -11
  61. data/parse-stack-next.gemspec +19 -1
  62. metadata +2 -38
  63. data/.bundle/config +0 -5
  64. data/.env.sample +0 -138
  65. data/.env.test +0 -10
  66. data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
  67. data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
  68. data/.github/dependabot.yml +0 -13
  69. data/.github/workflows/codeql.yml +0 -44
  70. data/.github/workflows/docs.yml +0 -39
  71. data/.github/workflows/release.yml +0 -43
  72. data/.github/workflows/ruby.yml +0 -38
  73. data/.gitignore +0 -56
  74. data/.ruby-version +0 -1
  75. data/.solargraph.yml +0 -22
  76. data/.vscode/settings.json +0 -3
  77. data/.yardopts +0 -19
  78. data/Gemfile +0 -43
  79. data/Gemfile.lock +0 -198
  80. data/Makefile +0 -63
  81. data/Rakefile +0 -825
  82. data/config/parse-config.json +0 -12
  83. data/scripts/debug-ips.js +0 -35
  84. data/scripts/docker/Dockerfile.parse +0 -17
  85. data/scripts/docker/atlas-init.js +0 -284
  86. data/scripts/docker/docker-compose.atlas.yml +0 -80
  87. data/scripts/docker/docker-compose.test.yml +0 -159
  88. data/scripts/docker/docker-compose.verifyemail.yml +0 -4
  89. data/scripts/docker/mongo-init.js +0 -21
  90. data/scripts/docker/preflight.sh +0 -76
  91. data/scripts/eval_mcp_with_lm_studio.rb +0 -274
  92. data/scripts/start-parse.sh +0 -154
  93. data/scripts/start_mcp_server.rb +0 -78
  94. data/scripts/test_server_connection.rb +0 -82
  95. data/scripts/vector_prototype/create_vector_index.js +0 -105
  96. data/scripts/vector_prototype/fetch_embeddings.py +0 -241
  97. data/scripts/vector_prototype/fixture_manifest.json +0 -9
  98. data/scripts/vector_prototype/query_prototype.rb +0 -84
  99. data/scripts/vector_prototype/run.sh +0 -34
@@ -438,17 +438,38 @@ input[name=q]::placeholder {
438
438
  margin-top: 1.4em;
439
439
  }
440
440
 
441
- #listing ul.alpha li.letter h2 {
441
+ /* YARD emits the section letters as bare `<li class="letter">A</li>`
442
+ * (no inner h2), so style the li itself. */
443
+ #listing ul.alpha li.letter {
442
444
  color: var(--accent);
443
- border-bottom: 2px solid var(--accent);
444
- display: inline-block;
445
- padding: 0 .8em .2em 0;
445
+ font-weight: 650;
446
446
  }
447
447
 
448
448
  #listing ul li a {
449
449
  border-bottom: 1px dotted var(--border);
450
450
  }
451
451
 
452
+ /* The "(Parse::Constraint)" origin annotations; stock #666 is too dim
453
+ * against the dark surfaces. */
454
+ #listing ul small {
455
+ color: var(--fg-muted);
456
+ }
457
+
458
+ /* Stock style.css stripes index rows with hardcoded light grays
459
+ * (li.r1 #f0f0f0 / li.r2 #fafafa) that glow white in dark mode. */
460
+ li.r1 { background: var(--bg-soft); }
461
+ li.r2 { background: var(--bg); }
462
+
463
+ /* File Listing pills at the top of _index.html. */
464
+ .index_inline_list li,
465
+ .index_inline_list li.r1,
466
+ .index_inline_list li.r2 {
467
+ background: var(--bg-soft);
468
+ border: 1px solid var(--border);
469
+ border-radius: var(--radius-sm);
470
+ margin-right: 6px;
471
+ }
472
+
452
473
  /* -------- inheritance / mixin lists -------- */
453
474
 
454
475
  dl.box {
@@ -892,9 +913,13 @@ h2.h2_sum,
892
913
 
893
914
  /* The collapse arrows in the class-tree iframe are a base64 PNG
894
915
  * sprite of dark glyphs on a transparent background — invisible
895
- * against our dark surface. CSS filter inverts them in place. */
896
- #full_list li a.toggle {
897
- filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
916
+ * against our dark surface. CSS filter inverts them in place.
917
+ * Dark mode only: in light mode the stock dark glyphs are correct,
918
+ * and inverting them would wash them out against the light rail. */
919
+ @media (prefers-color-scheme: dark) {
920
+ #full_list li a.toggle {
921
+ filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
922
+ }
898
923
  }
899
924
 
900
925
  /* ============================================================
@@ -928,11 +953,29 @@ h2.h2_sum,
928
953
  padding: 1px 6px !important;
929
954
  }
930
955
 
931
- /* But never on the code blocks themselves (the pre.code descendants). */
956
+ /* But never on the code blocks themselves (the pre.code descendants).
957
+ * The scoped variants are required, not belt-and-braces: file pages
958
+ * (README + guides) compile fenced blocks to <pre><code class="ruby">,
959
+ * and with both rules !important the positive `#filecontents code`
960
+ * (0,1,0,1) would otherwise beat a bare `pre code` (0,0,0,2) — the
961
+ * inline-level <code> then draws its border on every wrapped line
962
+ * fragment, streaking the whole block. */
932
963
  pre code,
933
964
  pre tt,
934
965
  pre.code code,
935
- pre.code tt {
966
+ pre.code tt,
967
+ .docstring pre code,
968
+ .docstring pre tt,
969
+ .tags pre code,
970
+ .tags pre tt,
971
+ .summary pre code,
972
+ .summary pre tt,
973
+ #filecontents pre code,
974
+ #filecontents pre tt,
975
+ .discussion pre code,
976
+ .discussion pre tt,
977
+ .method_details pre code,
978
+ .method_details pre tt {
936
979
  background: transparent !important;
937
980
  border: 0 !important;
938
981
  padding: 0 !important;
@@ -209,19 +209,43 @@ a:visited {
209
209
  * parse-stack-next overlay (sidebar / class-list iframe)
210
210
  * ============================================================ */
211
211
 
212
+ /* Scheme-aware palette. Light is the default; the dark block below
213
+ * mirrors common.css's prefers-color-scheme handling. Keeping the
214
+ * overlay unconditional (as it originally was) made the rail
215
+ * unreadable in light mode: common.css paints #full_list with the
216
+ * light surface while these rules kept the near-white dark-mode
217
+ * text (its `#full_list li .item a` selector out-specifies
218
+ * common.css's `#full_list li a` regardless of load order). */
212
219
  :root {
213
- --ps-bg: #1E1F22;
214
- --ps-bg-soft: #2A2C30;
215
- --ps-bg-hover: #34373C;
216
- --ps-fg: #E6E8EB;
217
- --ps-fg-muted: #9CA3AF;
218
- --ps-accent: #E63946;
219
- --ps-link: #4FB8F4;
220
- --ps-border: #3A3D42;
220
+ --ps-bg: #FFFFFF;
221
+ --ps-bg-soft: #F8F9FA;
222
+ --ps-bg-hover: #E8EAED;
223
+ --ps-fg: #1F2328;
224
+ --ps-fg-muted: #6B7280;
225
+ --ps-accent: #B22234;
226
+ --ps-link: #169CEE;
227
+ --ps-border: #E5E7EB;
228
+ --ps-scroll-thumb: #C7CBD1;
229
+ --ps-header-shadow: 0 1px 3px rgba(0, 0, 0, .08);
221
230
  --ps-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter",
222
231
  Roboto, "Helvetica Neue", Arial, sans-serif;
223
232
  }
224
233
 
234
+ @media (prefers-color-scheme: dark) {
235
+ :root {
236
+ --ps-bg: #1E1F22;
237
+ --ps-bg-soft: #2A2C30;
238
+ --ps-bg-hover: #34373C;
239
+ --ps-fg: #E6E8EB;
240
+ --ps-fg-muted: #9CA3AF;
241
+ --ps-accent: #E63946;
242
+ --ps-link: #4FB8F4;
243
+ --ps-border: #3A3D42;
244
+ --ps-scroll-thumb: #4a4d52;
245
+ --ps-header-shadow: 0 1px 3px rgba(0, 0, 0, .3);
246
+ }
247
+ }
248
+
225
249
  body {
226
250
  background: var(--ps-bg) !important;
227
251
  color: var(--ps-fg);
@@ -238,7 +262,7 @@ h1 {
238
262
  .fixed_header {
239
263
  background: var(--ps-bg-soft) !important;
240
264
  border-bottom: 1px solid var(--ps-border);
241
- box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
265
+ box-shadow: var(--ps-header-shadow);
242
266
  }
243
267
 
244
268
  #search input,
@@ -299,7 +323,7 @@ h1 {
299
323
 
300
324
  ::-webkit-scrollbar { width: 8px; height: 8px; }
301
325
  ::-webkit-scrollbar-track { background: var(--ps-bg); }
302
- ::-webkit-scrollbar-thumb { background: #4a4d52; border-radius: 4px; }
326
+ ::-webkit-scrollbar-thumb { background: var(--ps-scroll-thumb); border-radius: 4px; }
303
327
  ::-webkit-scrollbar-thumb:hover { background: var(--ps-link); }
304
328
 
305
329
  /* ============================================================
@@ -381,7 +405,10 @@ body > #content {
381
405
  }
382
406
 
383
407
  /* The collapse toggle PNG is a dark-on-transparent sprite; invert it
384
- * so the arrow shows on dark backgrounds. */
385
- #full_list li a.toggle {
386
- filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
408
+ * so the arrow shows on dark backgrounds. Dark mode only — in light
409
+ * mode the stock dark glyphs are already correct. */
410
+ @media (prefers-color-scheme: dark) {
411
+ #full_list li a.toggle {
412
+ filter: invert(0.85) hue-rotate(180deg) brightness(1.6);
413
+ }
387
414
  }
@@ -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
@@ -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,