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>
63 lines
3.7 KiB
Markdown
63 lines
3.7 KiB
Markdown
# ADR-MONGO-004 — Index and schema changes belong to the admin plane
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-08-13
|
|
- **Design source:** design §21–§25, decisions D-11, D-13
|
|
|
|
## Context
|
|
|
|
Spring Data can create indexes automatically from annotations. On a laptop this is convenient. On a
|
|
collection with a hundred million documents, an index build is a capacity event: it consumes CPU, IO
|
|
and memory on the primary for minutes to hours, and it starts because a pod restarted.
|
|
|
|
Worse, it starts N times when N pods restart, and there is no approval step, no ordering relative to
|
|
the code that needs the index, and no record afterwards of what was created.
|
|
|
|
Schema validators have the same shape with a sharper edge: tightening a validator on a collection with
|
|
existing data rejects writes to documents that were legal when they were written.
|
|
|
|
TTL has a third shape. It looks like a scheduler and is not one: the TTL monitor runs about once a
|
|
minute and deletes in batches, so an expired document routinely remains readable for minutes or hours.
|
|
|
|
## Decision
|
|
|
|
**Indexes and validators are declared in a manifest and applied by the admin plane (D4).** Automatic
|
|
index creation in production is disabled.
|
|
|
|
1. `MongoManifestRegistry` holds the declared indexes (`MongoIndexManifest`) and validator
|
|
(`MongoSchemaManifest`) per collection. The manifest is the source of truth, reviewed in a pull
|
|
request.
|
|
2. `MongoIndexDiffEngine` compares manifest against observed state and reports missing, extra and
|
|
*changed* indexes. Changed ones are reported rather than re-issued: MongoDB will not silently
|
|
rebuild an index whose definition moved.
|
|
3. `MongoIndexApplyPolicy` sets what an environment may do — `APPLY` (local), `APPLY_WITH_DIFF`
|
|
(staging), `DIFF_WITH_APPROVED_APPLY` (production), `REPORT_ONLY` (audit).
|
|
4. **Ownership gates every drop.** `MongoMetadataOwnership` distinguishes `APPLICATION_MANAGED` from
|
|
`SEARCH_MANAGED`, `ENCRYPTION_MANAGED` and `EXTERNAL`. Only application-managed objects are
|
|
droppable on drift. A diff engine without ownership eventually proposes dropping
|
|
`enxcol_.customers.esc`, and "the drift tool cleaned it up" is a very bad incident summary.
|
|
5. **Retirement is staged.** `MongoIndexRetirementState` moves an index declared → hidden →
|
|
observed-unused → droppable, one deployment per transition. Hiding is instantly reversible;
|
|
dropping is a rebuild.
|
|
6. **Stable validation actions are `error` and `warn` only.** `errorAndLog` is not part of the Stable
|
|
contract on 7.0 or 8.0 and is refused. Tightening goes `warn`+`MODERATE` → confirm zero warnings →
|
|
`error`+`STRICT`, in two deployments.
|
|
7. **TTL is physical cleanup only** (D-13). `MongoExpirationAccessPolicy` states the rule: a
|
|
document's presence is not authorization and its absence is not a deadline. Access control checks
|
|
the expiry field; scheduling uses a scheduler.
|
|
8. **Migrations are checksummed, locked, precondition-checked and resumable.**
|
|
`MongoMigrationRunner` fails hard when an applied id's checksum changed — two environments running
|
|
different code under one id is worse than a failed deploy.
|
|
|
|
## Consequences
|
|
|
|
**Positive.** Index builds are scheduled by people who know the capacity. Rollback is possible at
|
|
every step. Drift is visible without being dangerous. Nothing drops what it does not own.
|
|
|
|
**Negative.** Adding an index is a manifest change plus an apply, not an annotation. Local development
|
|
uses `APPLY` so the friction is confined to environments where it is warranted.
|
|
|
|
**Rejected alternative — "auto-create with a feature flag."** The flag is either on in production,
|
|
which is the problem, or off, in which case the manifest is the real mechanism and the annotation is a
|
|
second, divergent source of truth.
|