Hi all,

I'm cancelling the vote for Apache Unomi 4.0.0 RC1.

Thank you, Jonathan, for the very thorough review. It also confirmed
that the release mechanics are sound: the signatures, checksums,
source archive, RAT check and build from source all passed. I went
through every point in your mail against the code. They hold, with a
few small corrections that are noted in the tickets.

The issues are tracked in Jira, all targeted at 4.0.0:

Migration from 3.0:

- UNOMI-996 Migration from Unomi 3.0 stops before it finishes
- UNOMI-997 After migration from 3.0, some data ends up in the wrong tenant
- UNOMI-998 Migration tests do not cover an upgrade from Unomi 3.0

Regressions:

- UNOMI-999 Deleting the data of one scope deletes all the data of the tenant
- UNOMI-1000 Background tasks only process the internal "system" tenant
- UNOMI-1001 In compatibility mode, 3.0 third-party servers lose features
- UNOMI-1002 In compatibility mode, all public requests wait on each other
- UNOMI-1003 Segment and scoring lists ignore the sort, and fail with no limit
- UNOMI-1004 A hover condition with only a target id matches every hover event
- UNOMI-1005 The web tracker and the server disagree on cookies across sites
- UNOMI-1006 All Unomi nodes share the same scheduler node id by default
- UNOMI-1007 Health check environment variables no longer have any effect
- UNOMI-1008 Admin requests without a tenant header do not see migrated data

Documentation and packaging:

- UNOMI-1009 The 3.0 to 4.0 migration guide misses several breaking changes
- UNOMI-1010 Some manual pages still describe Unomi 3.0 behaviour
- UNOMI-1011 The release publishes an extra build cache artifact

Each ticket lists the automated tests needed to confirm the fix and to
prevent the problem from coming back. Any remaining point from the
review will be followed up before the next candidate. Help is very
welcome. Jonathan, thank you for offering a fix for UNOMI-1002.

The cleanup for RC1:

- the Nexus staging repository will be dropped,
- the RC1 archives will be removed from dist/dev,
- the tag unomi-root-4.0.0 will be renamed to unomi-root-4.0.0-rc1, so
the 4.0.0 tag can be - created again for the next candidate.
- I'll start a new vote for 4.0.0 RC2 once these tickets are resolved.

Regards,
  Serge Huber

On Mon, Oct 5, 2026 at 6:19 PM Serge Huber <[email protected]> wrote:
>
> 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>

Reply via email to