Code/config slice of plan phase 01 (operator-only steps for cluster
provisioning, secrets, and runtime smoke tests deferred to user).
- wrangler.toml: add `compatibility_flags = ["nodejs_compat_v2"]`
(compatibility_date `2025-10-01` already satisfies ≥ 2025-03-20)
- .env.deploy.example: add `MONGODB_URI` placeholder with mirror-protocol note
- scripts/check-secret-leaks.js: lint that fails build on `console.log(env.<SECRET>)`
for MONGODB_URI / TELEGRAM_BOT_TOKEN / TELEGRAM_WEBHOOK_SECRET / ADMIN_TOKEN
- package.json: install mongodb@^6.7.0 (resolved 6.21.0); wire secret-leak
check into `npm run lint`
- docs/using-mongodb.md: operational runbook (cluster spec, free-tier ceiling,
auto-pause behavior, network access permanence, rollback, rotation)
Bundle-size HARD GATE: PASS. Probe with `import { MongoClient }` measures
226 KiB gzipped (3 MiB Free cap, 92% headroom) — nodejs_compat_v2 provides
node:net/tls/crypto from runtime so transitive deps stay unbundled.
CPU-time gate and auto-pause behavior gate require real Atlas access;
deferred to operator (see docs/using-mongodb.md for procedure).
503/503 vitest tests still pass.
5.1 KiB
Using MongoDB Atlas
Operational runbook for the MongoDB Atlas backend introduced by plans/260425-1945-mongodb-atlas-migration/.
Cluster
| Field | Value |
|---|---|
| Provider | MongoDB Atlas |
| Tier | M0 Free |
| Region | aws-ap-southeast-1 (Singapore) |
| Cluster name | miti99bot-prod (operator confirms) |
| Database | miti99bot |
| DB user | miti99bot-worker (readWrite@miti99bot) |
Connection string format:
mongodb+srv://miti99bot-worker:<pass>@<host>/miti99bot?retryWrites=true&w=majority
Stored in two places (must match):
- CF Worker secret:
wrangler secret put MONGODB_URI .env.deploy(gitignored, used by local backfill / verify scripts)
Same secret-mirror protocol as TELEGRAM_BOT_TOKEN.
Free-tier ceiling
- 512 MB storage (data + indexes)
- 500 max concurrent connections
- ~100 ops/sec sustained (no daily cap)
- No backups, single region, no PITR
- Auto-pauses after 30 days of zero ops
Upgrade path: Flex Tier $8–$30/month (M2/M5 deprecated as of 2026).
Auto-pause
After 30 days idle the cluster pauses. First request after pause:
- Driver throws
MongoServerSelectionErrorafterserverSelectionTimeoutMS(5s). - Worker code (see
src/db/mongo-client.js, lands Phase 02) catches and returns 503 withRetry-After: 30. - Cluster auto-wakes within 30–60s on attempted connection.
The bot has 6+ daily crons; any cron that writes Mongo prevents pause. Phase 08 confirms.
Network access
0.0.0.0/0 — Cloudflare Workers do NOT have static egress IPs on the Free or basic Paid plans. Only auth (SCRAM-SHA-256) + TLS gate connections.
Permanent risk unless upgrading to CF Workers paid static-egress IP add-on (~$10/mo).
Mitigations:
- DB user has
readWriteon one db only (NOTdbAdmin/clusterAdmin). - Password ≥32 chars random.
- Rotate quarterly.
- Atlas free-tier email alerts configured for cluster unavailability + connections > 400.
Bundle gate (Phase 01 result)
Measured npx wrangler deploy --dry-run with a minimal probe importing MongoClient:
| Metric | Value | Cap (Free) | Cap (Paid) |
|---|---|---|---|
| Compressed (gzip) | 226 KiB | 3 MiB | 10 MiB |
| Raw (minified) | 1.74 MiB | — | — |
| On-disk (uncompressed) | 3.9 MiB | — | — |
Pass on both plans with >92% headroom. nodejs_compat_v2 provides node:net/node:tls/node:crypto from the runtime, so the driver's transitive deps are not bundled.
CPU-time gate (Phase 01 — operator-run)
Requires real Atlas + wrangler dev. Procedure:
- Add a temporary
/__mongo-pingroute that connects + runsdb.runCommand({ping:1})+ returns{wall_ms}. - Run 5+ cold cycles (10-min spaced).
- Inspect CF dashboard CPU column for each invocation.
- Hard gate: if any cold-start CPU time approaches 50ms (Free plan limit), abort migration. Escalate to paid plan or pivot via
phase-07-alt-pivot.md. - Record cold-ping P95 wall-clock as
BASELINE_COLD_PING_MShere:
BASELINE_COLD_PING_MS = <fill after measurement>
Phase 06 derives the abort threshold from this value: 2.5 × BASELINE_COLD_PING_MS.
Auto-pause behavior gate (Phase 01 — operator-run)
In Atlas UI, manually pause the cluster, then hit /__mongo-ping. Confirm:
- Driver throws within 5s (does NOT hang indefinitely).
- Error class is
MongoServerSelectionError(or driver subclass). - Phase 02
getDb()catches this and surfaces a 503.
Node API surface
src/ (the Worker) imports zero node:* modules today. nodejs_compat_v2 is enabled solely for the mongodb driver:
| Module | Used by Worker? | Used by scripts/? |
|---|---|---|
node:fs |
no | yes (build/scrape/migrate) |
node:path |
no | yes |
node:child_process |
no | yes (migrate.js) |
node:net |
indirectly (via mongodb) | no |
node:tls |
indirectly (via mongodb) | no |
node:crypto |
indirectly (via mongodb) | no |
process.env |
no | yes (register.js) |
Buffer |
no | no |
Risk: minimal. No existing module relies on the absence of these globals.
Rollback
If migration is abandoned at any phase before cutover:
wrangler secret delete MONGODB_URI- Revert
wrangler.toml: removecompatibility_flags = ["nodejs_compat_v2"]. npm uninstall mongodb.npm run deploy— bot continues on KV/D1 unchanged.- (Optional) Delete Atlas cluster from UI.
scripts/check-secret-leaks.js should stay — it covers other secrets too.
Rotation
MONGODB_URI rotation cadence: every 90 days, owner = repo maintainer.
Procedure:
- In Atlas UI → Database Access → edit
miti99bot-worker→ reset password. - Update
.env.deploywith new URI. wrangler secret put MONGODB_URI(paste new URI).npm run deploy(re-runs register; no Worker restart needed since secret reads at request time viaenv.MONGODB_URI).
Mismatch between .env.deploy and CF secret causes register-script failure on next deploy — same fail-loud pattern as TELEGRAM_WEBHOOK_SECRET.
Alerts
Configured in Atlas free-tier UI:
- Cluster unavailable → email maintainer.
- Current connections > 400 (80% of cap) → email maintainer.
Plus CF Observability rule (Phase 06): >10 errors per 1 min window → email.