dotCMS / dotCMS/core

Skill: audit an OSGi plugin for ES → OpenSearch cutover breakage

Open
#37,466 0 comments 0 reactions 1 assignee View on GitHub

@fabrizzio-dotCMS is already working on this.

Since Sep 8, 2026.

OpenSearch Team : Scout Type : Task
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Description

The ES → OpenSearch migration deprecated 20 symbols with @Deprecated(forRemoval = true) plus one plain @Deprecated, and the removal is hard: ESContentTool's javadoc names v26.08.04 (R7 / dotEvergreen cutover), and the Indicies* stack goes "once we complete migration to OpenSearch 3.x". Any OSGi plugin still compiled against those symbols fails to compile at that point, and a subset fails silently before that — the Velocity raw-response case returns a different string with no error.

Today the only way for a plugin author (or for us, auditing a customer's plugin during an upgrade) to find out is to read docs/backend/SEARCH_API_MIGRATION.md and grep by hand. That does not scale across the plugin ecosystem, and it misses the cases that are not a simple symbol match.

Ask: a skill that takes an OSGi plugin — source directory, or a built .jar with no sources — and reports every place it will break at the OpenSearch cutover, with severity, the exact replacement signature, and a file:line reference.

The full replacement mapping already exists and is verified against main: docs/backend/ES_OS_DEPRECATIONS.md. The skill should read its rules from that document (or a machine-readable file generated from it) rather than restating them, so there is one place to update when the inventory moves.

What it has to detect

Grouped by how it has to look, because a plain grep only covers the first group.

1. Direct symbol references (grep-able)

Symbol Replacement
ContentletAPI.esSearch(String, boolean, User, boolean) search(...)ContentSearchResults<Contentlet>
ContentletAPI.esSearchRaw(String, boolean, User, boolean) searchRaw(...)ContentSearchResponse
APILocator.getEsSearchAPI() APILocator.getSearchAPI()
APILocator.getIndiciesAPI() APILocator.getVersionedIndicesAPI()
IndiciesAPI / IndiciesInfo / IndiciesFactory VersionedIndicesAPI / VersionedIndices / IndicesFactory
ContentletIndexAPI.timestampFormatter threadSafeTimestampFormatter
SiteSearchAPI.getFacets(String, String) getAggregations(String, String)

2. Hook overrides — a plugin implementing ContentletAPIPreHook / ContentletAPIPostHook and overriding esSearch / esSearchRaw. The replacements (search / searchRaw) have no-op defaults, so the plugin keeps compiling but its interception silently stops firing once core calls the new path. This one is easy to miss and high impact.

3. Vendor imports — any import org.elasticsearch.*. At Phase 3 the ES client is gone. Also com.dotcms.content.elasticsearch.business.ESSearchResults, and the three duplicated stats classes (IndexStats, ClusterStats, NodeStats) where the old and new names coexist — importing the old one compiles today and breaks later.

4. Velocity templates$ESContent.esSearch(...) and $ESContent.esRaw(...), plus the silent break: $ESContent.raw($q).toString() no longer yields ES wire-format JSON, and $results.hits / $results.response / $results.aggregations no longer resolve. Templates ship inside plugins, so a Java-only scan misses these entirely.

5. Internal-API dependenceCacheLocator.getIndiciesCache(), FactoryLocator.getIndiciesFactory(), anything under com.dotcms.content.index.*, the *ES/*OS implementations, the routers. These carry no deprecation annotation, so the compiler never warned and there is no migration path — the plugin bet on something core never promised. Worth reporting as its own severity rather than as a deprecation.

6. Semantic changes that are not a rename — cases where swapping the symbol is not enough and the report has to say so:

  • IndiciesAPI.point(IndiciesInfo)VersionedIndicesAPI.saveIndices(VersionedIndices) throws DotDataException when the object carries no version. The old API accepted anything.
  • IndiciesAPI.loadIndicies()loadIndices(String version) takes a version argument and returns an Optional, ignoring legacy null-version rows.
  • ESSearchResultsContentSearchResults<T>: getTotalResults() replaces getResponse().getHits().getTotalHits().value.
  • ElasticsearchExceptionDotSearchException: still unchecked, so only explicit catch blocks need editing.

7. Jar-only input — for a plugin with no sources available, resolve references from the bytecode (javap -p -c, or the class constant pool) and from the OSGi manifest's Import-Package header, which is where org.elasticsearch.* shows up declaratively.

Proposed severity model

The report is useless if everything is "warning". Four levels, each with a different action:

Severity Meaning Example
BLOCKER Will not compile after removal esSearch, IndiciesAPI
SILENT Compiles, wrong behaviour at runtime, no error raw().toString(), hook overrides that stop firing
ADVISORY Deprecated but still resolves; no break yet ES_* config keys (they fall back)
UNSUPPORTED Depends on an internal API with no migration path CacheLocator.getIndiciesCache(), content.index.*
Acceptance Criteria
  • Skill invoked against a plugin source directory reports every finding with severity, file:line, the deprecated symbol, and the replacement signature
  • Skill invoked against a built .jar with no sources produces the same findings for groups 1, 2, 3 and 5 (bytecode + Import-Package)
  • Velocity templates inside the plugin are scanned, including the raw(...).toString() silent case
  • Hook overrides of esSearch / esSearchRaw are reported as SILENT, not as BLOCKER — they compile, they just stop firing
  • The four semantic-change cases emit their caveat, not just the replacement name
  • A fixture plugin exercising every deprecated symbol yields a finding for each one (no misses)
  • A fixture plugin already migrated to the neutral API yields zero findings (no false positives)
  • Rules are read from docs/backend/ES_OS_DEPRECATIONS.md or a file generated from it — adding an entry to the inventory needs no skill edit
  • Report is emitted in a form usable both by a human and by CI (non-zero exit when any BLOCKER is present)
  • Skill passes just skills-lint
Priority

Medium — but time-boxed: the value is preventive and drops sharply after the v26.08.04 cutover.

Additional Context

Naming — decided: dot-plugin-os-audit. The approved domains in .claude/skills/skills.config.json (dot-issue-, dot-pr-, dot-release-, dot-cicd-, dot-content-, dot-ui-) have no home for plugin tooling, so the implementing PR must add dot-plugin- to approvedDomains in the same PR — CONTRIBUTING §1 states that edit is the review point for "is this a real new domain?". The domain is expected to be reusable for other plugin-ecosystem tooling.

Considered and rejected: dot-content-audit-plugin, which avoids the config change but reads as auditing content rather than plugins.

Related existing skills (both core-facing, neither audits third-party plugins — mark as related, do not fork):

  • es-os-router-pattern — implementing the PhaseRouter<T> pattern inside core
  • extract-neutral-api — refactoring a vendor pair into a neutral contract

Source material:

  • docs/backend/ES_OS_DEPRECATIONS.md — the full verified inventory, 47 entries, with file:line and originating commit
  • docs/backend/SEARCH_API_MIGRATION.md — the plugin-facing migration guide
  • docs/backend/OPENSEARCH_MIGRATION.md — phase architecture, why Phase 3 has no fallback

Why now: the audit is most valuable before the cutover, while plugin authors still have a window. After v26.08.04 the skill stops being a preventive tool and becomes a post-mortem.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.