Thanks for the review Jonathan, I will cancel the release, create JIRA issues for these points and then fix all of them.
Best regards, Serge... On Mon, Oct 5, 2026 at 5:48 PM Jonathan Sinovassin-Naïk via dev <[email protected]> wrote: > > Hi Serge, > > -1 > > Thank you for the work on this release. The release mechanics are correct, > but the migration from 3.0.x fails on real 3.0.1 data, and several > regressions affect existing features. > > I compared each item below with the unomi-root-3.0.1 tag, and all of them > are new in 4.0.0. > > What I checked, and what passed: > > - The sha512 checksums and the GPG signatures of the 3 archives are > correct. The key 16DF 7E56 7738 172F 945F 0402 7C19 D1FB BD1A B1DF is in > KEYS. > - The source zip matches the unomi-root-4.0.0 tag, apart from the files > that Maven generates or that git ignores. The staging source-release zip is > identical to the dist one. > - RAT reports 0 unapproved files. > - The build from the source zip with JDK 17 and Maven 3.9.11 succeeds, with > 1681 unit tests and 0 failures. > > > BLOCKING ISSUES > > 1. Migration from 3.0.x > > I ran the procedure of migrate-3.0-to-4.0.adoc on a Unomi 3.0.1 data set, > on Elasticsearch 9.4.3, with the binary from dist/dev. > > a. unomi:migrate fails at the first reindex with "no mapping found in > bundles for: event.json". Before unomi:start, the only Unomi features > installed are unomi-base and unomi-startup, so > MigrationUtils.extractMappingFromBundles (MigrationUtils.java:305) finds no > mapping file. The mappings of unomi-persistence-elasticsearch-core and > cxs-geonames-services are both missing. In 3.0.1, the boot feature > unomi-kar installed these two bundles together with shell-commands. > Migrate16xToCurrentVersionIT cannot detect this, because Pax Exam installs > unomi-elasticsearch-core and the other features before the migration runs. > > b. An index with no known mapping stops the migration with "no mapping > found in bundles for: generic.json" > (migrate-4.0.0-01-tenantDocumentIds.groovy:82,136). A 3.0.1 cluster always > has a context-clusternode index. A cluster that saved a Salesforce > connector configuration also has context-sfdcconfiguration, which takes the > same path. > > c. The built-in definitions of 3.0.1 hide the built-in definitions of > 4.0.0. The script initialize_tenant_and_audit_fields.painless:22,95 tests > scope == 'system', but no 3.0.1 item has this value. In 3.0.1, rules, > segments, scorings and goals keep the scope "systemscope" under > metadata.scope. The built-in condition types, action types and property > types have no scope. All the 3.x items go to the "default" tenant, and the > copy in that tenant wins over the copy in "system" > (MultiTypeCacheServiceImpl.java:126-139, RulesServiceImpl.java:281-304). > > After the migration, the built-in definitions that changed in 4.0.0 keep > their 3.0.1 form, such as sessionAssigned, pastEventCondition, > profilePropertyCondition and booleanCondition. For example, a new visit > increments nbOfVisits but not totalNbOfVisits. > > d. migrate-4.0.0-05-fixSystemItemIds.groovy:60 uses a term query on the > analysed field itemType. The camelCase types, such as conditionType, > actionType and propertyType, match nothing, and the log still says "Fixed". > > e. migrate-4.0.0-10-tenantInitialization.groovy:109,112 writes a key file > that names the headers X-Unomi-Public-Key and X-Unomi-Key. > AuthenticationFilter.java:69 reads the header X-Unomi-Api-Key. > > f. The tests on -systemitems and -geonames in the painless script are > always false. During the reindex, ctx._index is "<index>-cloned", and the > geonames index is "geonameentry". The migrated geonames entries go to > "default", but GeonamesServiceImpl reads them as "system". > > g. The migration tests do not detect the issues above. > Migrate16xToCurrentVersionIT has 4.0.0 checks, and MigrationIT tests the > resume with synthetic scripts. But the 4.0.0 checks have these limits: > > - The IT restores a 1.6.x snapshot and runs "unomi:migrate 1.6.0 true", > so no test starts from 3.x data. The snapshot has no context-clusternode > index. > - Pax Exam installs unomi-elasticsearch-core before the migration runs, > so the missing mapping bundles of item a do not fail the test. > - checkDefinitionsServiceObjects accepts a "system_" prefix or a test > tenant prefix. It does not check that the 4.0.0 version of a built-in > definition is the one in use, which is item c. > - checkDefaultTenantCreated checks that the tenant index exists, but it > makes no assertion when tenantService.getTenant() returns null and the > search finds no tenant document. > - checkMigratedData runs only on Elasticsearch. > > I reproduced a to e. I found f in the code and did not reproduce it. > > 2. ElasticSearchPersistenceServiceImpl.purge(scope) deletes documents of > other scopes > > At ElasticSearchPersistenceServiceImpl.java:2734, the call TermQuery.of(b > -> b.field("scope").value(scope).field("tenantId").value(...)) keeps only > the last field and value, because the builder overwrites them. The query > has no scope filter. DELETE /cxs/cluster/{scope} therefore deletes > documents of any type of the tenant, in all the indices with the index > prefix. In 3.0.1, the query filtered on the scope. The OpenSearch > implementation filters on the scope and on the tenant. > > 3. Scheduled tasks run only for the "system" tenant > > Inside executeAsSystem, the persistence layer adds a filter on tenantId > "system" (ElasticSearchPersistenceServiceImpl.java:2982-3000). These tasks > therefore see only the "system" tenant: > > - The profile-purge task (ProfileServiceImpl.java:363-376) runs > purgeProfiles, purgeSessionItems and purgeEventItems inside > executeAsSystem. The settings org.apache.unomi.profile.purge.*, > org.apache.unomi.session.purge.existTime and > org.apache.unomi.event.purge.existTime have no effect on the data of > "default" or of any other tenant. > - The daily recalculation of segments and scorings with relative dates > reads the cache of the "system" tenant (SegmentServiceImpl.java:1252-1290 > and 1553). A tenant segment such as "visited in the last 7 days" is never > evaluated again. > - After a restart, initCamel runs with no execution context, so the > persistence layer uses the "system" tenant. The recurrent import and export > configurations of the tenants are not scheduled > (ProfileImportFromSourceRouteBuilder.java:104, > RouterCamelContext.java:317). A configuration comes back only when someone > saves it again. > > RulesServiceImpl and TenantUsageServiceImpl loop over the tenants with > executeAsTenant, but these three tasks do not. > > 4. Single-tenant compatibility mode > > - On a public path, the mode always creates a guest subject > (AuthenticationFilter.java:311). The mode still checks X-Unomi-Peer and the > IP address to accept a protected event, but the header gives no role. A 3.0 > third-party server that sends X-Unomi-Peer loses these features: > > - the profile merge on login, > - the profileId of the request body, > - the itemId of events, > - the cross-profile updateProperties, and the writes to systemProperties, > segments, scores and consents. > > The Javadoc of the method says "Protected events require IP + > X-Unomi-Peer (like V2)". The 3.0.x backport of UNOMI-975 (commit 8824da595) > keeps X-Unomi-Peer as the trusted caller for the profile binding. > - Each public request calls TenantServiceImpl.getOrCreateTenant, which is > synchronized (TenantServiceImpl.java:117). The method reads the tenant from > the persistence layer with no cache (TenantServiceImpl.java:213-214), so > all the context.json requests go through one JVM lock and one Elasticsearch > round trip. Each public request already read the tenant with no cache > before my change for UNOMI-980 (https://github.com/apache/unomi/pull/861). > That change added the lock, and I can propose a fix. > > OTHER REGRESSIONS > > - GET /cxs/query/{type}/{property}, and POST on the same path with no > condition, count the data of all the tenants. The persistence layer adds > the tenant filter only when the request carries a condition > (ElasticSearchPersistenceServiceImpl.java:2500-2513). POST > /cxs/query/{type}/{property}/{metricTypes} never filters on the tenant > (getSingleValuesMetrics, line 2780). OpenSearch behaves the same way. > - Segment and scoring lists ignore the sort. SegmentServiceImpl merges the > sorted results into a HashMap, and the sort is a TODO > (SegmentServiceImpl.java:352, 1637 and 1720). In POST /cxs/segments/query > and POST /cxs/scoring/query, a missing limit or a limit of -1 gives a > negative toIndex to subList (line 1732). When at least one item matches, > the call answers 400 instead of the list. > - hoverEventCondition with only targetId matches every hover event, because > since UNOMI-828 the condition is an OR of targetId and targetPath. My > change for UNOMI-982 (https://github.com/apache/unomi/pull/864) makes a > branch with an absent parameter evaluate to true. The OR is therefore > always true when one of the two parameters is missing. > - context.json, eventcollector and client now set allowCredentials=false ( > https://github.com/apache/unomi/pull/878). The shipped tracker sends > withCredentials=true, so a browser blocks the response on a cross-origin > setup. > - Unomi no longer reads UNOMI_HEALTHCHECK_ENABLED, > UNOMI_HEALTHCHECK_PROVIDERS and UNOMI_HEALTHCHECK_TIMEOUT. Commit cef067f2d > (UNOMI-919) removed their mapping from custom.system.properties. > configuration.adoc:1856-1862, index.adoc:180-181 and the quickstart > (5-min-quickstart.adoc:98) still document them. > - scheduler.nodeId defaults to "test-scheduler-node", and no environment > variable sets it. TaskLockManager compares the lock owner with this id > (TaskLockManager.java:240). Two nodes that keep the default can therefore > both take the lock and run the same task. > > > UNDOCUMENTED BREAKING CHANGES > > migrate-3.0-to-4.0.adoc covers the API keys, the cookie, the mandatory > passwords, the conditions with no type, the router file paths and the > privileged REST roles. The guide does not document these changes: > > - An admin call with Basic auth and no X-Unomi-Tenant-Id header runs in the > "system" tenant. It does not see the data that the migration put in > "default". The guide shows this call at lines 125-130. security.adoc calls > the header optional, but no page says what happens without it. An unknown > tenant id also falls back to "system" with only a warning. But > how-profile-tracking-works.adoc:126-128 says that the tenant must exist, > and that the request fails with 401. > - X-Unomi-Peer and thirdparty.cfg apply only in compatibility mode. > recipes.adoc and builtin-event-types.adoc say this for X-Unomi-Peer, but > configuration.adoc:165-222 still presents the thirdparty settings as > active. No page documents the tenant fields restrictedEventTypes and > authorizedIPs, which now control the protected events. > - These Java API changes are not mentioned: > - SchedulerService.getScheduleExecutorService() and > getSharedScheduleExecutorService() are removed. > - EventService.authenticateThirdPartyServer and isEventAllowed are > removed, and isEventAllowedForTenant replaces them. > - Parameter.getDefaultValue() now returns Object, and setDefaultValue now > takes an Object, which breaks binary compatibility. > - DefinitionsService.extractConditionsByType now returns a List instead > of a Set. > - The empty unomi-plugins-past-event artifact is removed. > PastEventConditionEvaluator and SourceEventPropertyConditionEvaluator moved > from unomi-plugins-baseplugin to the new unomi-plugins-advanced-conditions, > in the package org.apache.unomi.plugins.advancedconditions.conditions. > HoverEventConditionESQueryBuilder is removed. > - 18 shell commands are removed in favour of unomi:crud, among them > profile-list, rule-list and segment-view. shell-commands.adoc:99-189 still > lists them, and tutorial.adoc:226 and 274 still use them. > - Document ids now start with the tenant id: <tenantId>_<itemId>, or > <tenantId>_<itemId>_<itemType> for the system items. This change affects > any script or dashboard that reads _id. > - UNOMI_GRAPHQL_FEATURE_ACTIVATED is removed, and only the graphql > distributions or the cdp-graphql-feature enable GraphQL. In the health > check, org.apache.unomi.healthcheck.cfg becomes > org.apache.unomi.healthcheck-elasticsearch.cfg, and > httpClient.trustAllCertificates becomes esHttpClient.trustAllCertificates. > configuration.adoc still names the old file. POST /cxs/profiles with no > itemId now answers 400. > - condition-validation.adoc:187 says that the REST client receives the > validation message. The client receives {"errorMessage":"badRequest"}. > > MINOR, NOT BLOCKING > > The staging repository holds unomi-4.0.0-buildcache.jar. Unomi 3.0.1 did > not publish this artifact. > > Regards, > Jonathan > > On Sun, Oct 4, 2026 at 4:19 PM Mohamed-Tayeb BENTERKI <[email protected]> > wrote: > > > +1 🚀 > > > > On Sun, 4 Oct 2026 at 14:18, Serge Huber <[email protected]> wrote: > > > > > Hi all, > > > > > > I (finally !) submit Apache Unomi 4.0.0 to your vote. > > > > > > This release has been a long time coming. The work began as the "Unomi > > > Version 3" plan (UNOMI-875, February 2025), with implementation from > > March > > > 2025 — months before Unomi 3.0.0 was cut in November 2025. By September > > > 2025 the tree was already versioned 3.1.0-SNAPSHOT; 3.0.0 (and later > > > 3.0.1 in July 2026) was a first slice, while this line continued in > > > parallel. As the scope grew — multi-tenancy, a second persistence > > backend, > > > a large platform refresh, and a much stronger REST and operations story — > > > we renamed it 4.0.0 so the version number matches the size of the change. > > > This candidate is the result. > > > > > > A short view of what 4.0 brings: > > > > > > Features > > > - Multi-tenancy, with a compatibility mode for single-tenant setups > > > - OpenSearch as a persistence option alongside Elasticsearch > > > - Cluster-aware scheduling, unified tenant caching, and a unified shell > > > CRUD > > > - Tenant usage through the REST API, plus event retention purge > > > - Request tracing, and a rangeQuery API on persistence > > > - REST V2 compatibility mode, better input validation, and clearer error > > > responses > > > - Migration scripts from 3.x, including resume-safe 3.1/4.0 migration > > > > > > Platform and libraries > > > - Newer Karaf, Elasticsearch, OpenSearch, and Log4j > > > - Further third-party updates (UNOMI-990 and related) > > > - Stricter startup (explicit admin and health-check passwords) and > > tighter > > > control of privileged REST, Groovy actions, and import/export > > > - Build cache, BOM coverage for advanced conditions, and a published > > > distribution again > > > > > > Fixes > > > - Condition validation and matching (optional parameters, unresolved > > > types, segment/scoring save) > > > - Context/profile binding, CSV import, persona sessions, health-check > > > concurrency > > > - Scheduler locks and recovery in a cluster > > > - Tracker WAB, visit/session counts, logging, and several CI/IT flakes > > > > > > Documentation and developer experience > > > - Manual refresh with diagrams, Javadoc, and OpenAPI filled from Javadoc > > > - Pluggable persistence ITs, better IT progress/tooling, improved > > build.sh, > > > and contributor guidelines > > > > > > Tests grew a lot compared with 3.0.1. Annotated test methods went from > > > about > > > 280 to about 2,130. Most of that is new unit tests: roughly 90 unit tests > > > in > > > 15 classes became about 1,640 in 120 classes, while integration tests > > rose > > > from about 190 to about 490. The bulk of the unit work uses a new > > in-memory > > > persistence manager (UNOMI-881), so core services can be tested without > > > Elasticsearch or OpenSearch. That harness also has its own suite (about > > 200 > > > tests on the in-memory store), and more than a dozen service tests > > > (profiles, > > > events, rules, segments, scheduler, conditions, and others) now run on > > it. > > > > > > Full JIRA notes (about 79 issues): > > > > > > > > https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12319220&version=12356384 > > > > > > Staging repository: > > > https://repository.apache.org/content/repositories/orgapacheunomi-1054/ > > > > > > Source: > > > > > > > > https://repository.apache.org/content/repositories/orgapacheunomi-1054/org/apache/unomi/unomi-root/4.0.0/unomi-root-4.0.0-source-release.zip > > > > > > Convenience binaries: > > > > > > > > https://repository.apache.org/content/repositories/orgapacheunomi-1054/org/apache/unomi/unomi/4.0.0/ > > > > > > Git tag: > > > unomi-root-4.0.0 > > > > > > Dist: > > > https://dist.apache.org/repos/dist/dev/unomi/4.0.0/ > > > > > > Please vote to approve this release: > > > > > > [ ] +1 Approve the release > > > [ ] -1 Don't approve the release (please provide specific comments) > > > > > > This vote will be open for at least 72 hours, or until the necessary > > > number of binding votes (3 +1) is reached. > > > > > > Please let me know if you have any questions. > > > > > > Thanks, > > > Serge > > > > > > > > -- > [image: Dev] > > > *Jonathan SINOVASSIN NAIK*FULLSTACK DEVELOPPER > [image: linkedin] > <https://app.signitic.com/l/ACdRwwAAABAAAAAABhZkJQ.t7MdoRPQji8> [image: web] > <https://app.signitic.com/l/ACdRwwAAABYAAAAABhZkJQ.8yvongia6Lk> [image: > github] > <https://app.signitic.com/l/ZGhoWnNIeVJXV051NzBOYmZMMTFaUT09-NlpUZ01IaXcvRTNYWnk5bEtRWEZCdz09> > > [image: Bannière Jahia avec badges G2 - Leader - Best Relationship - Best > Results - Best Usability] > <https://app.signitic.com/l/ACdRwwAAABUAAo7LBhZkJQ.r8W_TAY9ofY>
