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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97d41b5209b0365349812d166993421d4f25ae734dff25141c141decf0c54cc4
4
- data.tar.gz: c0e3b483da29241890500469939f79a44396966e321827283dc3be327d7bdc6c
3
+ metadata.gz: b8f26110cea78b4a3fc393dce2ac3aada7d73442f7d1e970e2aa71d88d3ecba2
4
+ data.tar.gz: 753ead47dcc377ba89e5eca4d2e93a3f7767f5e71a168124f6f180fe7549b5a1
5
5
  SHA512:
6
- metadata.gz: e01b065bc9e57307d95dcba6ae474a025130d694f5cf30560441af37d31f047a79fd1c65dc3e8543024a1d449a31b30bf82d7a3ba4908a512b61975de4558353
7
- data.tar.gz: d5b0a9ac73f321dc78953924d175549bf6d5ee96918907851b2dc1589dd3131941d2c1f5eb30a166b9b806abea70d416ae7f869eb9dfcf781b3f10f1879f629b
6
+ metadata.gz: c1261b7e8db1b2d61e69fe70a1327829afd8d4c1b0960cb1ca1b3d5b418fcbc81b21dc05921a59865d7c226108392f0c3968a982277410872585b5cc4f42760a
7
+ data.tar.gz: 64f53a649209415edf4c3cecaab383f5ea01e0f106711ae867bfc7ac6d506dde592bd09b628a7956ccb621428ff4cea9c6f5970660d40465581496092b3d18ee
data/CHANGELOG.md CHANGED
@@ -1,11 +1,126 @@
1
1
  ## parse-stack-next Changelog
2
2
 
