* 🍃 fix: Amazon DocumentDB Compatibility for Pipeline-Form Updates - Rewrite acceptTerms without aggregation-pipeline update + $$NOW (null-guarded first-acceptance claim preserves the original timestamp under concurrent and repeat requests) - Rewrite decrementTagCounts clamp-at-zero decrement as ordered two-op bulkWrite (clamp before guarded $inc) - Rewrite extendFilesTTL TTL hold as projected read + per-doc guarded $set via tenantSafeBulkWrite, preserving only-widens/ceiling/cleared-stays-permanent semantics - Log background index-build failures via Model 'index' listeners (previously swallowed silently, e.g. partialFilterExpression rejection on DocumentDB <5.0) - Add misc/documentdb live-compatibility harness + assessment (AWS-cited) - Fix latent file.spec helper bug: createdAt backdating was silently stripped by mongoose immutability Closes #14488 * 🍃 fix: Harden DocumentDB-Safe Updates Against Cross-Call Races Addresses Codex review on #14495: - decrementTagCounts: normalize-null / $inc / clamp-negative op triple so interleaved decrements of the same tag converge on max(0, ...) exactly as the serialized pipeline did (clamp keys on count < 0, not count < amount) - acceptTerms: guard the repeat-acceptance fallback with a non-null timestamp (exact complement of the claim guard) and retry the claim when a config/reset-terms.js reset races between the two updates, so acceptance never resurrects a reset cycle without a fresh audit timestamp * 👷 ci: Suppress Ignored-File Warnings in Changed-File ESLint Run Changed files under config-ignored paths (packages/data-schemas/misc/**) emit "File ignored" warnings that fail --max-warnings=0.
11 KiB
Amazon DocumentDB Compatibility Assessment (issue #14488)
Adjudicated against official AWS documentation on 2026-07-28. Engine columns throughout: DocumentDB 3.6 / 4.0 / 5.0 / 8.0 instance-based and elastic clusters. AWS's supported-APIs page states that unsupported operators are omitted from its tables, so several verdicts below are implicit-by-omission and flagged as such.
Executive summary
- The login blocker was real: three aggregation-pipeline updates (one using
$$NOW) existed in the codebase. DocumentDB documents no support for pipeline-form updates on any engine version. All three are now rewritten with plain update operators that work on every engine, including elastic. - Partial-index creation fails on DocumentDB < 5.0 and elastic — and the
failure was provably silent (no log, no crash, uniqueness quietly
unenforced). Model creation now attaches an
indexlistener so failed builds log loudly. - Transactions already degrade gracefully via a runtime probe. GridFS is
unreachable dead code.
retryWrites=falseis a deployment-docs item. - Recommendation: DocumentDB 5.0+ instance-based is a supportable target; 4.0 runs but with degraded uniqueness enforcement (now logged); elastic clusters should be documented as unsupported (no unique indexes at all).
Proven incompatibilities — fixed in this PR
1. acceptTerms pipeline update + $$NOW (P0 — blocked login)
packages/data-schemas/src/methods/user.ts:303 used a pipeline-form
findByIdAndUpdate with $ifNull/$$NOW (introduced by PR #10810, matching
the reporter's regression window). When Terms gating is on, every login hits
this and DocumentDB rejects it.
AWS evidence: the supported APIs page
lists only classic update operators (no pipeline form anywhere); the $set/
$unset stage operators are marked unsupported for 3.6/4.0/5.0; $$NOW is
absent from the System variables table entirely ($$CURRENT and $$REMOVE
are explicitly "No"). Implicit-by-omission, but consistent with the reported
Failed to parse update: field must be of BSON type object class of error
(AWS documents no exact error string).
Fix: null-guarded first-acceptance claim (termsAcceptedAt: null matches both
the schema's explicit null default and missing legacy fields — a
$exists: false guard would never fire because of that default), with a
plain-$set fallback for repeat acceptance. First-acceptance timestamp
preservation, concurrency convergence, and the IUser | null contract are
covered by tests, including a raw-inserted legacy document without the field.
2. decrementTagCounts pipeline update (P1 — silent tag-count drift)
packages/data-schemas/src/methods/conversationTag.ts:47 used a
$max/$subtract/$ifNull pipeline inside bulkWrite, wrapped in a
try/catch that only logs — on DocumentDB, conversation deletion succeeded
while tag counts silently drifted.
Fix: two mutually exclusive plain ops per tag in one ordered bulkWrite —
clamp-to-zero (count below the decrement amount, or null/missing) first,
then a guarded $inc. Clamp-at-zero, missing-count tolerance, and
variable-amount semantics all preserved; now covered by a new test block
(previously untested).
3. extendFilesTTL pipeline update (P1 — /files/usage TTL holds fail)
packages/data-schemas/src/methods/file.ts:607 — not in the reporter's
list; found by sweeping the codebase (rg for pipeline-shaped update args
and $$NOW; these three sites were the only hits).
Fix: read the candidate files (one projected query), compute each file's
min(now + renewMs, createdAt + maxLifetimeMs) ceiling client-side, then
issue per-document guarded $sets (expiresAt: { $exists: true, $lt: next })
through tenantSafeBulkWrite. Only-widens, per-file ceiling, and
cleared-TTL-stays-permanent semantics are preserved under concurrency by the
write guard. Cost: one extra read round trip on this path — unavoidable
without a schema change, because the ceiling is per-document.
Proven, made loud — partial indexes (P1 on < 5.0 / elastic)
Four unique partial indexes exist:
packages/data-schemas/src/schema/user.ts:192and:199— OAuth ids (googleId,openidId, …) withpartialFilterExpression: { $exists: true }packages/data-schemas/src/schema/file.ts:173—execute_codefiles ($eq-shaped filter)packages/data-schemas/src/schema/group.ts:56— group source ids ($exists: true)
AWS: partial-index.html
— "The partial index feature is supported in Amazon DocumentDB 5.0
instance-based clusters"; the index-properties table marks Partial as
No/No/Yes/Yes/No across 3.6/4.0/5.0/8.0/elastic. The $exists and $eq
filter shapes used here are inside DocumentDB 5.0's supported operator list
($eq, $exists, $and, $gt/$gte/$lt/$lte), so on 5.0+ these indexes build.
On 3.6/4.0/elastic the builds fail — and empirically (probe: unique index over
pre-seeded duplicates, mongoose 8, autoIndex) the failure is completely
silent: no unhandled rejection, no log, the index simply doesn't exist and
duplicate inserts succeed. Mongoose only surfaces build errors through a
Model.on('index') listener, which nothing attached. createModels now
attaches one that logs every failed build (packages/data-schemas/src/models/index.ts).
Operational consequence on < 5.0 remains: OAuth-account uniqueness is not
DB-enforced — documented, loud, but not fixable in application code.
Proven compatible — no action needed
- Transactions: supported on 4.0+ instance-based ("Amazon DocumentDB …
supports transactions in Amazon DocumentDB 4.0 and later" —
transactions.html);
unsupported on 3.6 and elastic. LibreChat already probes at runtime
(
packages/data-schemas/src/utils/transactions.ts, cached inapi/server/services/PermissionService.js) and falls back to non-transactional writes — the same mode as standalone MongoDB without a replica set. DocumentDB's restrictions (1-minute execution limit, no cursors in transactions, no retryable commit/abort) don't intersect LibreChat's usage. - GridFS:
packages/api/src/cache/keyvMongo.tsonly constructs aGridFSBucketwhenuseGridFSis set — no caller ever sets it and the class isn't exported (the singleton uses a plainlogscollection). Dead code. Moot regardless: AWS lists GridFS as supported on instance-based clusters (elastic: no). - TTL indexes: supported everywhere including elastic. AWS warns deletion is best-effort ("Documents are not guaranteed to be deleted within any specific period") — acceptable, since LibreChat treats TTL as cleanup, not as a security boundary.
$ifNull: supported on all versions (only its pipeline-update context was the problem).
Deployment requirements (documentation, not code)
retryWrites=falseis mandatory inMONGO_URI. AWS: "Amazon DocumentDB does not currently support retryable writes"; the failure mode is{"ok":0,"errmsg":"Unrecognized field: 'txnNumber'","code":9}(functional-differences.html).api/db/connect.jspassesMONGO_URIthrough verbatim, so this belongs in the deployment docs (and the live harness flags a URI missing it).- TLS with the AWS CA bundle; clusters are VPC-only (tunnel/bastion for external access).
Document as unsupported — elastic clusters
Elastic cluster limitations:
no unique indexes (any), no partial indexes, no ACID transactions, no
GridFS, no change streams, $expr unsupported, and the cursor-methods table
even lists sort()/skip()/limit() as "No". The email + tenantId unique
index alone disqualifies elastic clusters. Recommend stating this explicitly
in the docs.
Undetermined — honest gaps
- The reporter's engine version and cluster type — still unknown; it decides whether the partial-index caveat applies to them (5.0+: it doesn't). Worth asking directly on the issue.
- DocumentDB 8.0 pipeline-update acceptance — 8.0 added
$set/$unsetaggregation stages, but AWS never documents pipeline-form updates; the harness probe answers this live. collModis only "Partial" on every version — avoidModel.syncIndexes()against DocumentDB (it may issuecollModbeyond the documentedexpireAfterSeconds).- Read-side aggregations (3 files:
methods/prompt.ts,methods/aclEntry.ts,methods/agentCategory.ts) were not audited stage-by-stage; no exotic stages ($facet,$setWindowFields,$unionWith,$graphLookup) are used anywhere. - No faithful local emulator exists. The
documentdb-localDocker image is the PostgreSQL-based Linux Foundation project — AWS's own OSS blog confirms "a different engine than the one used in Amazon DocumentDB." Live regression testing must run against a real cluster.
Regression strategy
- In-repo (CI today): the behavioral tests added in
user.methods.spec.ts,conversationTag.methods.spec.ts, andfile.spec.tslock the pipeline-free implementations' semantics (first-acceptance preservation, clamp-at-zero, per-file ceiling). - Live harness (this directory):
compat.documentdb.spec.tsexercises the exact operations behind #14488 against a real cluster and prints a capability matrix (pipeline updates,$$NOW, transactions, partial unique indexes, TTL,retryWrites). Gated onDOCUMENTDB_URI; verified green against real MongoDB as a baseline. Suggested cadence: before releases and whenever update-operator code indata-schemaschanges; optionally a scheduled GitHub Action on a runner with VPC access to a dev cluster.
Support matrix and recommendation
| Capability (LibreChat dependency) | 3.6 | 4.0 | 5.0 | 8.0 | Elastic |
|---|---|---|---|---|---|
| Pipeline updates (no longer used) | ✗ | ✗ | ✗ | ? | ✗ |
| Plain update operators (all writes now) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Unique indexes | ✓ | ✓ | ✓ | ✓ | ✗ |
| Partial unique indexes (OAuth ids) | ✗ | ✗ | ✓ | ✓ | ✗ |
| Transactions (runtime-probed) | ✗ | ✓ | ✓ | ✓ | ✗ |
| TTL indexes | ✓ | ✓ | ✓ | ✓ | ✓ |
Recommendation: support DocumentDB 5.0+ instance-based with
retryWrites=false documented as required. 4.0 functions with
partial-unique-index loss (now logged loudly at startup) — "works, with a
documented caveat". Elastic clusters: unsupported, full stop.