11 KiB
MongoDB Document Persistence Platform — Repository Adaptation Contract
Design source: docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md
Stable plan: docs/superpowers/plans/2026-08-11-mongodb-document-persistence-platform-implementation-plan.md
Advanced plan: docs/superpowers/plans/2026-08-11-mongodb-advanced-capabilities-expansion-plan.md
The design package declares its own module root (modules/mongodb) and root package
(io.backend.skeleton.mongodb) as implementation assumptions, not as contract. This file is the
single record of how that assumed layout was mapped onto this repository. Only paths, build DSL,
and composition-root ownership changed. Public contracts, policy order, and error semantics are
implemented exactly as specified.
1. Why the module layout differs
The design assumes 19 Stable Gradle projects under modules/mongodb/ and 12 Advanced projects
under modules/mongodb-advanced/. This repository is a Clean Architecture template whose
fail-closed registry (src/config/architecture/modules.json, enforced by src/settings.gradle
and verifyCleanArchitectureDependencies) declares exactly 19 leaf identities. Creating 31 more
Gradle projects would violate HARD-STOP #5 in AGENTS.md.
Therefore the design's 31 modules become package boundaries inside the registered leaf
:adapter:outbound:persistence-mongo, following the precedent already set by
docs/httpclient/repository-adaptation.md. The design's
module dependency table (§6.3) is enforced by MongoModuleBoundaryTest as a closed edge matrix:
every top-level package is declared with the packages it may import, the matrix is compared against
the tree for exact equality, and every observed edge must appear in it. A forbidden edge fails the
build the same way a missing Gradle dependency would, and so does a new package nobody registered.
This used to be a stronger claim than the test. The rules forbade a handful of reverse dependencies
and said nothing about the rest, so four edges outside the design's DAG existed and passed:
reactive → imperative, reactive → query, transaction → reactive and geo → imperative. They
are declared in the matrix now rather than removed — each is a real coupling the code relies on, and
the point of recording them is that the next one is a decision instead of an accident.
2. Package mapping
Root package: io.backend.skeleton.mongodb → dev.caskeleton.adapter.outbound.mongo.
2.1 Stable modules
| Design module | Repository package |
|---|---|
mongodb-core-api |
…outbound.mongo.api (+ .capability, .consistency, .error, .mapping, .observation, .profile, .schema) |
mongodb-spring-data |
…outbound.mongo.mapping (+ .type), …outbound.mongo.failure |
mongodb-imperative |
…outbound.mongo.imperative (+ .atomic, .bulk, .revision) |
mongodb-reactive |
…outbound.mongo.reactive (+ .cursor) |
mongodb-query |
…outbound.mongo.query (+ .budget, .pagination) |
mongodb-aggregation |
…outbound.mongo.aggregation |
mongodb-transaction |
…outbound.mongo.transaction (+ .retry, .session) |
mongodb-index-schema |
…outbound.mongo.schema (+ .index, .manifest, .model, .ttl, .validation) |
mongodb-change-stream |
…outbound.mongo.changestream (+ .projector, .recovery) |
mongodb-geospatial |
…outbound.mongo.geo |
mongodb-migration-core |
…outbound.mongo.migration |
mongodb-migration-flamingock |
…outbound.mongo.migration.flamingock |
mongodb-observability |
…outbound.mongo.observation |
mongodb-security |
…outbound.mongo.security (+ .admin), …outbound.mongo.nativecap |
mongodb-spring-boot-starter |
…outbound.mongo.autoconfigure |
mongodb-testkit-core |
…outbound.mongo.testkit.mapping, .compat, .performance (testkit source set) |
mongodb-testkit-replicaset |
…outbound.mongo.testkit.rs (testkit source set) |
mongodb-testkit-failover |
…outbound.mongo.testkit.failover (testkit source set) |
mongodb-testkit-migration |
…outbound.mongo.testkit.migration (testkit source set) |
…outbound.mongo.architecture has no design counterpart: it holds the @MongoOperation marker and
the reusable ArchUnit rule set a fork applies to its own document/repository code.
2.2 Advanced modules
| Design module | Repository package |
|---|---|
mongodb-sharding |
…outbound.mongo.advanced.sharding (+ .admin for the D4 shard plane) |
mongodb-timeseries |
…outbound.mongo.advanced.timeseries |
mongodb-csfle |
…outbound.mongo.advanced.encryption.csfle |
mongodb-queryable-encryption |
…outbound.mongo.advanced.encryption.qe |
mongodb-search |
…outbound.mongo.advanced.search |
mongodb-vector-search |
…outbound.mongo.advanced.vector |
mongodb-tenancy-shared |
…outbound.mongo.advanced.tenancy.shared |
mongodb-tenancy-database |
…outbound.mongo.advanced.tenancy.database |
mongodb-change-stream-messaging-bridge |
…outbound.mongo.advanced.bridge |
mongodb-gridfs-compat |
…outbound.mongo.advanced.gridfs |
mongodb-testkit-sharded |
…outbound.mongo.testkit.sharded (testkit source set) |
mongodb-testkit-atlas |
…outbound.mongo.testkit.atlas (testkit source set) |
The design's rule that a Stable module never depends on an Advanced one survives as an ArchUnit rule
(stableNeverDependsOnAdvanced) plus the opt-in flag: every Advanced entry point requires
MongoAdvancedCapabilityFlags to have the matching capability enabled and refuses construction
otherwise. Being on the classpath is not being enabled.
3. Other deliberate substitutions
| Design assumption | Repository reality | Adaptation |
|---|---|---|
Gradle Kotlin DSL under modules/mongodb* |
Groovy DSL, root build.gradle conventions, LockMode.STRICT locking |
Dependencies declared in src/adapter/outbound/persistence-mongo/build.gradle; gradle.lockfile regenerated. |
mongodb-spring-boot-starter is a separate module the app depends on |
modules.json gives adapter-outbound-persistence-mongo runtime_memberships: [] and does not list it among app-bootstrap's allowed dependencies |
The autoconfigure package stays inside the leaf and registers through the leaf's own META-INF/spring/…AutoConfiguration.imports. This differs from the httpclient precedent, where the starter moved to :app-bootstrap; here the registry forbids that edge. |
| Spring Boot 4.1 / Spring Data MongoDB 5.1 baseline | Repository baseline is Spring Boot 4.0.8 / Spring Data MongoDB 5.0.x | The platform targets the Spring Data MongoDB API surface common to both; no 5.1-only type is referenced. The support matrix records the actual pinned versions. |
MongoRetryScope lives in mongodb-transaction |
The mongodb-spring-data failure translator must classify retry scope, and it cannot depend on mongodb-transaction |
MongoRetryScope lives in …api.error (core-api), which both packages already depend on. Same values, same meaning, one legal position in the DAG. |
mongodb-migration-flamingock depends on Flamingock |
Adding an unvetted external dependency is out of scope for this task, and the design itself requires the public contract not to depend on Flamingock types | The adapter is provider-neutral: it consumes a platform-owned FlamingockChangeUnitView. Wiring an actual Flamingock distribution is a one-file change behind that view. |
| Testkit as its own Gradle module | The design forbids production modules depending on the testkit | A dedicated testkit source set whose output is on the test compile/runtime classpaths only. ArchUnit rule productionNeverDependsOnTestkit enforces the direction. |
Per-task git commit |
AGENTS.md: commit policy is human-only |
Implementation is delivered unstaged; commits are the human's action. This is the only plan step intentionally not executed, and it is recorded here. |
docs/mongodb/**, scripts/verify-mongodb-*.sh |
Repository already owns docs/ and scripts/ |
Created at the same repository-relative paths. The two gate scripts were later removed (2026-08-15); see §5. |
4. What is unchanged from the design
- D1 / D2 / D3 / D4 exposure planes and the ordered D3 admission sequence (§5).
- Stable API V1 with
apiStrict=trueon the D1/D2 client generation; D3/D4 on separate generations. MongoExecutionOutcome, including both ambiguous outcomes (WRITE_RESULT_UNKNOWN,TRANSACTION_COMMIT_UNKNOWN), andMongoFailureContext's permitted-field list.- The complete stable exception hierarchy and the label-before-code classification order.
- The BSON representation manifest (UUID
STANDARD,Decimal128, UTC instants, alias type metadata) and the document-size budget. - Update-operator-first writes, and optimistic revision as the precondition for whole-document replacement.
- Transaction body retry and commit retry as separate loops: a new session per body attempt, and commit-only retry on unknown commit. The body is never replayed after a commit ambiguity.
- Registered operation names and manifests for query, aggregation and index; no free-form JSON query and no unbounded pipeline.
- Keyset pagination with an authenticated cursor and a unique tie-breaker requirement.
- Change stream as an at-least-once projector with resume-token checkpointing and explicit history-lost handling.
- TTL as physical cleanup only, never the sole basis for access denial or business scheduling.
- Manifest-owned index/validator state with an apply policy that never drops what it does not own.
- Low-cardinality observation tags, command redaction, and the credential reference indirection.
- The Stable release gate's evidence categories, and the Advanced promotion gate's requirement for actual-topology evidence.
5. Verification
The two release-gate scripts (scripts/verify-mongodb-platform.sh and
scripts/verify-mongodb-advanced.sh) were removed on 2026-08-15. They wrapped the Gradle lanes below
and added two things Gradle does not do on its own: a lane that executed zero tests was reported as a
failure rather than counted as a pass, and a promotion.json recording the commit, server image and
contract-manifest hash. Neither exists until something replaces it, so a green run of the commands
below is weaker evidence than the gate was.
From src/:
./gradlew :adapter:outbound:persistence-mongo:check --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
The container-backed lanes the gate ran behind MONGODB_DOCKER=1 — mongoCompatibilityTest,
mongoMigrationTest, mongoSecurityIntegrationTest, mongoReplicaSetTest, mongoFailoverTest,
mongoPerformanceTest — are now invoked by name or not at all.