Skip to content

feat(server): stop CE startup when database was previously EE#41906

Open
salevine wants to merge 4 commits into
releasefrom
feat/stop_ce_downgrade_from_ee
Open

feat(server): stop CE startup when database was previously EE#41906
salevine wants to merge 4 commits into
releasefrom
feat/stop_ce_downgrade_from_ee

Conversation

@salevine

@salevine salevine commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Running Appsmith Community Edition (CE) against a MongoDB that was previously operated by Appsmith Enterprise Edition (EE) is an unsupported EE→CE downgrade — CE migrations and CE code then run against EE-shaped data and can corrupt it. Today this happens silently.

This PR adds a startup guard that detects the condition before any Mongock migration runs and hard-stops CE startup with a clear, operator-facing log message.

How it works

  • Where: inside MongoConfig.mongockInitializingBeanRunner, immediately before buildInitializingBeanRunner() — guaranteed to run before any migration, and where the existing checkForbiddenIds already throws-to-abort.
  • CE-only, no EE change: the guard call is gated on DeploymentProperties.getEdition() == "CE". An EE build already reports edition "EE" (hardcoded, binary-determined — not read from config), so EE skips the guard automatically and can never self-block against its own database. The edition value is shared via DeploymentPropertiesCE.EDITION_CE.
  • Signal (verified against a live EE→CE database and the EE repo): query the Mongock audit collection mongockChangeLog. EE records its @ChangeUnit migrations in the parent package com.appsmith.server.migrations.db. with an EE token in the class name (e.g. Migration003EE01..., Migration042EE01AddWorkflowPlugin), plus a legacy com.appsmith.server.migrations.DatabaseChangelogEE. CE migrations live in the com.appsmith.server.migrations.db.ce. subpackage, and CE has zero classes directly in the parent db package. So the DB was used by EE iff mongockChangeLog holds a changeLogClass under ...migrations.db. that is not under ...migrations.db.ce., or the legacy DatabaseChangelogEE. This keys on the package layout (the same split Mongock scans), not an EE name token, so a CE class like db.ce.EeThing is not misread as EE. Prefixes are anchored and Pattern.quote-escaped.
  • On detection (fail-closed): log an ERROR banner — stable token APPSMITH-EE-DOWNGRADE-BLOCKED, with changeId/changeLogClass passed as CR/LF-stripped SLF4J parameters — then throw to abort startup.
  • On query/infrastructure error (fail-open): log a warning and proceed, in a catch that does not wrap the abort, so a flaky/locked-down Mongo never bricks a healthy CE instance.

Detection was corrected mid-branch: an earlier revision used a ...migrations.db.ee. prefix, which does not exist in EE and so never fired. Verified against a real EE→CE database (84 EE records matched, 0 CE false positives; old rule matched 0).

Testing

  • EnterpriseDowngradeGuardCEImplTest (embedded Mongo) — 8/8: real EE class present → abort; legacy DatabaseChangelogEE → abort; CE-only (db.ce + legacy DatabaseChangelog0/2) → pass; empty collection → pass; missing collection → pass; FAILED-state EE record → abort; CE-package near-misses (db.ce.EeThing) → pass; mixed CE+EE database → abort.
  • EnterpriseDowngradeGuardCEImplUnitTest (Mockito) — 2/2: fail-open when the query throws.
  • spotless:check and test-compile clean. Verified live: CE startup against an EE database now aborts.

