borinquenkid commented on code in PR #16725:
URL: https://github.com/apache/grails-core/pull/16725#discussion_r4365056470
##########
.agents/skills/mongodb-developer/SKILL.md:
##########
@@ -0,0 +1,122 @@
+---
+name: mongodb-developer
+description: Guide for working in the grails-data-mongodb module (GORM for
MongoDB) — codec-based document mapping, GeoJSON, embedded documents, TTL
indexes, multi-tenancy, and Testcontainers-backed tests. Use this when changing
code or tests under grails-data-mongodb.
+license: Apache-2.0
+---
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+## What I Do
+
+- Provide repository-specific guidance for the `grails-data-mongodb` project
(GORM for MongoDB — a document-database GORM implementation, not a relational
one; do not bring Hibernate/binder mental models here).
+- Guide changes around `MongoMappingContext`,
`BsonPersistentEntityCodec`/`PersistentEntityCodec`, and the
codec-registry-based encode/decode pipeline.
+- Keep changes aligned with the Testcontainers-backed testing pattern this
module actually uses.
+
+## When to Use Me
+
+Activate this skill when working on the MongoDB module, especially for:
+
+- Changes under `grails-data-mongodb/**` (any of its 9 subprojects — see
Module Structure).
+- Document/collection mapping, codec, or GeoJSON-type work.
+- Index (including TTL) creation/reconciliation logic.
+- Multi-tenancy or multi-connection MongoDB work.
+
+## Module Context
+
+`grails-data-mongodb` has no `settings.gradle` of its own — it's part of the
root aggregate build, same as `grails-data-hibernate7`. All of root
`AGENTS.md`'s rules (jakarta, `@GrailsCompileStatic`,
CodeNarc/Checkstyle/PMD/SpotBugs, `dependencies.gradle`/BOM) apply here
unmodified.
+
+Nine Gradle projects live under this directory, mapped to real Gradle project
names (not always `grails-data-mongodb-<dirname>` — check before assuming):
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `core/` | `:grails-data-mongodb-core` | The actual GORM-for-MongoDB
implementation: `MongoDatastore`, `MongoMappingContext`, `MongoQuery`, GeoJSON
types |
+| `bson/` | `:grails-data-mongodb-bson` | Low-level, GORM-independent BSON
codec machinery (`BsonPersistentEntityCodec`, per-type property codecs,
hand-rolled JSON tokenizer) |
+| `ext/` | `:grails-data-mongodb-ext` | A single file, Groovy extension
methods on the raw MongoDB driver (`distinct`, `watch`/change streams,
`deleteMany`) |
+| `boot-plugin/` | `:grails-data-mongodb-spring-boot` | Spring Boot
auto-configuration (`MongoDbGormAutoConfiguration`) |
+| `spring-data/` | `:grails-data-mongodb-spring-data` | Lets GORM-for-MongoDB
and Spring Data MongoDB share a `MongoClient`/codecs/transaction |
+| `grails-plugin/` | `:grails-data-mongodb` (note: not `-grails-plugin`
suffixed) | Classic Grails plugin descriptor (`MongodbGrailsPlugin`); also
ships the app-facing `grails.test.mongodb.MongoSpec` test base via
`testFixtures` |
+| `gson-templates/` | `:grails-data-mongodb-gson-templates` | JSON Views
(`.gson`) for `ObjectId` and GeoJSON types |
+| `embedded/` | `:grails-data-mongodb-embedded` | Starts a real `mongod`
(Flapdoodle, a `compileOnly` dependency the application adds itself) before the
application context refreshes and publishes its URL into the configured
properties; for applications, not for this module's own tests |
+| `docs/` | `:grails-data-mongodb-docs` | Asciidoc manual + groovydoc
aggregation only, no source |
+
+## Key Classes and Responsibilities
+
+There is no single `GrailsDomainBinder`-equivalent class — the responsibility
splits across three cooperating layers:
+
+### Mapping (the binder equivalent)
+
+- `MongoMappingContext` (`core/.../mongo/config/MongoMappingContext.java`) —
owns the private inner `MongoDocumentMappingFactory extends
AbstractGormMappingFactory<MongoCollection, MongoAttribute>`, which is the
actual binder: forces the `_id` field name, applies the global
`stringIdDefaultStoredAs` default, wires codec-registry-backed custom types,
and registers all GeoJSON custom types.
+- `MongoCollection` (`core/.../config/MongoCollection.groovy`) — entity-level
mapping (collection name, database, `writeConcern`, indices, sort).
+- `MongoAttribute` (`core/.../config/MongoAttribute.groovy`) — property-level
mapping (`reference` for DBRef vs. plain identifier, `field`, `geoIndex`,
`index`, `indexAttributes`).
+- `MappingBuilder.document { ... }`
(`core/.../grails/mongodb/mapping/MappingBuilder.groovy`) — the programmatic
DSL entry point.
+
+### Codec pipeline (the runtime read/write engine)
+
+- `BsonPersistentEntityCodec` (`bson/.../BsonPersistentEntityCodec.groovy`)
and its Mongo subclass `PersistentEntityCodec`
(`core/.../engine/codecs/PersistentEntityCodec.groovy`) — a static
`ENCODERS`/`DECODERS` registry keyed by `PersistentProperty` subtype
(`Identity`, `TenantId`, `Simple`, `Embedded`, `EmbeddedCollection`, `Custom`,
`Basic`, plus Mongo-specific `OneToOne`/`ManyToOne`/`OneToMany`/`ManyToMany`).
`encode`/`decode` walk `entity.persistentProperties` and dispatch per property
kind. `PersistentEntityCodec` also implements MongoDB's partial-update path
(`encodeUpdate`) via `DirtyCheckable`, producing `$set`/`$unset` documents
directly rather than rewriting the whole document.
+
+### Datastore
+
+- `MongoDatastore` (`core/.../mongo/MongoDatastore.java`) — owns index
creation/reconciliation against live MongoDB and transaction-capability
detection based on cluster topology.
+
+## Confirmed MongoDB-Specific Concepts (implemented — don't assume beyond this
list)
+
+- **Embedded documents & embedded collections** —
`Embedded`/`EmbeddedCollection` property kinds, with dedicated in-place
`$set`/`$unset` update logic distinct from top-level property updates.
+- **GeoJSON types** —
`grails.mongodb.geo.{Point,LineString,Polygon,MultiPoint,MultiLineString,MultiPolygon,GeometryCollection,Box,Circle,Sphere,Shape}`,
each with a custom-type marshaller. Query support: `near`, `nearSphere`,
`withinCircle`, `withinBox`, `withinPolygon`, `geoWithin`, `geoIntersects`.
Legacy `2d`/`2dsphere` indexes via `MongoAttribute.geoIndex(String)`.
+- **TTL indexes** — property-level `indexAttributes: [expireAfterSeconds: N]`
materializes a MongoDB TTL index, reconciled in place via `collMod` rather than
drop/rebuild (see Pitfalls).
+- **DBRef vs. plain identifier** — `MongoAttribute.reference` selects how a
`ToOne`/`ToMany` association is stored: `true` writes a `com.mongodb.DBRef`,
`false` (the default) writes the associated identifier. It does not embed;
embedding is `static embedded = [...]`, a different property kind (see the
first bullet). An agent that sets `reference: false` to embed a document will
store an id instead.
+- **Schemaless/dynamic attributes** — `DynamicAttributes` trait support baked
into `BsonPersistentEntityCodec` and `MongoEntity`.
+- **Change streams** — `MongoExtensions.watch(...)`, a thin Groovy-extension
wrapper over the driver's native `watch()`, not a GORM-level abstraction.
+- **Multi-document transactions** — opt-in via `grails.mongodb.transactional`,
gated on cluster topology (`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED` supported;
`STANDALONE` falls back with a one-time warning).
+- **Multi-tenancy / multiple named connections** — supported
(discriminator-based multi-tenancy, `grails.mongodb.connections`).
+
+### Not implemented — do not document or rely on these
+
+- **`shard "name"` in the mapping DSL is a no-op.** There is no `shard` method
on `Collection`/`MongoCollection`. The call silently falls through to the
generic `Entity.methodMissing` catch-all, which just records it as an ad hoc
property config — it never issues a MongoDB `shardCollection` command.
Verified: zero `shardCollection` calls anywhere in `core/src/main`. If you see
`shard "..."` in a mapping block, treat it as dead syntax, not a real feature.
+- **Capped collections** — not implemented anywhere in this module.
+
+## Testing Rules
+
+The module's own tests use a real MongoDB via Testcontainers; the `embedded`
subproject is a runtime feature for applications, not a test fallback. Docker
or Podman must be running on the host.
+
+- **`GrailsDataTckSpec<GrailsDataMongoTckManager>`** is the dominant pattern
(111+ specs) — the same shared TCK harness the Hibernate modules use
(`grails-datamapping-tck`), parameterized with `GrailsDataMongoTckManager`
(`core/src/test/groovy/.../GrailsDataMongoTckManager.groovy`), which starts a
real `MongoDBContainer` in `setupSpec()`/stops it in `cleanupSpec()`. Its
`destroy()` after each feature deletes the documents of every non-system
database but keeps the collections and their indexes, so a later feature in the
same fork can inherit an index an earlier one created; a spec that creates an
index has to drop it itself.
+ ```groovy
+ class DocumentMappingSpec extends
GrailsDataTckSpec<GrailsDataMongoTckManager> {
+ void setupSpec() {
+ manager.registerDomainClasses(CustomMapping)
+ }
+ void "test custom document mapping"() { ... }
+ }
+ ```
+ Domain classes are top-level classes in the same spec file, registered via
`manager.registerDomainClasses(...)` in `setupSpec()` — same convention as
`HibernateGormDatastoreSpec`.
+- **`AutoStartedMongoSpec`** (`grails-testing-support-mongodb`) — a
lighter-weight base used by a handful of specs; a Spock global extension
auto-injects `@Shared MongoDBContainer dbContainer` and auto-constructs a
`@Shared MongoDatastore` field if present.
+- **`grails.test.mongodb.MongoSpec`** — a separate, *app-facing* base class
shipped via `testFixtures` in `grails-plugin/`, for Grails-application-level
Mongo integration tests. Don't confuse it with the two internal patterns above
— a spec being both a `MongoSpec` and an `AutoStartedMongoSpec` is explicitly
forbidden.
+- The MongoDB image version comes from `-PmongodbContainerVersion`, which CI
always passes; see `gradle/mongodb-forked-test-config.gradle`.
+- `core` applies `gradle/mongodb-forked-test-config.gradle` (`maxParallelForks
= rootProject.ext.configuredTestParallel`, the forked, parallel layout); every
other subproject (`bson`, `ext`, `embedded`, `spring-data`, `boot-plugin`,
`grails-plugin`, `gson-templates`) applies `gradle/mongodb-test-config.gradle`,
which runs serially (`maxParallelForks = 1`). Check which file a subproject
applies before changing its parallelism.
+- `-PskipMongodbTests` / `-PonlyMongodbTests` gate these tests at the root,
matching the `Container missing` row in root `AGENTS.md`'s Common Issues table
— use `-PskipMongodbTests` when Docker isn't available.
+
+## Change Workflow
+
+1. Identify which layer owns the behavior: mapping
(`MongoMappingContext`/`MongoCollection`/`MongoAttribute`) vs. codec/runtime
(`BsonPersistentEntityCodec`/`PersistentEntityCodec`) vs. datastore-level
(`MongoDatastore`, index/transaction handling).
+2. Check whether the change affects the general `bson/` codec machinery (used
by any BSON-backed consumer) or is genuinely Mongo-specific (`core/`) — don't
put Mongo-only logic in `bson/`.
+3. Update or add specs via `GrailsDataTckSpec<GrailsDataMongoTckManager>`,
registering any new domain classes as top-level classes in the same spec file.
+4. Run the affected subproject's tests with Docker/Podman running; expect
`core` tests to run serially.
Review Comment:
Fixed in e08436b6ad: step 4 says `core` runs in parallel forks, each with
its own container, and `-PmaxTestParallel=1` makes it serial.
##########
.agents/skills/mongodb-developer/SKILL.md:
##########
@@ -0,0 +1,122 @@
+---
+name: mongodb-developer
+description: Guide for working in the grails-data-mongodb module (GORM for
MongoDB) — codec-based document mapping, GeoJSON, embedded documents, TTL
indexes, multi-tenancy, and Testcontainers-backed tests. Use this when changing
code or tests under grails-data-mongodb.
+license: Apache-2.0
+---
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+## What I Do
+
+- Provide repository-specific guidance for the `grails-data-mongodb` project
(GORM for MongoDB — a document-database GORM implementation, not a relational
one; do not bring Hibernate/binder mental models here).
+- Guide changes around `MongoMappingContext`,
`BsonPersistentEntityCodec`/`PersistentEntityCodec`, and the
codec-registry-based encode/decode pipeline.
+- Keep changes aligned with the Testcontainers-backed testing pattern this
module actually uses.
+
+## When to Use Me
+
+Activate this skill when working on the MongoDB module, especially for:
+
+- Changes under `grails-data-mongodb/**` (any of its 9 subprojects — see
Module Structure).
+- Document/collection mapping, codec, or GeoJSON-type work.
+- Index (including TTL) creation/reconciliation logic.
+- Multi-tenancy or multi-connection MongoDB work.
+
+## Module Context
+
+`grails-data-mongodb` has no `settings.gradle` of its own — it's part of the
root aggregate build, same as `grails-data-hibernate7`. All of root
`AGENTS.md`'s rules (jakarta, `@GrailsCompileStatic`,
CodeNarc/Checkstyle/PMD/SpotBugs, `dependencies.gradle`/BOM) apply here
unmodified.
+
+Nine Gradle projects live under this directory, mapped to real Gradle project
names (not always `grails-data-mongodb-<dirname>` — check before assuming):
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `core/` | `:grails-data-mongodb-core` | The actual GORM-for-MongoDB
implementation: `MongoDatastore`, `MongoMappingContext`, `MongoQuery`, GeoJSON
types |
+| `bson/` | `:grails-data-mongodb-bson` | Low-level, GORM-independent BSON
codec machinery (`BsonPersistentEntityCodec`, per-type property codecs,
hand-rolled JSON tokenizer) |
+| `ext/` | `:grails-data-mongodb-ext` | A single file, Groovy extension
methods on the raw MongoDB driver (`distinct`, `watch`/change streams,
`deleteMany`) |
+| `boot-plugin/` | `:grails-data-mongodb-spring-boot` | Spring Boot
auto-configuration (`MongoDbGormAutoConfiguration`) |
+| `spring-data/` | `:grails-data-mongodb-spring-data` | Lets GORM-for-MongoDB
and Spring Data MongoDB share a `MongoClient`/codecs/transaction |
+| `grails-plugin/` | `:grails-data-mongodb` (note: not `-grails-plugin`
suffixed) | Classic Grails plugin descriptor (`MongodbGrailsPlugin`); also
ships the app-facing `grails.test.mongodb.MongoSpec` test base via
`testFixtures` |
+| `gson-templates/` | `:grails-data-mongodb-gson-templates` | JSON Views
(`.gson`) for `ObjectId` and GeoJSON types |
+| `embedded/` | `:grails-data-mongodb-embedded` | Starts a real `mongod`
(Flapdoodle, a `compileOnly` dependency the application adds itself) before the
application context refreshes and publishes its URL into the configured
properties; for applications, not for this module's own tests |
+| `docs/` | `:grails-data-mongodb-docs` | Asciidoc manual + groovydoc
aggregation only, no source |
+
+## Key Classes and Responsibilities
+
+There is no single `GrailsDomainBinder`-equivalent class — the responsibility
splits across three cooperating layers:
+
+### Mapping (the binder equivalent)
+
+- `MongoMappingContext` (`core/.../mongo/config/MongoMappingContext.java`) —
owns the private inner `MongoDocumentMappingFactory extends
AbstractGormMappingFactory<MongoCollection, MongoAttribute>`, which is the
actual binder: forces the `_id` field name, applies the global
`stringIdDefaultStoredAs` default, wires codec-registry-backed custom types,
and registers all GeoJSON custom types.
+- `MongoCollection` (`core/.../config/MongoCollection.groovy`) — entity-level
mapping (collection name, database, `writeConcern`, indices, sort).
+- `MongoAttribute` (`core/.../config/MongoAttribute.groovy`) — property-level
mapping (`reference` for DBRef vs. plain identifier, `field`, `geoIndex`,
`index`, `indexAttributes`).
+- `MappingBuilder.document { ... }`
(`core/.../grails/mongodb/mapping/MappingBuilder.groovy`) — the programmatic
DSL entry point.
+
+### Codec pipeline (the runtime read/write engine)
+
+- `BsonPersistentEntityCodec` (`bson/.../BsonPersistentEntityCodec.groovy`)
and its Mongo subclass `PersistentEntityCodec`
(`core/.../engine/codecs/PersistentEntityCodec.groovy`) — a static
`ENCODERS`/`DECODERS` registry keyed by `PersistentProperty` subtype
(`Identity`, `TenantId`, `Simple`, `Embedded`, `EmbeddedCollection`, `Custom`,
`Basic`, plus Mongo-specific `OneToOne`/`ManyToOne`/`OneToMany`/`ManyToMany`).
`encode`/`decode` walk `entity.persistentProperties` and dispatch per property
kind. `PersistentEntityCodec` also implements MongoDB's partial-update path
(`encodeUpdate`) via `DirtyCheckable`, producing `$set`/`$unset` documents
directly rather than rewriting the whole document.
+
+### Datastore
+
+- `MongoDatastore` (`core/.../mongo/MongoDatastore.java`) — owns index
creation/reconciliation against live MongoDB and transaction-capability
detection based on cluster topology.
+
+## Confirmed MongoDB-Specific Concepts (implemented — don't assume beyond this
list)
+
+- **Embedded documents & embedded collections** —
`Embedded`/`EmbeddedCollection` property kinds, with dedicated in-place
`$set`/`$unset` update logic distinct from top-level property updates.
+- **GeoJSON types** —
`grails.mongodb.geo.{Point,LineString,Polygon,MultiPoint,MultiLineString,MultiPolygon,GeometryCollection,Box,Circle,Sphere,Shape}`,
each with a custom-type marshaller. Query support: `near`, `nearSphere`,
`withinCircle`, `withinBox`, `withinPolygon`, `geoWithin`, `geoIntersects`.
Legacy `2d`/`2dsphere` indexes via `MongoAttribute.geoIndex(String)`.
+- **TTL indexes** — property-level `indexAttributes: [expireAfterSeconds: N]`
materializes a MongoDB TTL index, reconciled in place via `collMod` rather than
drop/rebuild (see Pitfalls).
+- **DBRef vs. plain identifier** — `MongoAttribute.reference` selects how a
`ToOne`/`ToMany` association is stored: `true` writes a `com.mongodb.DBRef`,
`false` (the default) writes the associated identifier. It does not embed;
embedding is `static embedded = [...]`, a different property kind (see the
first bullet). An agent that sets `reference: false` to embed a document will
store an id instead.
+- **Schemaless/dynamic attributes** — `DynamicAttributes` trait support baked
into `BsonPersistentEntityCodec` and `MongoEntity`.
+- **Change streams** — `MongoExtensions.watch(...)`, a thin Groovy-extension
wrapper over the driver's native `watch()`, not a GORM-level abstraction.
+- **Multi-document transactions** — opt-in via `grails.mongodb.transactional`,
gated on cluster topology (`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED` supported;
`STANDALONE` falls back with a one-time warning).
+- **Multi-tenancy / multiple named connections** — supported
(discriminator-based multi-tenancy, `grails.mongodb.connections`).
+
+### Not implemented — do not document or rely on these
+
+- **`shard "name"` in the mapping DSL is a no-op.** There is no `shard` method
on `Collection`/`MongoCollection`. The call silently falls through to the
generic `Entity.methodMissing` catch-all, which just records it as an ad hoc
property config — it never issues a MongoDB `shardCollection` command.
Verified: zero `shardCollection` calls anywhere in `core/src/main`. If you see
`shard "..."` in a mapping block, treat it as dead syntax, not a real feature.
+- **Capped collections** — not implemented anywhere in this module.
+
+## Testing Rules
+
+The module's own tests use a real MongoDB via Testcontainers; the `embedded`
subproject is a runtime feature for applications, not a test fallback. Docker
or Podman must be running on the host.
+
+- **`GrailsDataTckSpec<GrailsDataMongoTckManager>`** is the dominant pattern
(111+ specs) — the same shared TCK harness the Hibernate modules use
(`grails-datamapping-tck`), parameterized with `GrailsDataMongoTckManager`
(`core/src/test/groovy/.../GrailsDataMongoTckManager.groovy`), which starts a
real `MongoDBContainer` in `setupSpec()`/stops it in `cleanupSpec()`. Its
`destroy()` after each feature deletes the documents of every non-system
database but keeps the collections and their indexes, so a later feature in the
same fork can inherit an index an earlier one created; a spec that creates an
index has to drop it itself.
+ ```groovy
+ class DocumentMappingSpec extends
GrailsDataTckSpec<GrailsDataMongoTckManager> {
+ void setupSpec() {
+ manager.registerDomainClasses(CustomMapping)
+ }
+ void "test custom document mapping"() { ... }
+ }
+ ```
+ Domain classes are top-level classes in the same spec file, registered via
`manager.registerDomainClasses(...)` in `setupSpec()` — same convention as
`HibernateGormDatastoreSpec`.
+- **`AutoStartedMongoSpec`** (`grails-testing-support-mongodb`) — a
lighter-weight base used by a handful of specs; a Spock global extension
auto-injects `@Shared MongoDBContainer dbContainer` and auto-constructs a
`@Shared MongoDatastore` field if present.
+- **`grails.test.mongodb.MongoSpec`** — a separate, *app-facing* base class
shipped via `testFixtures` in `grails-plugin/`, for Grails-application-level
Mongo integration tests. Don't confuse it with the two internal patterns above
— a spec being both a `MongoSpec` and an `AutoStartedMongoSpec` is explicitly
forbidden.
+- The MongoDB image version comes from `-PmongodbContainerVersion`, which CI
always passes; see `gradle/mongodb-forked-test-config.gradle`.
+- `core` applies `gradle/mongodb-forked-test-config.gradle` (`maxParallelForks
= rootProject.ext.configuredTestParallel`, the forked, parallel layout); every
other subproject (`bson`, `ext`, `embedded`, `spring-data`, `boot-plugin`,
`grails-plugin`, `gson-templates`) applies `gradle/mongodb-test-config.gradle`,
which runs serially (`maxParallelForks = 1`). Check which file a subproject
applies before changing its parallelism.
+- `-PskipMongodbTests` / `-PonlyMongodbTests` gate these tests at the root,
matching the `Container missing` row in root `AGENTS.md`'s Common Issues table
— use `-PskipMongodbTests` when Docker isn't available.
+
+## Change Workflow
+
+1. Identify which layer owns the behavior: mapping
(`MongoMappingContext`/`MongoCollection`/`MongoAttribute`) vs. codec/runtime
(`BsonPersistentEntityCodec`/`PersistentEntityCodec`) vs. datastore-level
(`MongoDatastore`, index/transaction handling).
+2. Check whether the change affects the general `bson/` codec machinery (used
by any BSON-backed consumer) or is genuinely Mongo-specific (`core/`) — don't
put Mongo-only logic in `bson/`.
+3. Update or add specs via `GrailsDataTckSpec<GrailsDataMongoTckManager>`,
registering any new domain classes as top-level classes in the same spec file.
+4. Run the affected subproject's tests with Docker/Podman running; expect
`core` tests to run serially.
+
+## Pitfalls to Avoid
+
+- Do not treat `shard "name"` as a functioning DSL keyword — it's a documented
no-op (see above). Don't build new features assuming it works, and flag it if
you're asked to "fix" sharding — the DSL surface exists but the implementation
doesn't.
+- Do not change a subproject's test parallelism without reading the test
config file it applies; the serial ones share one container.
Review Comment:
Fixed in e08436b6ad: replaced by what the subprojects run
(`bson`/`ext`/`grails-plugin` start nothing, `embedded`/`spring-data` an
embedded server, `boot-plugin`'s three `AutoStartedMongoSpec` specs a container
from `MongoContainerHolder`, one per thread).
##########
.agents/skills/mongodb-developer/SKILL.md:
##########
@@ -0,0 +1,122 @@
+---
+name: mongodb-developer
+description: Guide for working in the grails-data-mongodb module (GORM for
MongoDB) — codec-based document mapping, GeoJSON, embedded documents, TTL
indexes, multi-tenancy, and Testcontainers-backed tests. Use this when changing
code or tests under grails-data-mongodb.
+license: Apache-2.0
+---
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+## What I Do
+
+- Provide repository-specific guidance for the `grails-data-mongodb` project
(GORM for MongoDB — a document-database GORM implementation, not a relational
one; do not bring Hibernate/binder mental models here).
+- Guide changes around `MongoMappingContext`,
`BsonPersistentEntityCodec`/`PersistentEntityCodec`, and the
codec-registry-based encode/decode pipeline.
+- Keep changes aligned with the Testcontainers-backed testing pattern this
module actually uses.
+
+## When to Use Me
+
+Activate this skill when working on the MongoDB module, especially for:
+
+- Changes under `grails-data-mongodb/**` (any of its 9 subprojects — see
Module Structure).
+- Document/collection mapping, codec, or GeoJSON-type work.
+- Index (including TTL) creation/reconciliation logic.
+- Multi-tenancy or multi-connection MongoDB work.
+
+## Module Context
+
+`grails-data-mongodb` has no `settings.gradle` of its own — it's part of the
root aggregate build, same as `grails-data-hibernate7`. All of root
`AGENTS.md`'s rules (jakarta, `@GrailsCompileStatic`,
CodeNarc/Checkstyle/PMD/SpotBugs, `dependencies.gradle`/BOM) apply here
unmodified.
+
+Nine Gradle projects live under this directory, mapped to real Gradle project
names (not always `grails-data-mongodb-<dirname>` — check before assuming):
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `core/` | `:grails-data-mongodb-core` | The actual GORM-for-MongoDB
implementation: `MongoDatastore`, `MongoMappingContext`, `MongoQuery`, GeoJSON
types |
+| `bson/` | `:grails-data-mongodb-bson` | Low-level, GORM-independent BSON
codec machinery (`BsonPersistentEntityCodec`, per-type property codecs,
hand-rolled JSON tokenizer) |
+| `ext/` | `:grails-data-mongodb-ext` | A single file, Groovy extension
methods on the raw MongoDB driver (`distinct`, `watch`/change streams,
`deleteMany`) |
+| `boot-plugin/` | `:grails-data-mongodb-spring-boot` | Spring Boot
auto-configuration (`MongoDbGormAutoConfiguration`) |
+| `spring-data/` | `:grails-data-mongodb-spring-data` | Lets GORM-for-MongoDB
and Spring Data MongoDB share a `MongoClient`/codecs/transaction |
+| `grails-plugin/` | `:grails-data-mongodb` (note: not `-grails-plugin`
suffixed) | Classic Grails plugin descriptor (`MongodbGrailsPlugin`); also
ships the app-facing `grails.test.mongodb.MongoSpec` test base via
`testFixtures` |
+| `gson-templates/` | `:grails-data-mongodb-gson-templates` | JSON Views
(`.gson`) for `ObjectId` and GeoJSON types |
+| `embedded/` | `:grails-data-mongodb-embedded` | Starts a real `mongod`
(Flapdoodle, a `compileOnly` dependency the application adds itself) before the
application context refreshes and publishes its URL into the configured
properties; for applications, not for this module's own tests |
+| `docs/` | `:grails-data-mongodb-docs` | Asciidoc manual + groovydoc
aggregation only, no source |
+
+## Key Classes and Responsibilities
+
+There is no single `GrailsDomainBinder`-equivalent class — the responsibility
splits across three cooperating layers:
+
+### Mapping (the binder equivalent)
+
+- `MongoMappingContext` (`core/.../mongo/config/MongoMappingContext.java`) —
owns the private inner `MongoDocumentMappingFactory extends
AbstractGormMappingFactory<MongoCollection, MongoAttribute>`, which is the
actual binder: forces the `_id` field name, applies the global
`stringIdDefaultStoredAs` default, wires codec-registry-backed custom types,
and registers all GeoJSON custom types.
+- `MongoCollection` (`core/.../config/MongoCollection.groovy`) — entity-level
mapping (collection name, database, `writeConcern`, indices, sort).
+- `MongoAttribute` (`core/.../config/MongoAttribute.groovy`) — property-level
mapping (`reference` for DBRef vs. plain identifier, `field`, `geoIndex`,
`index`, `indexAttributes`).
+- `MappingBuilder.document { ... }`
(`core/.../grails/mongodb/mapping/MappingBuilder.groovy`) — the programmatic
DSL entry point.
+
+### Codec pipeline (the runtime read/write engine)
+
+- `BsonPersistentEntityCodec` (`bson/.../BsonPersistentEntityCodec.groovy`)
and its Mongo subclass `PersistentEntityCodec`
(`core/.../engine/codecs/PersistentEntityCodec.groovy`) — a static
`ENCODERS`/`DECODERS` registry keyed by `PersistentProperty` subtype
(`Identity`, `TenantId`, `Simple`, `Embedded`, `EmbeddedCollection`, `Custom`,
`Basic`, plus Mongo-specific `OneToOne`/`ManyToOne`/`OneToMany`/`ManyToMany`).
`encode`/`decode` walk `entity.persistentProperties` and dispatch per property
kind. `PersistentEntityCodec` also implements MongoDB's partial-update path
(`encodeUpdate`) via `DirtyCheckable`, producing `$set`/`$unset` documents
directly rather than rewriting the whole document.
+
+### Datastore
+
+- `MongoDatastore` (`core/.../mongo/MongoDatastore.java`) — owns index
creation/reconciliation against live MongoDB and transaction-capability
detection based on cluster topology.
+
+## Confirmed MongoDB-Specific Concepts (implemented — don't assume beyond this
list)
+
+- **Embedded documents & embedded collections** —
`Embedded`/`EmbeddedCollection` property kinds, with dedicated in-place
`$set`/`$unset` update logic distinct from top-level property updates.
+- **GeoJSON types** —
`grails.mongodb.geo.{Point,LineString,Polygon,MultiPoint,MultiLineString,MultiPolygon,GeometryCollection,Box,Circle,Sphere,Shape}`,
each with a custom-type marshaller. Query support: `near`, `nearSphere`,
`withinCircle`, `withinBox`, `withinPolygon`, `geoWithin`, `geoIntersects`.
Legacy `2d`/`2dsphere` indexes via `MongoAttribute.geoIndex(String)`.
+- **TTL indexes** — property-level `indexAttributes: [expireAfterSeconds: N]`
materializes a MongoDB TTL index, reconciled in place via `collMod` rather than
drop/rebuild (see Pitfalls).
+- **DBRef vs. plain identifier** — `MongoAttribute.reference` selects how a
`ToOne`/`ToMany` association is stored: `true` writes a `com.mongodb.DBRef`,
`false` (the default) writes the associated identifier. It does not embed;
embedding is `static embedded = [...]`, a different property kind (see the
first bullet). An agent that sets `reference: false` to embed a document will
store an id instead.
+- **Schemaless/dynamic attributes** — `DynamicAttributes` trait support baked
into `BsonPersistentEntityCodec` and `MongoEntity`.
+- **Change streams** — `MongoExtensions.watch(...)`, a thin Groovy-extension
wrapper over the driver's native `watch()`, not a GORM-level abstraction.
+- **Multi-document transactions** — opt-in via `grails.mongodb.transactional`,
gated on cluster topology (`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED` supported;
`STANDALONE` falls back with a one-time warning).
+- **Multi-tenancy / multiple named connections** — supported
(discriminator-based multi-tenancy, `grails.mongodb.connections`).
+
+### Not implemented — do not document or rely on these
+
+- **`shard "name"` in the mapping DSL is a no-op.** There is no `shard` method
on `Collection`/`MongoCollection`. The call silently falls through to the
generic `Entity.methodMissing` catch-all, which just records it as an ad hoc
property config — it never issues a MongoDB `shardCollection` command.
Verified: zero `shardCollection` calls anywhere in `core/src/main`. If you see
`shard "..."` in a mapping block, treat it as dead syntax, not a real feature.
+- **Capped collections** — not implemented anywhere in this module.
+
+## Testing Rules
+
+The module's own tests use a real MongoDB via Testcontainers; the `embedded`
subproject is a runtime feature for applications, not a test fallback. Docker
or Podman must be running on the host.
+
+- **`GrailsDataTckSpec<GrailsDataMongoTckManager>`** is the dominant pattern
(111+ specs) — the same shared TCK harness the Hibernate modules use
(`grails-datamapping-tck`), parameterized with `GrailsDataMongoTckManager`
(`core/src/test/groovy/.../GrailsDataMongoTckManager.groovy`), which starts a
real `MongoDBContainer` in `setupSpec()`/stops it in `cleanupSpec()`. Its
`destroy()` after each feature deletes the documents of every non-system
database but keeps the collections and their indexes, so a later feature in the
same fork can inherit an index an earlier one created; a spec that creates an
index has to drop it itself.
+ ```groovy
+ class DocumentMappingSpec extends
GrailsDataTckSpec<GrailsDataMongoTckManager> {
+ void setupSpec() {
+ manager.registerDomainClasses(CustomMapping)
+ }
+ void "test custom document mapping"() { ... }
+ }
+ ```
+ Domain classes are top-level classes in the same spec file, registered via
`manager.registerDomainClasses(...)` in `setupSpec()` — same convention as
`HibernateGormDatastoreSpec`.
+- **`AutoStartedMongoSpec`** (`grails-testing-support-mongodb`) — a
lighter-weight base used by a handful of specs; a Spock global extension
auto-injects `@Shared MongoDBContainer dbContainer` and auto-constructs a
`@Shared MongoDatastore` field if present.
+- **`grails.test.mongodb.MongoSpec`** — a separate, *app-facing* base class
shipped via `testFixtures` in `grails-plugin/`, for Grails-application-level
Mongo integration tests. Don't confuse it with the two internal patterns above
— a spec being both a `MongoSpec` and an `AutoStartedMongoSpec` is explicitly
forbidden.
+- The MongoDB image version comes from `-PmongodbContainerVersion`, which CI
always passes; see `gradle/mongodb-forked-test-config.gradle`.
+- `core` applies `gradle/mongodb-forked-test-config.gradle` (`maxParallelForks
= rootProject.ext.configuredTestParallel`, the forked, parallel layout); every
other subproject (`bson`, `ext`, `embedded`, `spring-data`, `boot-plugin`,
`grails-plugin`, `gson-templates`) applies `gradle/mongodb-test-config.gradle`,
which runs serially (`maxParallelForks = 1`). Check which file a subproject
applies before changing its parallelism.
+- `-PskipMongodbTests` / `-PonlyMongodbTests` gate these tests at the root,
matching the `Container missing` row in root `AGENTS.md`'s Common Issues table
— use `-PskipMongodbTests` when Docker isn't available.
+
+## Change Workflow
+
+1. Identify which layer owns the behavior: mapping
(`MongoMappingContext`/`MongoCollection`/`MongoAttribute`) vs. codec/runtime
(`BsonPersistentEntityCodec`/`PersistentEntityCodec`) vs. datastore-level
(`MongoDatastore`, index/transaction handling).
+2. Check whether the change affects the general `bson/` codec machinery (used
by any BSON-backed consumer) or is genuinely Mongo-specific (`core/`) — don't
put Mongo-only logic in `bson/`.
+3. Update or add specs via `GrailsDataTckSpec<GrailsDataMongoTckManager>`,
registering any new domain classes as top-level classes in the same spec file.
+4. Run the affected subproject's tests with Docker/Podman running; expect
`core` tests to run serially.
+
+## Pitfalls to Avoid
+
+- Do not treat `shard "name"` as a functioning DSL keyword — it's a documented
no-op (see above). Don't build new features assuming it works, and flag it if
you're asked to "fix" sharding — the DSL surface exists but the implementation
doesn't.
+- Do not change a subproject's test parallelism without reading the test
config file it applies; the serial ones share one container.
+- TTL index changes go through `collMod`, not drop/rebuild — a single-field
`expireAfterSeconds` change should reconcile in place. MongoDB silently ignores
the TTL option on a compound index; don't declare one there.
+- The global `stringIds.defaultStoredAs` setting must be read by
`MongoMappingContext` **before** `initialize(classes)` runs (`createIdentity`
is invoked during entity registration and depends on it) — if you touch
mapping-context initialization order, this dependency is easy to break silently.
+- Known unaddressed gaps (real `// TODO`s in the encode/decode path, not
exhaustive but worth checking before assuming behavior): unprocessed
`OneToMany` associations in `encodeUpdate` (`PersistentEntityCodec.groovy`),
`Map` handling in the embedded-collection update path, and embedded-collection
support in the base `BsonPersistentEntityCodec.encodeUpdate` (only the Mongo
subclass handles it).
+
+## Known Status and Constraints
+
+- Multi-document transactions require positive cluster-topology detection
(`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED`); a `STANDALONE` cluster falls back to
legacy client-side flush behavior with a one-time warning, not a hard failure.
+- `MongoDbGormAutoConfiguration` only closes a `MongoClient` it created
itself, not one supplied as an external bean — preserve that ownership tracking
if you touch auto-configuration shutdown logic.
Review Comment:
Fixed in e08436b6ad: it points at `MongoDatastore.ownsClient()` and the two
nested configurations, with the conditions and `destroyMethod = ''`.
##########
build-logic/AGENTS.md:
##########
@@ -0,0 +1,56 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Agent Guide for build-logic
+
+> **IMPORTANT**: `build-logic` is the Gradle convention-plugin layer that the
root build, `grails-gradle` and
+> `grails-forge` consume through `includeBuild`. It is build tooling, not
Grails application code, so two of the
+> root rules do not apply here (see below). Root [`AGENTS.md`](../AGENTS.md)
still governs PR, branch and review
+> conventions and repository-wide policy.
+
+## Quick Reference
+
+Run from inside `build-logic/`:
+
+```bash
+./gradlew build # all three projects
+./gradlew :build-logic:test # the convention plugins (Spock and
TestKit specs)
+./gradlew :grails-docs-core:test # the documentation engine
+```
+
+## Rules That Differ From Root
+
+1. **Use plain `@CompileStatic`, not `@GrailsCompileStatic`.** Nothing here is
a Grails artefact; no source under this directory uses `@GrailsCompileStatic`.
+2. **`javax.inject.Inject` is correct here.** Gradle's constructor injection
uses JSR-330's `javax.inject`, which is unrelated to the Jakarta EE migration.
Do not change it to `jakarta.*`.
+3. **Versions are not pinned here.** `plugins/build.gradle` and
`docs-core/build.gradle` apply the root's `dependencies.gradle`; do not
hardcode a version in this build.
+4. **There is no dedicated CI job for this build.** Every other build includes
it and exercises the plugins through its own CI runs, so a missing job is not a
sign of neglect.
Review Comment:
Fixed in e08436b6ad: the rule lists the three jobs (`buildLogic`, the
`:build-logic-root:vulnerability-scan:check` step of the `build` matrix, and
`buildHealth` in `build-health.yml`).
##########
build-logic/AGENTS.md:
##########
@@ -0,0 +1,56 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Agent Guide for build-logic
+
+> **IMPORTANT**: `build-logic` is the Gradle convention-plugin layer that the
root build, `grails-gradle` and
+> `grails-forge` consume through `includeBuild`. It is build tooling, not
Grails application code, so two of the
+> root rules do not apply here (see below). Root [`AGENTS.md`](../AGENTS.md)
still governs PR, branch and review
+> conventions and repository-wide policy.
+
+## Quick Reference
+
+Run from inside `build-logic/`:
+
+```bash
+./gradlew build # all three projects
+./gradlew :build-logic:test # the convention plugins (Spock and
TestKit specs)
+./gradlew :grails-docs-core:test # the documentation engine
+```
+
+## Rules That Differ From Root
+
+1. **Use plain `@CompileStatic`, not `@GrailsCompileStatic`.** Nothing here is
a Grails artefact; no source under this directory uses `@GrailsCompileStatic`.
+2. **`javax.inject.Inject` is correct here.** Gradle's constructor injection
uses JSR-330's `javax.inject`, which is unrelated to the Jakarta EE migration.
Do not change it to `jakarta.*`.
+3. **Versions are not pinned here.** `plugins/build.gradle` and
`docs-core/build.gradle` apply the root's `dependencies.gradle`; do not
hardcode a version in this build.
Review Comment:
Fixed in e08436b6ad: `vulnerability-scan` is listed, and the rule says
versions come from `dependencies.gradle` with the literal pins named as the
exceptions (`docs-core`, the three settings plugins) and left alone.
##########
build-logic/AGENTS.md:
##########
@@ -0,0 +1,56 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Agent Guide for build-logic
+
+> **IMPORTANT**: `build-logic` is the Gradle convention-plugin layer that the
root build, `grails-gradle` and
+> `grails-forge` consume through `includeBuild`. It is build tooling, not
Grails application code, so two of the
+> root rules do not apply here (see below). Root [`AGENTS.md`](../AGENTS.md)
still governs PR, branch and review
+> conventions and repository-wide policy.
+
+## Quick Reference
+
+Run from inside `build-logic/`:
+
+```bash
+./gradlew build # all three projects
+./gradlew :build-logic:test # the convention plugins (Spock and
TestKit specs)
+./gradlew :grails-docs-core:test # the documentation engine
+```
+
+## Rules That Differ From Root
+
+1. **Use plain `@CompileStatic`, not `@GrailsCompileStatic`.** Nothing here is
a Grails artefact; no source under this directory uses `@GrailsCompileStatic`.
+2. **`javax.inject.Inject` is correct here.** Gradle's constructor injection
uses JSR-330's `javax.inject`, which is unrelated to the Jakarta EE migration.
Do not change it to `jakarta.*`.
+3. **Versions are not pinned here.** `plugins/build.gradle` and
`docs-core/build.gradle` apply the root's `dependencies.gradle`; do not
hardcode a version in this build.
+4. **There is no dedicated CI job for this build.** Every other build includes
it and exercises the plugins through its own CI runs, so a missing job is not a
sign of neglect.
+
+## Project Structure
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `plugins/` | `:build-logic` | The convention plugins (19 plugin ids
registered in `plugins/build.gradle`), the repository-conventions and
violation-aggregation tasks |
+| `docs-core/` | `:grails-docs-core` | The user-guide generation engine and
the BOM extraction used by the documentation builds |
+| `vulnerability-scan/` | `:vulnerability-scan` | The dependency vulnerability
scan run by the Vulnerability Scan workflow |
+
+The `gradle-developer` skill (`../.agents/skills/gradle-developer/SKILL.md`)
covers the convention plugins, the BOM
+platforms and the Gradle 9 task-configuration rules; read it before changing a
plugin.
+
+## Keep Other Documents In Step
+
+Root `AGENTS.md` and the `hibernate-developer` and `violation-fixer` skills
describe behaviour implemented by plugins in
Review Comment:
Fixed in e08436b6ad: `gradle-developer` replaces `hibernate-developer`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]