3
+ ### 5.5.6
4
+
5
+ #### MCP clients now receive the SSE response instead of hanging
6
+
7
+ - **FIXED**: The Streamable HTTP SSE transport framed its events with custom
8
+ event names — `event: progress` for `notifications/progress` and
9
+ `event: response` for the final JSON-RPC response. MCP defines a single SSE
10
+ event type for JSON-RPC traffic, and clients match only the default
11
+ `message` type, so every frame the SDK emitted was silently discarded: tool
12
+ progress never surfaced, and, critically, the terminating response never
13
+ arrived, leaving the client blocked until its own timeout on any streaming
14
+ `tools/call`. All frames on both the request-scoped POST stream and the
15
+ server-to-client GET notification stream now carry `event: message`, and
16
+ clients discriminate from the JSON-RPC envelope (`method` present for a
17
+ notification, `id` plus `result`/`error` for a response) as the protocol
18
+ intends. Deployments that worked around this with a middleware rewriting
19
+ the event name can drop it.
20
+
21
+ ### 5.5.5
22
+
23
+ #### Agent `call_method` runs under the caller's scope, not the master key
24
+
25
+ - **FIXED**: Instance `call_method` resolved its receiver with a bare
26
+ `klass.find` on the master-backed default client, so a scoped agent could
27
+ read — and through a write method, mutate — any object by id. The receiver
28
+ (both the real call and the dry-run existence check) is now fetched through
29
+ the requesting agent's scope: a session scope fetches with the session token
30
+ (Parse Server enforces ACL/CLP), an `acl_user` / `acl_role` scope routes
31
+ mongo-direct (ACL simulation), and a row the scope cannot read fails closed
32
+ to "not found". The method body runs inside the caller's session so its own
33
+ writes inherit the same principal, and an instance write/admin method under
34
+ an `acl_user` / `acl_role` scope — which has no session token to bind — is
35
+ refused rather than run with master authority.
36
+ - **FIXED**: Aggregation pipelines now default-deny joins under an active
37
+ tenant scope. A `$lookup` / `$graphLookup` / `$unionWith` sub-pipeline runs
38
+ in the joined collection's context with no tenant predicate injected, so any
39
+ such join could surface rows from other tenants; all are refused while a
40
+ tenant scope is active. The `$unionWith` bare-string shorthand
41
+ (`{ "$unionWith" => "Class" }`) is now covered by that guard as well as the
42
+ class-allowlist, hidden/underscore, and CLP-`find` gates, which previously
43
+ inspected only the Hash form.
44
+ - **IMPROVED**: Unimplemented agent tools now raise a typed
45
+ `Parse::Agent::NotImplemented` error, and tools without a handler are omitted
46
+ from tool listings.
47
+
48
+ #### Blank or injected credentials no longer escalate to the master key
49
+
50
+ - **FIXED**: An explicitly-supplied blank or whitespace `session_token:` no
51
+ longer falls through to the master key. It now fails closed to an anonymous
52
+ request (master suppressed, no session header) and does not fall back to a
53
+ bound or ambient token. `Parse.with_session` likewise rejects a blank token
54
+ at the source instead of storing a whitespace ambient that the request layer
55
+ would then drop.
56
+ - **FIXED**: The control options `use_master_key` and `session` can no longer
57
+ be set from a string-keyed conditions hash — the "forward a request params
58
+ hash straight into `Query.new` / `where`" pattern. `{"use_master_key" =>
59
+ true}` from untrusted params was an ACL/CLP-bypass mass-assignment; these two
60
+ keys are now honored only when passed as symbols (code-authored), and a
61
+ string form is treated as an ordinary field constraint with a warning.
62
+
63
+ #### Atlas Search and aggregation stay ACL-scoped
64
+
65
+ - **FIXED**: Atlas Search keeps `$search` at pipeline stage 0 while running the
66
+ scoped ACL/CLP enforcement chain (ACL `$match` folded after `$search`,
67
+ protectedFields strip, pointerFields filter, and a protected-field `$expr`
68
+ oracle guard). Query-derived constraints on the builder-block, options, and
69
+ autocomplete paths are converted to MongoDB storage form before the
70
+ post-`$search` `$match`, so a pointer/date/objectId constraint targets the
71
+ correct storage column and no longer fails BSON serialization.
72
+ - **FIXED**: `search_with_stage` rejects a non-`$search` stage and any
73
+ `returnStoredSource`, which could otherwise return only index-stored fields
74
+ (without `_rperm`), be read as public by the ACL match, and leak restricted
75
+ rows.
76
+ - **FIXED**: A scoped `$geoNear` folds the ACL predicate into `$geoNear.query`
77
+ rather than prepending a `$match`, keeping `$geoNear` at stage 0 so the query
78
+ no longer fails under ACL scoping. The fold embeds a copy of the caller's
79
+ existing query so the caller's pipeline is not mutated.
80
+
81
+ #### Outbound fetches and the shipped CLI are hardened
82
+
83
+ - **FIXED**: `parse-console --url` now parses and scheme-validates its argument
84
+ (HTTP(S) only) before fetching, instead of passing it to `Kernel#open` —
85
+ closing a path where a `|command` argument would be executed as a subprocess.
86
+ - **IMPROVED**: The Cohere reranker `base_url` is validated to reject
87
+ credentials embedded in the URL and plaintext HTTP to non-loopback hosts.
88
+ - **IMPROVED**: Remote file/image fetches enforce a streaming size cap that
89
+ aborts mid-download; the per-call `max_bytes:` ceiling is validated as a
90
+ positive integer, so a zero, negative, or non-numeric value is refused up
91
+ front rather than silently rejecting every response.
92
+
93
+ #### Redaction, locking, and API ergonomics
94
+
95
+ - **IMPROVED**: Query-string credential redaction for logging and request
96
+ profiling is now a single shared implementation, so the two cannot drift. It
97
+ redacts the value of any credential-bearing parameter name (matched by a
98
+ generic rule with a safe-list, and aware of percent-encoded names) and uses
99
+ possessive-quantifier matching so it stays linear on pathological URLs.
100
+ - **FIXED**: The in-process fallback mutex registry (used when no shared lock
101
+ store is configured) is bounded, and its eviction can no longer reclaim a
102
+ mutex a caller is about to lock — a pending-acquirer reservation prevents two
103
+ callers from getting distinct mutexes for the same key.
104
+ - **IMPROVED**: `call_function` and `trigger_job` accept request options as
105
+ bare keyword arguments (for example `call_function("f", {}, session_token:
106
+ t)`), merged with the explicit `opts:` hash for back-compat.
107
+
108
+ #### Packaging, CI, and docs
109
+
110
+ - **CHANGED**: The shipped-gem file list was narrowed to an allowlist of the
111
+ library, binaries, docs, examples, and standard metadata.
112
+ - **CHANGED**: The SHA-pinned `ruby/setup-ruby` GitHub action was bumped to
113
+ `v1.318.0` across the CI, docs, and release workflows.
114
+ - **IMPROVED**: README, test-server, and guide updates, plus YARD styling.
115
+
3
116
  ### 5.5.4