Follow-ups (not in this PR)

  1. CE invariant guard (recommended). Add a CE test/ArchUnit assertion that CE migrations only ever live in ...migrations.db.ce. (never directly in the parent db package), since the guard treats any parent-db class as EE.
  2. Docs: replace the placeholder runbook URL behind the APPSMITH-EE-DOWNGRADE-BLOCKED token (// TODO(docs)).
  3. Optional EE-side test: assert EE DeploymentProperties.getEdition() == "EE" so an accidentally-dropped override is caught in CI rather than at startup.

No EE-repo code change is required — the edition gate keeps the whole feature in CE.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Community Edition startup now performs a pre-check for existing Enterprise Edition migration history to prevent incompatible downgrades.
    • If Enterprise migration evidence is detected, startup is stopped with a clear error message/banner.
  • Bug Fixes

    • Detection includes both current and legacy Enterprise migration markers and handles mixed scenarios.
    • The check is fail-open when audit data can’t be read, avoiding unnecessary startup blocks.
  • Tests

    • Added unit and integration tests covering blocking vs non-blocking outcomes, including empty or missing audit data.

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/27768419055
Commit: 57f2657
Cypress dashboard.
Tags: @tag.All
Spec:


Thu, 18 Jun 2026 15:59:26 UTC

Automation

/ok-to-test tags="@tag.All"

Running Appsmith Community Edition (CE) against a MongoDB that was
previously operated by Appsmith Enterprise Edition (EE) is an
unsupported downgrade: CE migrations and CE code then run against
EE-shaped data and can corrupt it. Until now this happened silently.

Add a guard that runs inside MongoConfig.mongockInitializingBeanRunner,
before buildInitializingBeanRunner() — i.e. before any Mongock migration
executes. It queries the Mongock audit collection (mongockChangeLog) for
a record whose changeLogClass starts with the EE migration package
prefix (com.appsmith.server.migrations.db.ee.). Such a record proves EE
ran against this database. The match is:

- anchored to the package prefix (built with Pattern.quote) so a CE class
  that merely contains the substring cannot trip it;
- state-agnostic — a FAILED/partial EE migration still proves EE ran.

On detection the guard logs a clear ERROR banner (carrying the stable
token APPSMITH-EE-DOWNGRADE-BLOCKED, with changeId/changeLogClass passed
as CR/LF-stripped SLF4J parameters to avoid log forging) and then throws
to abort startup, matching the existing throw-to-abort convention in the
same bean method (checkForbiddenIds).

Failure semantics are split deliberately:
- detection (EE found) -> fail-closed (abort startup);
- query/infrastructure error -> fail-open (log a warning and proceed),
  in a catch that does not wrap the abort, so a flaky/locked-down Mongo
  never bricks a healthy CE instance.

The check is CE-only via the standard 4-part CE/EE seam
(EnterpriseDowngradeGuardCE / ...CEImpl / EnterpriseDowngradeGuard /
EnterpriseDowngradeGuardImpl); the EE overlay replaces
EnterpriseDowngradeGuardImpl with a no-op so EE never blocks itself.

Tests (embedded Mongo + a fast pure-Mockito unit test) cover: EE record
present -> abort; CE-only and legacy changelog -> pass; empty and missing
collection -> pass; FAILED-state EE record -> abort; anchored-prefix
near-misses -> pass; and the fail-open path on query error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Introduces an EnterpriseDowngradeGuard CE/EE interface hierarchy and EnterpriseDowngradeGuardCEImpl that queries mongockChangeLog for EE migration package prefix matches at startup. On detection, it throws EnterpriseDowngradeException to abort CE startup. The guard is wired into MongoConfig.mongockInitializingBeanRunner before Mongock migrations run, with CE edition checks using a centralized EDITION_CE constant.

Changes

Enterprise Downgrade Guard

Layer / File(s) Summary
CE interface contract and CEImpl with EE detection logic
src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCE.java, src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImpl.java
EnterpriseDowngradeGuardCE declares assertNotEnterpriseDatabase(MongoClient, String); EnterpriseDowngradeGuardCEImpl implements it by querying mongockChangeLog via regex for EE package prefixes, failing open on read errors, stripping CR/LF from log values, and throwing EnterpriseDowngradeException (state-agnostic) on any EE migration class match.
EE interface and Spring component wiring
src/main/java/com/appsmith/server/helpers/EnterpriseDowngradeGuard.java, src/main/java/com/appsmith/server/helpers/EnterpriseDowngradeGuardImpl.java
EnterpriseDowngradeGuard extends EnterpriseDowngradeGuardCE without additions; EnterpriseDowngradeGuardImpl is the @Component that subclasses EnterpriseDowngradeGuardCEImpl and implements EnterpriseDowngradeGuard for Spring injection.
MongoConfig startup guard invocation with CE edition constant
src/main/java/com/appsmith/server/configurations/DeploymentPropertiesCE.java, src/main/java/com/appsmith/server/configurations/MongoConfig.java
DeploymentPropertiesCE defines EDITION_CE = "CE" constant and updates getEdition() to return it. mongockInitializingBeanRunner bean method gains EnterpriseDowngradeGuard and DeploymentProperties parameters, imports EnterpriseDowngradeGuard, and calls assertNotEnterpriseDatabase before Mongock migrations execute when edition equals CE.
Integration and unit test coverage
src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java, src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplUnitTest.java
Integration tests seed mongockChangeLog with EE/CE/FAILED-state/near-miss audit records and verify throw/no-throw behavior across eight scenarios including empty and missing collections. Unit tests use Mockito to verify fail-open on MongoClient exception and no-throw on empty query result.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A guardian wakes before migrations run,
It scans the changelog — EE records? None!
But find one lurking from an enterprise past,
CE startup halts — that exception is cast. 🚫
Fail-open on errors, regex anchored tight,
Downgrade protection shining bright. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preventing CE startup when database was previously EE, which is the core purpose of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering motivation, implementation details, testing, and follow-up items. All required sections from the template are appropriately addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stop_ce_downgrade_from_ee

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java`:
- Around line 111-113: The test setup code is silently swallowing all errors
when creating the MONGOCK_CHANGE_LOG collection using onErrorResume(e ->
Mono.empty()), which can hide genuine Mongo test setup failures and allow tests
to pass despite broken setup. Replace the blanket error suppression with a more
targeted approach that only ignores the specific expected error (such as when
the collection already exists) while allowing other errors to propagate so
actual setup issues are caught and cause the test to fail appropriately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 61fd7e46-0b87-4c37-aa93-d12ab7f5d962

📥 Commits

Reviewing files that changed from the base of the PR and between 057cc61 and 375e57a.

📒 Files selected for processing (7)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/EnterpriseDowngradeGuard.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/EnterpriseDowngradeGuardImpl.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCE.java
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplUnitTest.java

The guard shipped with a wrong detection signal and never fired. It
assumed EE migrations are recorded under the package
"com.appsmith.server.migrations.db.ee." and matched changeLogClass with
that prefix. That package does not exist, so a CE build started against
an EE database without stopping (false negative — the dangerous
direction).

Verified against a live EE-then-CE database (mongockChangeLog, 233 docs)
and the appsmith-ee repo: EE records its 69 @ChangeUnit migrations in the
PARENT package "com.appsmith.server.migrations.db." with an EE token in
the class name (e.g. Migration003EE01..., Migration042EE01AddWorkflowPlugin),
plus a legacy "com.appsmith.server.migrations.DatabaseChangelogEE". CE
migrations all live in the "com.appsmith.server.migrations.db.ce."
subpackage, and CE has zero classes directly in the parent db package.

Change detection to: a changeLogClass under "...migrations.db." that is
NOT under "...migrations.db.ce.", OR the legacy DatabaseChangelogEE class.
This relies on the package layout (the same split Mongock scans) rather
than an "EE" name token, so a future CE class such as db.ce.EeThing is not
misread as EE. Built with Filters.or(and(regex parent, not(regex ce)), eq
legacy); prefixes are anchored and Pattern.quote-escaped.

Against the live DB the new rule matches 84 EE records and 0 CE (db.ce.)
records; the old rule matched 0. Tests updated to use real EE class names
and a mixed CE+EE database case; guard suite is 8/8 + 2/2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java (1)

127-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid swallowing all setup errors in this test case.

onErrorResume(e -> Mono.empty()) can hide broken Mongo test setup and still let the test pass, which weakens coverage for the "empty collection" path.

Suggested change
-        Mono.from(mongoClient.getDatabase(databaseName).createCollection(MONGOCK_CHANGE_LOG))
-                .onErrorResume(e -> Mono.empty())
-                .block();
-        clearChangeLog();
+        // Create the collection explicitly, then empty it without masking setup failures.
+        Mono.from(changeLogCollection().insertOne(new Document("_id", "seed-empty-case")))
+                .block();
+        clearChangeLog();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java`
around lines 127 - 129, The test setup code in the MongoDB collection creation
block swallows all errors with onErrorResume(e -> Mono.empty()), which can hide
broken test setup and allow tests to pass when they should fail. Remove or
replace the onErrorResume(e -> Mono.empty()) call in the
Mono.from(mongoClient.getDatabase(databaseName).createCollection(MONGOCK_CHANGE_LOG))
chain to ensure that actual setup errors are not silently ignored, allowing the
test to properly detect when the "empty collection" path is broken.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java`:
- Around line 127-129: The test setup code in the MongoDB collection creation
block swallows all errors with onErrorResume(e -> Mono.empty()), which can hide
broken test setup and allow tests to pass when they should fail. Remove or
replace the onErrorResume(e -> Mono.empty()) call in the
Mono.from(mongoClient.getDatabase(databaseName).createCollection(MONGOCK_CHANGE_LOG))
chain to ensure that actual setup errors are not silently ignored, allowing the
test to properly detect when the "empty collection" path is broken.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 748f7cee-1e8c-448f-bd74-95361030fe9d

📥 Commits

Reviewing files that changed from the base of the PR and between 375e57a and 09125fb.

📒 Files selected for processing (2)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImpl.java
  • app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImplTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/EnterpriseDowngradeGuardCEImpl.java

salevine and others added 2 commits June 16, 2026 17:03
The guard inspects the database's Mongock changelog, not the running
edition, so once this code syncs to EE an EE build would self-abort
against its own (legitimate) EE database.

Gate the guard call in MongoConfig on the running edition: invoke it
only when DeploymentProperties.getEdition() == "CE". An EE build already
overrides getEdition() to return "EE", so EE skips the guard
automatically — no EE-repo override is needed and EE can never
self-block. This is also the semantically correct condition: the guard
exists to stop a CE build from running on EE data.

Edition is binary-determined (DeploymentPropertiesCE.getEdition() returns
a hardcoded "CE"; it is not read from infra.json or any external config),
so a CE deployment cannot be misconfigured into skipping the guard.

Extract the CE edition value to DeploymentPropertiesCE.EDITION_CE and
reference it from both getEdition() and the MongoConfig gate so the two
cannot drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aceExists

The empty-changelog case swallowed every error from createCollection via
onErrorResume(e -> Mono.empty()), masking genuine Mongo setup failures. Limit
the suppression to the expected "collection already exists" error (code 48) so
real setup breakage fails the test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@salevine salevine requested a review from subrata71 June 17, 2026 01:12
@salevine salevine added ok-to-test Required label for CI and removed ok-to-test Required label for CI labels Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Required label for CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants