feat(mongodb): implement the MongoDB document persistence platform

Implements the mongodb-superpowers-package design: Stable Tasks 1-50 and
Advanced Tasks 1-15.

The design assumes 19 Stable + 12 Advanced Gradle projects under
modules/mongodb*. This repository's fail-closed registry declares exactly 19
leaf identities, so those modules become package boundaries inside the
registered leaf :adapter:outbound:persistence-mongo, with the design's module
dependency table enforced by ten ArchUnit rules. The mapping and every
deviation are recorded in docs/mongodb/repository-adaptation.md.

Contract highlights, all enforced by tests rather than convention:

- Transaction body retry and commit retry are separate loops. A new session per
  body attempt; commit-only retry on an unknown commit. The body is never
  replayed after a commit ambiguity, so a failover cannot become a duplicate.
- MongoExecutionOutcome keeps both ambiguous outcomes distinct from success and
  failure, and MongoFailureContext records only the design-permitted fields.
- Failure classification reads server error labels before numeric codes.
- BSON representations come from a pinned manifest, never a library default,
  and a golden type-signature gate fails on any drift.
- Index and validator changes go through the manifest and the admin plane;
  metadata ownership gates every drop.
- Every Advanced capability refuses construction unless its flag is enabled.

Verified against real servers, not only unit tests. Running the lanes for the
first time exposed four defects that a green `check` had hidden:

- Four release lanes passed while executing zero tests; the gate now counts
  executed tests per lane and fails on zero.
- The "single replica set" fixture was a standalone, because Testcontainers 2.x
  needs withReplicaSet(); its test only asserted a connection string.
- The three-node fixture was three independent clusters, so no election could
  occur, and awaitNewPrimary() compared against the post-stop primary.
- The migration lease checked modifiedCount, so a same-millisecond refresh read
  as a lost lease.

scripts/verify-mongodb-platform.sh now reports:
  9 lanes, 0 skipped, 0 failed, every evidence category produced.

scripts/verify-mongodb-advanced.sh reports NOT PROMOTABLE: actual-topology
evidence (real sharded cluster, real KMS, real target deployment) is
unobtainable here, so it is named rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-14 13:41:00 +09:00
co-authored by Claude Opus 5
parent 3b5aee50e3
commit d57d2f62a0
430 changed files with 29846 additions and 154 deletions
@@ -1,13 +1,195 @@
// Driven adapter: opt-in Spring Data MongoDB infrastructure. This leaf owns only enablement and
// Mongo client/template auto-configuration; consuming projects add real documents, repositories,
// mappings, and ports without shipping a fake business domain in the template.
// MongoDB Document Persistence Platform leaf — see
// docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md (design package)
// and docs/mongodb/repository-adaptation.md (how the design's 19 stable + 12 advanced library
// modules map here).
//
// spring-boot-starter-data-mongodb's version is managed by the Spring Boot BOM (applied to every
// module in src/build.gradle), so no module-scoped platform is needed.
description = 'Outbound adapter: opt-in Spring Data MongoDB infrastructure'
// The design models the platform as 19 Stable and 12 Advanced Gradle modules under
// `modules/mongodb` and `modules/mongodb-advanced`. This repository's fail-closed 19-leaf registry
// (src/config/architecture/modules.json) outranks that layout, so the module boundaries are
// packages under dev.caskeleton.adapter.outbound.mongo and MongoModuleBoundaryTest enforces the
// design's module dependency table.
//
// Driver and Spring Data MongoDB versions come from the Spring Boot BOM applied to every module in
// src/build.gradle (design §4: "개별 Driver 버전 override 금지"), so nothing here pins them.
description = 'Outbound adapter: MongoDB document persistence platform (manifests, atomic writes, ' +
'consistency profiles, guardrails, change streams)'
dependencies {
// D1/D2 imperative execution path and the mapping subsystem.
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
// D1/D2 reactive execution path: reactive template, cursors and change streams (design §20).
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
// The starter package registers auto-configuration and binds typed properties.
implementation 'org.springframework.boot:spring-boot-autoconfigure'
// Driver-native observability conventions (design §27) publish through Micrometer.
implementation 'io.micrometer:micrometer-core'
implementation 'org.slf4j:slf4j-api'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
// The design's module dependency table is enforced as package rules, so ArchUnit is what keeps
// "packages instead of modules" from meaning "no boundary at all".
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
testImplementation 'io.projectreactor:reactor-test'
// Real replica set / failover / migration lanes (design §29). Test-scoped so no production
// package can reach a container fixture.
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testImplementation 'org.testcontainers:testcontainers-mongodb'
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
}
// The testkit is its own source set rather than part of `test` because several lanes consume it and
// because the design forbids a production module from depending on the testkit. Declaring its
// dependencies only on the test configurations gives that guarantee without a new Gradle project.
sourceSets {
testkit {
java.srcDir 'src/testkit/java'
resources.srcDir 'src/testkit/resources'
compileClasspath += sourceSets.main.output
runtimeClasspath += output + compileClasspath
}
mongoPerformanceTest {
java.srcDir 'src/mongoPerformanceTest/java'
compileClasspath += sourceSets.main.output + sourceSets.testkit.output
runtimeClasspath += output + compileClasspath
}
}
configurations {
// The testkit compiles against exactly what a test does: testImplementation already extends
// implementation, so this is the module's own dependencies plus the test libraries.
testkitImplementation.extendsFrom testImplementation
testkitRuntimeOnly.extendsFrom testRuntimeOnly
mongoPerformanceTestImplementation.extendsFrom testImplementation
mongoPerformanceTestRuntimeOnly.extendsFrom testRuntimeOnly
}
// Every test lane compiles and runs against the testkit.
sourceSets.test {
compileClasspath += sourceSets.testkit.output
runtimeClasspath += sourceSets.testkit.output
}
dependencies {
testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
testkitImplementation 'io.projectreactor:reactor-test'
testkitImplementation 'org.testcontainers:testcontainers'
testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testkitImplementation 'org.testcontainers:testcontainers-mongodb'
testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
}
// Pinned server images. The design forbids `latest` for a certification lane (Task 44): a mutable
// tag makes a red run unattributable. `-PmongoPrimaryImage=` / `-PmongoCompatibilityImage=`
// override them for a one-off run.
Closure<Void> applyMongoImageSelection = { task ->
task.systemProperty 'mongodb.primary.image',
(project.findProperty('mongoPrimaryImage') ?: 'mongo:8.0.16').toString()
task.systemProperty 'mongodb.compatibility.image',
(project.findProperty('mongoCompatibilityImage') ?: 'mongo:7.0.28').toString()
task.systemProperty 'mongodb.toxiproxy.image',
(project.findProperty('mongoToxiproxyImage') ?: 'ghcr.io/shopify/toxiproxy:2.12.0').toString()
}
// Docker-backed lanes are excluded from the default unit run: they fail closed without Docker, and
// a `check` that fails on a laptop without Docker teaches people to skip `check`.
tasks.named('test', Test) {
useJUnitPlatform {
excludeTags 'quarantine',
'mongodb-replicaset',
'mongodb-failover',
'mongodb-migration',
'mongodb-compatibility',
'mongodb-security-integration'
}
}
tasks.register('mongoReplicaSetTest', Test) {
group = 'verification'
description = 'Single-node replica set contract lane: mapping, atomic write, transaction, ' +
'change stream (design §29).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-replicaset' }
applyMongoImageSelection(it)
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
tasks.register('mongoFailoverTest', Test) {
group = 'verification'
description = 'Three-node replica set failover lane: primary kill, partition, unknown commit, ' +
'resume (design §29).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-failover' }
applyMongoImageSelection(it)
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
tasks.register('mongoMigrationTest', Test) {
group = 'verification'
description = 'Migration lane: empty / N-1 / oldest-supported snapshots, lock, checkpoint ' +
'restart (design §12).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-migration' }
applyMongoImageSelection(it)
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
tasks.register('mongoCompatibilityTest', Test) {
group = 'verification'
description = 'MongoDB 7.0 compatibility and 8.0 primary certification matrix (design §30).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-compatibility' }
applyMongoImageSelection(it)
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
tasks.register('mongoSecurityIntegrationTest', Test) {
group = 'verification'
description = 'RBAC, TLS, injection and redaction release gate against a real server ' +
'(design §26).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-security-integration' }
applyMongoImageSelection(it)
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
tasks.register('mongoPerformanceTest', Test) {
group = 'verification'
description = 'Certifies contention, aggregation spill, pagination and pool resource bounds ' +
'(design §29).'
testClassesDirs = sourceSets.mongoPerformanceTest.output.classesDirs
classpath = sourceSets.mongoPerformanceTest.runtimeClasspath
useJUnitPlatform()
applyMongoImageSelection(it)
systemProperty 'performance.assertions.enabled',
(project.findProperty('performance.assertions.enabled') ?: 'false').toString()
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}
// `check` gains only the hermetic lanes. The Docker-backed ones stay opt-in for the reason above.
tasks.named('check') {
dependsOn 'mongoStableContractTest'
}
tasks.register('mongoStableContractTest', Test) {
group = 'verification'
description = 'Hermetic stable contract suite: manifests, guardrails, retry scopes, ' +
'redaction (design §30).'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
useJUnitPlatform { includeTags 'mongodb-contract' }
failOnNoDiscoveredTests = true
outputs.upToDateWhen { false }
}