4
117
 
5
118
  #### Dependency updates
6
119
 
7
- - **CHANGED**: Bumped `graphql` to 2.6.5 (from 2.6.3) in the locked
8
- dependencies.
120
+ - **CHANGED**: Bumped locked dependencies to their latest compatible
121
+ releases — `graphql` 2.6.3 → 2.6.5, `i18n` 1.15.1 → 1.15.2, `json` 2.19.9
122
+ → 2.21.1, `pp` 0.6.3 → 0.6.4, `rdoc` 7.2.0 → 8.0.0, `tilt` 2.7.0 → 2.8.0,
123
+ and `yard` 0.9.44 → 0.9.45.
9
124
 
10
125
  ### 5.5.3
11
126
 
@@ -4506,17 +4621,17 @@ query.where(genre: "rock").last_updated(limit: 3)
4506
4621
 
4507
4622
  ```ruby
4508
4623
  # Default behavior - pointers for storage (backward compatible)
4509
- post.assets.as_json
4624
+ post.documents.as_json
4510
4625
  # => [{"__type"=>"Pointer", "className"=>"Document", "objectId"=>"abc"}, ...]
4511
4626
 
4512
4627
  # Serialize with fetched fields (no autofetch, pointers stay as pointers)
4513
- post.assets.as_json(pointers_only: false)
4628
+ post.documents.as_json(pointers_only: false)
4514
4629
  # => [{"objectId"=>"abc", "file"=>{...}, "caption"=>"My photo", ...}, ...]
4515
4630
 
4516
- # In webhooks, manually override assets serialization:
4631
+ # In webhooks, manually override documents serialization:
4517
4632
  cloud_results.map do |post|
4518
4633
  json = post.as_json
4519
- json['assets'] = post.assets.as_json(pointers_only: false) if post.assets.any?
4634
+ json['documents'] = post.documents.as_json(pointers_only: false) if post.documents.any?
4520
4635
  json
4521
4636
  end
4522
4637
  ```
data/README.md CHANGED
@@ -4,14 +4,14 @@
4
4
 
