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>
4.9 KiB
Advanced — CSFLE and Queryable Encryption
Capabilities: MongoCapability.CSFLE, MongoCapability.QUERYABLE_ENCRYPTION
Properties: ca-skeleton.persistence-mongo.advanced.csfle.enabled,
ca-skeleton.persistence-mongo.advanced.queryable-encryption.enabled
Status: Advanced.
Requirements
| Topology | Replica set or sharded cluster. |
| Server | MongoDB 7.0 or 8.0 (see §4 for the 8.0 query-type limits). |
| Privilege | MongoPrincipalRole.ENCRYPTION_ADMIN for the key vault; the application role never holds it. |
| Environment | A real KMS and key vault. A local key provider does not exercise any of the failure modes that matter. |
1. CSFLE
MongoCsfleProfile binds a collection to its MongoCsfleFieldPolicy list, a key vault
MongoCredentialReference and the key vault namespace. MongoCsfleClientFactory builds the encrypted
client; MongoDataKeyResolver resolves data keys.
MongoCsfleMode:
| Mode | Queryable | Trade-off |
|---|---|---|
RANDOMIZED |
no | Same plaintext encrypts differently each time. The safe default. |
DETERMINISTIC |
equality only | Same plaintext always yields the same ciphertext, so equality works — and so does frequency analysis. |
UNINDEXED |
no | Stored encrypted, excluded from any index. |
MongoCsfleFieldPolicy.forPii(field, queryable) defaults to RANDOMIZED when the field is not
queried. Deterministic encryption requires a written equalityQueryJustification; the constructor
refuses a blank one, naming frequency analysis. A low-cardinality deterministic field (a status, a
country, a boolean) leaks its distribution to anyone who can read the collection, which is the party
encryption was protecting against.
2. Queryable Encryption
MongoQueryableEncryptionProfile binds a collection to MongoEncryptedFieldDescriptor entries.
MongoQueryableEncryptionQueryType has exactly two values:
EQUALITYRANGE— must declare its domain (min,max). The constructor refuses a range field without one, because changing the domain later means re-encrypting the field.
MongoQueryableEncryptionCollectionManager owns the collection's lifecycle, because a QE collection
is not just a collection: it carries metadata collections.
3. Metadata ownership
MongoEncryptionMetadataOwnership maps customers to enxcol_.customers.esc and
enxcol_.customers.ecoc, and reports __safeContent__-prefixed indexes as
MongoMetadataOwnership.ENCRYPTION_MANAGED.
These are never application-owned and never droppable by drift reconciliation. A drift tool that
drops enxcol_.customers.ecoc corrupts the collection's queryability. This is the single most
important integration point between encryption and
ADR-MONGO-004.
4. Unsupported combinations
Refused at declaration, not discovered at runtime:
| Combination | Why |
|---|---|
| CSFLE and QE on the same collection | Two incompatible encryption schemes over one namespace. Both profile constructors refuse it. |
| CSFLE on a time series collection | requireNotTimeSeries(true) raises MongoOperationRejectedException. |
QE prefix / suffix / substring |
Not available on the platform's 8.0 baseline. The factory methods throw UnsupportedOperationException rather than returning a profile that fails later. |
| Deterministic CSFLE without a justification | IllegalArgumentException naming frequency analysis. |
| Range QE without a declared domain | IllegalArgumentException naming re-encryption. |
5. Failure recovery
| Symptom | Cause | Action |
|---|---|---|
MongoEncryptionException on read |
Wrong data key, or the key vault is unreachable | Check KMS reachability and the key vault credential. Data is intact; the client cannot decrypt it. |
MongoEncryptionException on write |
KMS permission revoked mid-operation | Restore the grant. Writes fail closed — nothing was written in plaintext. |
| Queries return nothing on a deterministic field | The field was re-keyed | Equality matching is over ciphertext; a new key produces different ciphertext. Re-encrypt the field. |
| QE queries fail after a drift reconciliation | A metadata collection was dropped | Restore from backup. This is why ownership gates drops. |
Key rotation. Rotating the customer master key re-wraps the data keys and does not require re-encrypting documents. Rotating a data key does require re-encrypting every document that used it. These are different operations with different costs, and confusing them is how a rotation becomes an outage.
6. Promotion evidence
Per ADR-MONGO-ADV-001, promotion requires the
real KMS and key vault, plus negative cases that fail closed: wrong key, missing permission, rotation
mid-operation (MongoAtlasCapabilityContractSuite.kmsFailureModes). A local key provider certifies
none of these — it never rejects anything.