Files
clean-architecture-backend-…/docs/mongodb/advanced/encryption.md
T
DongHyeonkaandClaude Opus 5 d57d2f62a0 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>
2026-08-14 13:41:00 +09:00

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:

  • EQUALITY
  • RANGE — 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.