5
5
  A full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [parse-stack-next](https://github.com/neurosynq/parse-stack-next) is a Ruby client SDK, REST client, and Active Model ORM for [Parse Server](http://parseplatform.org/), combining a low-level API client, a query engine, an object-relational mapper (ORM), and a Cloud Code Webhooks rack application in a single gem.
6
6
 
7
- ### What's new in 5.5
7
+ ## What's new in 5.5
8
8
 
9
9
  - **5.5.0 — Multimodal bytes-fetch with magic-byte MIME verification** — `embed_image ..., source: :bytes` has the SDK download an image itself through the `Parse::File.safe_open_url` SSRF primitive, verify the content by **magic-byte sniff** (the `Content-Type` header is never consulted — a `.jpg` URL serving HTML is refused), cross-check the URL extension, enforce a `Parse::Embeddings.allowed_image_types` allowlist, strip EXIF/XMP metadata **by default** (JPEG APP1, PNG `eXIf`, WebP `EXIF`/`XMP ` chunks; opt out with `exif_strip: false`), and forward the verified bytes to Voyage/Cohere as a base64 data URI. No provider-side URL fetch occurs, so the `trust_provider_url_fetch` sentinel is not required — the host allowlist still applies. See [CHANGELOG.md](./CHANGELOG.md)
10
10
  - **5.5.0 — Embedding-model migration tooling** — `Class.reembed!(only_stale: true)` bulk re-embeds rows through the current provider/model (resumable; skips rows already current), driven by the new auto-declared `<into>_meta` provenance sibling (`{provider, model, dimensions, modality, embedded_at}`, stamped on every recompute). `Parse::Embeddings::BatchEmbedder` adds batch-level requests-per-minute pacing and exponential backoff for bulk jobs; `Parse::Embeddings::Cache.enable!` adds an opt-in query-embed cache keyed by `(provider, model, input_type, input-hash)` so repeated identical queries skip the provider round-trip. See [CHANGELOG.md](./CHANGELOG.md)
11
11
  - **5.5.0 — Vector index drift detection** — on first auto-discovered use of an Atlas vectorSearch index, the SDK verifies the deployed index's `numDimensions`/`similarity` against the `:vector` property declaration and confirms a registered `agent_tenant_scope` field is covered as a `type: "filter"` path. Policy via `Parse::VectorSearch.index_drift_policy` (`:warn` default / `:raise` / `:ignore`). `Parse::Schema::SearchIndexMigrator` now auto-includes the tenant-scope field in `vectorSearch` declarations, so newly created indexes support tenant-scoped pre-filtering out of the box. See [CHANGELOG.md](./CHANGELOG.md)
12
12
  - **5.5.0 — Retrieval spend-cap and filter hardening** — the per-tenant embedding spend cap now covers every query-embed path (`find_similar(text:)`, `hybrid_search(text:)`, `Parse::Retrieval.retrieve`), not just the `semantic_search` agent tool; tenant identity resolves through the ambient `Parse.with_cache_tenant` scope. Caller-supplied retrieval filters now translate Parse pointer values to storage form (`{ owner: user }` → `{ "_p_owner" => "_User$id" }`), so pointer filters match rows instead of silently matching nothing. See [CHANGELOG.md](./CHANGELOG.md)
13
13
 
14
- ### What's new in 5.4
14
+ ## What's new in 5.4
15
15
 
16
16
  - **5.4.0 — Hybrid search + reranking for RAG** — `Class.hybrid_search(text:, lexical:, vector:, k:, fusion:)` fuses a lexical Atlas Search branch with a `$vectorSearch` branch using reciprocal-rank fusion (RRF): lexical search nails exact tokens (codes, proper nouns), vector search nails paraphrase, and fusing the two beats either alone. Each branch enforces ACL/CLP independently before fusion (no separate hydration fetch to secure); results carry `#hybrid_score` / `#hybrid_ranks`. `Parse::VectorSearch::Hybrid.rank_fusion_supported?` detects Atlas 8.0+ native `$rankFusion` by a cached behavioural probe (native execution is opt-in; client-side RRF is the always-enforced default). `Parse::Retrieval::Reranker` adds cross-encoder reranking (`Reranker::Cohere` over `/v2/rerank`, plus a deterministic `Reranker::Fixture`), wired into `Parse::Retrieval.retrieve(hybrid:, rerank:)`. `Parse::Embeddings::SpendCap` adds an opt-in per-tenant embedding token cap (hard-refuse) at the `semantic_search` agent-tool boundary. See [CHANGELOG.md](./CHANGELOG.md) and [`docs/atlas_vector_search_guide.md`](./docs/atlas_vector_search_guide.md)
17
17
  - **5.4.0 — Vector backfill, visibility, and webhook redaction** — `Class.embed_pending!` backfills embeddings for records whose managed `:vector` field is null (objectId-cursor pagination); `Parse::Object#compute_embedding!` forces an in-place recompute without a save; `vector_visibility :owner_only | :public` controls whether a class's vectors appear in `as_json` by default; and webhook trigger payloads now strip declared `:vector` columns by default (a `:public` class keeps them). See [CHANGELOG.md](./CHANGELOG.md)
@@ -20,13 +20,13 @@ A full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [
20
20
  - **5.4.0 — Audience hash queries persist correctly** — `Parse::Audience#query` is now stored as a JSON string on the wire to match Parse Server's `_Audience.query` column type, so saving an audience with a `Hash` query no longer fails the server schema check. The public API is unchanged — assign a `Hash`, read a `Hash` back. See [CHANGELOG.md](./CHANGELOG.md)
21
21
  - **5.4.0 — Faster AtlasSearch role-cache expiry** — `Parse::AtlasSearch` `role_cache_ttl` now defaults to 30 seconds (was 120) so a role grant or revoke is reflected in `$search` ACL decisions sooner, at the cost of slightly more frequent role lookups. See [CHANGELOG.md](./CHANGELOG.md)
22
22
 
23
- ### What's new in 5.3
23
+ ## What's new in 5.3
24
24
 
25
25
  - **5.3.0 — Run webhook handlers (and clients) as the calling user** — Parse Server embeds the caller's live session token in every trigger webhook fired by a logged-in user. A handler can now opt in to acting on the server *as that user* — full ACL/CLP/`protectedFields` enforcement, no master key. `payload.session_token` exposes the captured token (`nil` for master-key requests; still scrubbed from `payload.user`/`payload.object`/`as_json`/logs); `payload.user_agent` returns a client-mode `Parse::Agent`, and `payload.user_client` a non-master `Parse::Client` with the token **bound** so even raw REST calls authorize as the user. The same user-scoped client is available client-side via `Parse::User#session_client` and the `Parse::Client#become(token)` primitive, with `Parse::Client#with_session { … }` for block scoping. Backed by a new `Parse::Client.new(session_token:)` option. See [Acting as the calling user](#acting-as-the-calling-user)
26
26
  - **5.3.0 — Pluralized class-name aliases** — referencing the plural form of a model constant now resolves to that class, so `Posts.where(:author.eq => user).count` works for a class `Post`. The alias is created lazily on first reference and is the *same class object*, so every class method (`query`/`where`, `count`, `find`, `all`, scopes) works through it and `Posts.parse_class` still returns `"Post"`. Because it is the same class it adds no `Parse::Object.descendants` entry and never registers a separate Parse schema class. Classes whose name already ends in `s` are skipped by the automatic path; non-Parse plurals and typos fall through to a normal `NameError`. On by default — opt out with `Parse.pluralized_aliases = false` (or `PARSE_PLURALIZED_ALIASES=false`). For a custom plural, an `s`-ending class, or a namespaced model, call `pluralized_alias!` in the class body. See [Pluralized class-name aliases](#pluralized-class-name-aliases)
27
27
  - **5.3.0 — afterSave create reports changed fields; force_ssl-consistent file equality** — a trigger handler that keys off dirty tracking now sees every field on an `afterSave` *create*, symmetric with `afterSave` updates: the built object marks each populated data property changed (from `nil`) while `createdAt`/`updatedAt`/`ACL`/`objectId` stay clean and object readability, `new?`, and `existed?` are unchanged — so a handler that builds a payload from `*_changed?` / `changes` works uniformly across create and update. Separately, `Parse::File#==` now compares both files through the canonical `url` reader, so two files at the same location compare equal regardless of `Parse::File.force_ssl` (and `a == b` matches `b == a`), and a re-signed URL for the same object no longer reads as a change. See [Cloud Code Triggers](#cloud-code-triggers)
28
28
 
29
- ### What's new in 5.2
29
+ ## What's new in 5.2
30
30
 
31
31
  - **5.2.1 — Webhook triggers receive the full Parse object** — trigger handlers (`beforeSave`/`afterSave`/…) now get the complete server object (`createdAt`/`updatedAt`, `ACL`, internal fields); only live credentials (session tokens, password hashes) are stripped. `Parse::Object#existed?` / `#new?` are reliable in `afterSave`, `afterSave` updates carry dirty tracking, and the model lifecycle runs in ActiveModel order — `before_save → before_create` then `after_create → after_save` — so `before_create` now fires for REST/JS/Auth0 creates (and `after_save` no longer double-fires). See [Cloud Code Triggers](#cloud-code-triggers)
32
32
  - **Retrieval layer — `Parse::Retrieval` (`Parse::RAG`)** — `Parse::Retrieval.retrieve(query:, klass:, k:, filter:, tenant_scope:, …)` embeds a natural-language query, runs Atlas `$vectorSearch` through the existing ACL-enforcing `find_similar`, and splits each retrieved document's text field into scored `Parse::Retrieval::Chunk`s. Chunking is presentation-only (embedding stays one-vector-per-record), via `Parse::Retrieval::Chunker::FixedSizeOverlap(size:, overlap:, by:, max_chunks_per_document:)` (subclass `Chunker::Base` for custom strategies). ACL is mongo-direct (no REST two-stage); tenant scope folds into the Atlas pre-filter
@@ -40,7 +40,7 @@ A full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [
40
40
 
41
41
  See [CHANGELOG.md](./CHANGELOG.md) for the full 5.2 entry.
42
42
 
43
- ### What's new in 5.1
43
+ ## What's new in 5.1
44
44
 
45
45
  - **`Parse::File` URL normalization + presigned-URL stash** — `Parse::File#url=` and `attributes=` now strip signed-URL query parameters (`X-Amz-Signature`, `AWSAccessKeyId`, `Key-Pair-Id`, etc.) before storage; the bare canonical URL lands in `@url`, and the original signed URL is stashed in `file.presigned_url` with a data-driven expiry in `file.presigned_url_expires_at`. New `file.presigned_url_valid?(buffer: 60)` predicate, configurable `Parse::File.signed_url_policy = :strip | :raise`, and `Parse::File.log_filter` / `log_filter_strict` regexes for `lograge` / Sentry / Honeybadger scrubbers. `Parse::File#inspect` no longer emits the URL — see CHANGELOG for the error-reporter payload migration callout
46
46
  - **`Parse::Lock` — public TTL-bounded mutual-exclusion primitive** — `Parse::Lock.acquire(key, ttl:, wait:) { … }` exposes the Redis-backed lock previously hidden inside `first_or_create!` as a first-class API. In-process `Mutex` fallback for memory-backed caches, fails closed on backend errors, HMAC-keyed via `PARSE_STACK_LOCK_SECRET`, namespace-separated from `first_or_create!` so the two cannot collide
@@ -56,7 +56,7 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full 5.2 entry.
56
56
 
57
57
  See [CHANGELOG.md](./CHANGELOG.md) for the full 5.1 entry, including breaking changes, migration callouts, and the round-by-round security review notes.
58
58
 
59
- ### What's new in 5.0
59
+ ## What's new in 5.0
60
60
 
61
61
  - **RAG foundation** — `:vector` property type, `Parse::Embeddings` provider registry shipping built-in adapters for OpenAI, Cohere (v3 + v4.0 Matryoshka text-mode), Voyage (incl. open-weight `voyage-4-nano` and `voyage-multimodal-3` text-mode), Jina v3/v4/v5/code, Qwen 3 (DashScope), and a generic `LocalHTTP` client for Ollama / LM Studio / vLLM / TEI. `Klass.find_similar(vector:/text:, k:)` over Atlas `$vectorSearch`, and an `embed` class macro that digest-tracks source fields so vectors only recompute when content changes
62
62
  - **`Parse::Cache::Redis`** — Moneta-compatible Redis cache wrapper with a built-in `ConnectionPool`, optional `cache_namespace:` for multi-tenant Redis sharing, and graceful degrade on pool saturation
@@ -64,12 +64,12 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full 5.1 entry, including breaking ch
64
64
  - **MCP transport hardening** — Streamable HTTP `Mcp-Session-Id` header (renamed from `X-MCP-Session-Id`, **breaking**), `MCP-Protocol-Version` validation, `DELETE /` session termination, structured-content (`outputSchema`) on built-in tools, optional `health_path:` liveness probe
65
65
  - **`Parse::GraphQL::TypeGenerator`** — generate `graphql-ruby` types directly from your `Parse::Object` subclasses (no Parse Server round-trip), with `:vector` columns surfaced as `[Float]` and association registries (`has_one_associations`, `has_many_associations`) populated at DSL time
66
66
  - **LiveQuery promoted to stable** — the experimental warning is removed; `Parse.live_query_enabled = true` is retained as a network-egress safety toggle, not a stability gate
67
- - **Server-version deprecation warning** — one-shot warning when connecting to Parse Server below the supported floor (currently 7.0.0); silence with `Parse.suppress_server_version_warning = true`
67
+ - **Server-version deprecation warning** — one-shot warning when connecting to a Parse Server older than the configured threshold (default `7.0.0`, override with `PARSE_DEPRECATED_SERVER_VERSION_BELOW`); silence with `Parse.suppress_server_version_warning = true`. The **supported baseline is Parse Server 9.x** (the SDK is developed and tested against a pinned `parse-server:9.9.0`); the default warning threshold is intentionally conservative so older deployments only get an advisory, not a hard break.
68
68
  - **`mongo_relation_index :field, dedup: true`** — register a compound `{owningId, relatedId}` UNIQUE on relation join collections to prevent duplicate-pair subscriptions without breaking `has_many` semantics
69
69
 
70
70
  See [CHANGELOG.md](./CHANGELOG.md) for the full 5.0 entry, including security-hardening notes and Ruby 3.x cleanup.
71
71
 
72
- ### Core capabilities
72
+ ## Core capabilities
73
73
 
74
74
  > **Vector search requires MongoDB Atlas (or Atlas Local).** The `:vector`
75
75
  > property, `find_similar`, `hybrid_search`, and `Parse::Retrieval` all
@@ -104,16 +104,16 @@ Below is a [quick start guide](#overview). See also the [Usage Guide](./docs/usa
104
104
 
105
105
  > **Note:** API reference docs are published at [neurosynq.github.io/parse-stack-next](https://neurosynq.github.io/parse-stack-next/index.html). Generated via YARD from the current source; covers the full 5.x surface.
106
106
 
107
- ### Credits
107
+ ## Credits
108
108
 
109
109
  This project (`parse-stack-next`) is a continuation of the [Parse Stack framework](https://github.com/modernistik/parse-stack) originally created by [Modernistik](https://www.modernistik.com). We are grateful for their foundational work and continue to build upon it under the [neurosynq](https://github.com/neurosynq) organization.
110
110
 
111
- ### Code Status
111
+ ## Code Status
112
112
  [![Gem Version](https://img.shields.io/gem/v/parse-stack-next.svg)](https://rubygems.org/gems/parse-stack-next)
113
113
  [![Downloads](https://img.shields.io/gem/dt/parse-stack-next.svg)](https://rubygems.org/gems/parse-stack-next)
114
114
  [![Releases](https://img.shields.io/github/v/release/neurosynq/parse-stack-next)](https://github.com/neurosynq/parse-stack-next/releases)
115
115
 
116
- #### Tutorial Videos
116
+ ### Tutorial Videos
117
117
 
118
118
  The following videos were recorded for the original parse-stack gem. The model, query, and association surface they cover is unchanged in parse-stack-next, so they remain a useful introduction; see the [Usage Guide](./docs/usage_guide.md) for v5.x-specific features (vector search, Redis cache, agent tools).
119
119
 
@@ -177,7 +177,7 @@ Parse.auto_generate_models!
177
177
  # or define custom Subclasses (Highly Recommended)
178
178
  class Song < Parse::Object
179
179
  property :name
180
- property :play, :integer
180
+ property :plays, :integer
181
181
  property :audio_file, :file
182
182
  property :tags, :array
183
183
  property :released, :date
@@ -248,7 +248,7 @@ Runnable, self-contained scripts live in [`examples/`](examples/) — see
248
248
 
249
249
  ## Release History
250
250
 
251
- **Current version: 5.4.0** | **Ruby 3.2+ required**
251
+ **Current version: 5.5.5** | **Ruby 3.2+ required**
252
252
 
253
253
  The 5.0 highlights (vector search / RAG, pooled Redis cache, AS::N instrumentation, MCP transport hardening, GraphQL type generation) are summarized in the [What's new in 5.0](#whats-new-in-50) section above. Earlier releases are recorded below.
254
254
 
@@ -294,6 +294,19 @@ Per-version detail lives in [CHANGELOG.md](./CHANGELOG.md) and on the [Releases
294
294
 
295
295
  The 1.x line is the original [`modernistik/parse-stack`](https://github.com/modernistik/parse-stack) — Active Model ORM, REST client, query DSL, associations, and Cloud Code webhooks for Parse Server. `parse-stack-next` is a continuation of that work; the first release published under the new gem name is **4.5.0** (above), on RubyGems as [`parse-stack-next`](https://rubygems.org/gems/parse-stack-next).
296
296
 
297
+ ## Guides
298
+
299
+ In-depth guides for the larger subsystems live under [`docs/`](./docs):
300
+
301
+ - [Usage Guide](./docs/usage_guide.md) — end-to-end tour of models, queries, and CRUD.
302
+ - [Client SDK Guide](./docs/client_sdk_guide.md) — running as an unprivileged (non-master) client: session tokens, `with_session`, row-level ACL enforcement, LiveQuery from the client side.
303
+ - [ACL & CLP Guide](./docs/acl_clp_guide.md) — access-control model, Class-Level Permissions, and how enforcement differs across REST vs mongo-direct.
304
+ - [Direct MongoDB Guide](./docs/mongodb_direct_guide.md) — the `results_direct` / `Parse::MongoDB` path, its authority model, and when to reach for it.
305
+ - [MongoDB Index Optimization Guide](./docs/mongodb_index_optimization_guide.md) — index DSL, schema migrators, and the writer-URI triple-gate.
306
+ - [Atlas & Vector Search Guide](./docs/atlas_vector_search_guide.md) — `$search`, vector search, `find_similar`, `hybrid_search`, embeddings, and RAG retrieval.
307
+ - [AI Agent & MCP Guide](./docs/mcp_guide.md) — `Parse::Agent`, the built-in tools, permission tiers, and the MCP server surface.
308
+ - [Webhooks Guide](./docs/webhooks_guide.md) — Cloud Code webhook receiver, triggers, field guards, and replay protection.
309
+
297
310
  ## Table of Contents
298
311
 
299
312
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
data/bin/parse-console CHANGED
@@ -76,7 +76,15 @@ opt_parser = OptionParser.new do |o|
76
76
  o.on('--url URL', 'Load the env config from a url.') do |url|
77
77
  begin
78
78
  puts "Loading config: #{url}"
79
- json = JSON.load open(url)
79
+ # SEC-20: do NOT pass a user-supplied string to bare Kernel#open
80
+ # `open("|command")` executes a subprocess. Parse an explicit URI and
81
+ # require an HTTP(S) scheme, then use open-uri's URI::HTTP#open (a real
82
+ # network fetch), never the Kernel form.
83
+ uri = URI.parse(url)
84
+ unless uri.is_a?(URI::HTTP) # URI::HTTPS < URI::HTTP, so this admits both
85
+ raise "Refusing to load config from a non-HTTP(S) URL: #{url.inspect}"
86
+ end
87
+ json = JSON.load(uri.open)
80
88
  raise "Contents not a JSON hash." unless json.is_a?(Hash)
81
89
  json.each { |k,v| ENV[k.upcase] = v }
82
90
  opts[:server_url] ||= ENV['PARSE_SERVER_URL']