Skip to content

chore: migrate to Spring Boot 4#3804

Draft
bdshadow wants to merge 61 commits into
mainfrom
bdshadow/spring-boot-4
Draft

chore: migrate to Spring Boot 4#3804
bdshadow wants to merge 61 commits into
mainfrom
bdshadow/spring-boot-4

Conversation

@bdshadow

Copy link
Copy Markdown
Member

Summary

Migrates the Tolgee platform backend from Spring Boot 3 to Spring Boot 4 (Spring Framework 7). This is a large, cross-cutting framework upgrade; opening as a draft while the test suite is brought fully green.

Core version bumps:

Area From → To
Spring Boot 3.x → 4.1.0
Spring Framework 6 → 7
Hibernate ORM 6 → 7.4.1
Spring Data 3 → 4
Spring Batch 5 → 6
Spring Security 6 → 7
Jackson 2 (com.fasterxml) → 3 (tools.jackson)
Redisson 3.27 → 4.6.1
Spring Cloud Azure 5.x → 7.3.0
Kotlin 2.3.21
Gradle 9.6.0

Notable migration work

  • Autoconfigure package splits — Boot 4 splits the autoconfigure monolith into per-module packages (data-redis, hibernate, persistence, security, servlet, webmvc-test, restclient, liquibase, …); imports and excludes updated accordingly.
  • Jackson 2 → 3 — moved to tools.jackson coordinates and APIs; annotations remain on com.fasterxml.jackson.annotation.
  • Liquibase — Boot 4 moved Liquibase auto-configuration to a separate spring-boot-liquibase module; added it so the custom SpringLiquibase beans run before the EntityManagerFactory.
  • Redisson 4.x — extend RedissonAutoConfigurationV4, exclude the new unconditional V4 autoconfig, add redisson-spring-cache (split out of core), and declare micrometer-core explicitly (Redisson 4 marks it optional).
  • Hibernate 7 HQL — added explicit select clauses to join queries that no longer infer a result type.
  • JSONB attributes — restored JSON-based deep-copy for non-Serializable values (hypersistence-utils 3.15 dropped that fallback).
  • JPA auditing / startup ordering — wire the date-time provider via dateTimeProviderRef and defer DB access out of bean construction, since Boot 4 creates servlet-filter beans before Liquibase migrates.

Upstream bug found

Hibernate 7's hibernate-models crashes with IndexOutOfBoundsException on a JSONB entity attribute whose type is a Map subclass reaching Map<K,V> through a one-type-parameter generic intermediate. Reported upstream: hibernate/hibernate-models#263 (worked around in CompactSharedMap).

Status

  • ✅ Full backend compiles (all modules)
  • ✅ Application boots end-to-end (context loads, Liquibase migrates, web server starts, batch jobs execute)
  • ✅ Core integration tests green (e.g. BatchJobsGeneralWithoutRedisTest)
  • ⏳ Widening test-suite coverage; the Redis-backed concurrent batch tests (BatchJobsGeneralWithRedisTest, BatchJobRecoveryTest, …) are pre-existing flaky/timing-sensitive tests still being verified

Related

The matching billing branch (bdshadow/spring-boot-4) carries the corresponding billing changes.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 233864ee-e243-4282-a2a6-b403f821113b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bdshadow/spring-boot-4

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.

@bdshadow
bdshadow force-pushed the bdshadow/spring-boot-4 branch 7 times, most recently from fa9dd06 to fad86f7 Compare July 22, 2026 09:55
@bdshadow
bdshadow force-pushed the bdshadow/spring-boot-4 branch from 41d08cc to df298af Compare July 22, 2026 12:10
bdshadow added 21 commits July 23, 2026 23:59
…etamodel

Migrate Jackson 2 -> 3 across the backend:
- version catalog + deps use tools.jackson coordinates; drop the duplicate
  jackson-module-kotlin catalog alias
- rewrite imports (databind/core/module/dataformat -> tools.jackson;
  annotations stay on com.fasterxml)
- API changes: JsonDeserializer -> ValueDeserializer, JsonProcessingException
  -> JacksonException, immutable-mapper builders (jacksonMapperBuilder /
  JsonMapper.builder), JsonNode.fields() -> properties(), elements() ->
  values(), TextNode -> StringNode, ObjectNode.set<T>() -> set(),
  writer(pp) -> writer().with(pp), YAMLFactory.builder() / YAMLWriteFeature
- align Jackson to Spring Boot 4's managed version (3.1.4), no ahead-of-BOM override

Unblock Hibernate 7 static-metamodel generation under kapt:
- prepend the antlr runtime hibernate-jpamodelgen needs to the kapt worker
  boot classpath (Gradle bundles antlr 4.7.2 via tomlj, which shadows it)
- bump hypersistence-utils to the Hibernate 7 variant (hibernate-73)
- core package moves: Job -> core.job, Step -> core.step, JobExecution ->
  core.job, JobParameters/JobParameter -> core.job.parameters
- item API moved to the new infrastructure namespace:
  org.springframework.batch.item.* -> org.springframework.batch.infrastructure.item.*
- RepositoryItemReader now takes (repository, sorts) via constructor
  (no-arg gone); setSort -> setSorts
- JobParameters(Map) -> JobParameters(Set); JobParameter carries its own name
- JobLauncher (deprecated) -> JobOperator; run() -> start()
…g Boot 4

Both stem from Spring Framework 7 / Boot 4 adopting JSpecify null-safety:
- HibernatePropertiesCustomizer moved org.springframework.boot.autoconfigure.orm.jpa
  -> org.springframework.boot.hibernate.autoconfigure (autoconfigure split into
  per-module packages); customize() value type Any? -> Any (non-null)
- JpaRepository<X?, Long?> -> JpaRepository<X, Long> (repository T/ID type params
  are now non-null)
Spring 7's JSpecify null-safety makes HttpEntity<T> require T : Any, so the
bodyless-GET pattern HttpEntity<String?>(null, headers) no longer compiles.
Use HttpEntity<String>(null, headers) (the constructor body param is @nullable).
Clears the last data-module compile errors so it builds clean:
- Hibernate 7: ActionQueue.registerProcess(BeforeTransactionCompletionProcess)
  -> registerCallback(TransactionCompletionCallbacks.BeforeCompletionCallback)
- Hibernate 7 / JPA 3.2: org.hibernate.query.NullPrecedence -> jakarta.persistence.criteria.Nulls
- Spring Framework 7: Cache.get<T> now requires T : Any (bound the accessor's T)
- Spring Security 7: PasswordEncoder.encode returns @nullable String (assert non-null)
Boot 4 dependencies ship Kotlin metadata the old 2.1.20 compiler cannot read
(spring-hateoas 3.1.1 -> metadata 2.4.0, opentelemetry-extension-kotlin 1.63.0
-> 2.3.0). Kotlin 2.3.x reads metadata up to 2.4.0, clearing the resulting
unresolved-reference errors and letting extension-kotlin realign with the rest
of OpenTelemetry at 1.63.0.
api:
- Boot 4 Redis autoconfigure move+rename: RedisAutoConfiguration/RedisProperties
  -> org.springframework.boot.data.redis.autoconfigure.DataRedis*
- JSpecify bounds: ProjectLastModifiedManager<T : Any>, PagedModelWithNextCursor<T : Any>

testing:
- AutoConfigureMockMvc moved to the new spring-boot-webmvc-test module
- Spring 7 MockMvc: MockMultipartHttpServletRequestBuilder no longer extends
  MockHttpServletRequestBuilder -> addToken generic over AbstractMockHttpServletRequestBuilder<B>
- Jackson 3: TypeFactory.defaultInstance() -> createDefaultInstance()
- JSpecify: getBean<reified T : Any>, non-null header arg
- RestTemplateBuilder moved org.springframework.boot.web.client ->
  org.springframework.boot.restclient (added spring-boot-restclient dependency;
  starter-web no longer pulls it transitively)
- EntityScan moved org.springframework.boot.autoconfigure.domain ->
  org.springframework.boot.persistence.autoconfigure
- Spring 7 Kotlin RestTemplate extensions require a non-null reified type
  (postForObject/getForObject<T>, not <T?>)
- Spring Data 4: non-null Page.map element (GlossaryTermService)

With this the whole tolgee-platform backend (data, api, testing, app, ee) compiles.
Completes the backend main-source migration (whole platform + billing compiles):
- Boot 4 autoconfigure package moves: SecurityProperties -> security.autoconfigure,
  MultipartConfigFactory -> boot.servlet, actuator Health* -> boot.health.contributor
- Ldap/Thymeleaf autoconfigure are no longer on the classpath -> removed the now-dead
  @SpringBootApplication excludes
- Boot 4 removed SecurityProperties.DEFAULT_FILTER_ORDER (inlined -100)
- Boot 4 HealthContributor is sealed -> implement only HealthIndicator
- Spring 7: WebMvcConfigurer.configureMessageConverters non-null element list;
  Jackson 3 converter base (AbstractJackson2 -> AbstractJacksonHttpMessageConverter);
  FilterRegistrationBean filter set via constructor
- JSpecify: pagedResponse<T : Any>
- AutoConfigureMockMvc moved to org.springframework.boot.webmvc.test.autoconfigure
  across all test files; expose spring-boot-webmvc-test as api from :testing so it
  propagates to consumers' test classpaths
- Jackson 3: ObjectMapper().registerKotlinModule() -> jacksonObjectMapper();
  JsonNode.map(Function) now shadows Iterable.map -> iterate via values()
- Spring 7: MockMvcBuilders.standaloneSetup non-null vararg; HttpHeaders.set takes
  a single String? (dropped single-element listOf wrappers)
- ee-test needs spring-boot-restclient and jakarta.mail on its own classpath
- Spring 7: ResponseEntity(body, headers, status) is ambiguous with a null
  headers arg -> cast to HttpHeaders?; ClientHttpResponse.getRawStatusCode()
  removed -> drop the override (getStatusCode covers it)
spring-cloud-azure 5.23.0 registers a KeyVaultEnvironmentPostProcessor that
references org.springframework.boot.ConfigurableBootstrapContext, which Boot 4
moved to org.springframework.boot.bootstrap.* -> NoClassDefFoundError on startup.
7.x is the Boot-4-compatible line (references the new package).
…x JPA startup ordering

Hibernate 7's model introspection (hibernate-models MapValueSwitch) crashes with
IndexOutOfBoundsException on @type JSONB entity fields whose declared type is a
custom Map subclass reaching Map<String,V> through a 1-type-arg generic
intermediate (CompactSharedMap<V>): it treats CompactSharedMap<V> as the Map and
reads type-argument index 1. Fix by removing Map<String,V> from CompactSharedMap
and having DescribingDataMap/DescribingRelationsMap implement Map<String,X>
directly (inheriting the storage impl), so Hibernate resolves a clean 2-arg Map.

Reported upstream: hibernate/hibernate-models#263

Also defer JPA beans that Boot 4 now forces during early web-server startup:
- @lazy CurrentDateProvider.entityManager (EntityManager not resolvable yet)
- @lazy TestClockHeaderFilter.currentDateProvider (breaks the auditing DateTimeProvider cycle)
Declare the app's primary mapper as JsonMapper so Boot 4's auto-configured
@primary jacksonJsonMapper (@ConditionalOnMissingBean(JsonMapper)) backs off,
leaving a single primary ObjectMapper for injection.
…ced date

Boot 4 instantiates servlet-filter beans during web-server startup, which reaches
CurrentDateProvider before Liquibase has migrated. Wire the DateTimeProvider
declaratively (dropping the constructor dependency on AuditingHandler that formed a
startup cycle) and load the persisted forced date lazily on first read instead of
in the constructor.
Hibernate 7 rejects a query with a non-fetch join and no select clause when no
result type is supplied. Add an explicit 'select <root>' to the affected
repository queries; fetch-join-only queries are unaffected.
MappingJackson2HttpMessageConverter is no longer an injectable bean under Boot 4;
add the octet-stream media type via WebMvcConfigurer.extendMessageConverters on
the JacksonJsonHttpMessageConverter instead.
Boot 4 split Liquibase auto-configuration into its own module. Without it the
EntityManagerFactory depends-on wiring that runs the SpringLiquibase beans before
the EMF is never registered, so migrations never execute.
hypersistence-utils 3.15.x clones JSON attributes only via Java serialization and
throws NonSerializableObjectException for attributes whose element/value types are
not Serializable (e.g. batch jobs' List<BatchTranslationTargetItem>). Register a
JsonSerializer that restores the pre-3.15 JSON round-trip clone, via
hypersistence-utils.properties.
bdshadow added 26 commits July 23, 2026 23:59
Hibernate 7's CamelCaseToUnderscoresNamingStrategy inserts an underscore at
digit->uppercase boundaries, so key1Id/key2Id now derive key1_id/key2_id instead of
key1id/key2id (the actual Liquibase-created columns), making KeysDistance queries
fail with 'column ... does not exist'. Pin the columns with @column and align the
@table index column lists.
Redisson 4.6.1 registers a new unconditional RedissonAutoConfigurationV4 that creates
a StringRedisTemplate and connects to localhost:6379 at startup. The ee test config
only excluded V2, so every ee backend test failed to load its context
(Connection refused). Exclude V4 too, matching the platform and billing test configs.
Jackson 3 (jackson-module-kotlin) no longer populates val properties declared in a
class body (no primary constructor parameter), so such @RequestBody DTOs kept their
defaults -- e.g. GlossaryHighlightsRequest.languageTag stayed blank and failed
@notblank with 400 (breaking the glossary highlights panel). Move the properties into
primary constructors for GlossaryHighlightsRequest, LabelRequest and the QA settings
requests.
Hibernate 7 inserts an underscore at the digit->uppercase boundary, deriving table
s3_content_storage_config from S3ContentStorageConfig instead of the actual
Liquibase-created s3content_storage_config, so any query touching content storage
failed with 'relation ... does not exist'. Pin it with @table.
…ate 7

Hibernate 7 rejects referencing a join-fetch alias in WHERE and on-restricted
fetch joins, so the latest-merge filter can no longer live in the repository
query. Move it onto the Branch.merges collection via @SQLRestriction: it is
emitted in the LEFT JOIN's ON clause, so `left join fetch b.merges` loads only
the latest merge per branch while branches without merges are still returned.
Jackson 3 requires non-null constructor params without defaults; requests omitting
'force' failed with 'Missing required creator property'. Default enabled/force to false.
Jackson 3 flipped WRITE_DATES_AS_TIMESTAMPS to disabled by default, so dates
serialized as ISO strings instead of epoch millis - breaking the API's `number`
timestamp contract and activity-log JSONB values. Re-enable it on the primary
API JsonMapper and, via a supplier, on hypersistence's JSONB mapper.
Jackson 3's kotlin module does not populate class-body-declared `val`s (no
setter) and requires non-defaulted creator properties. Make the S3 storage
config's signingRegion/path writable and default SelfHostedEePlanModel's
isPayAsYouGo so requests/responses deserialize as before.
…ing Boot 4

Two Hibernate 7 changes and one Jackson 3 change broke the translation list:
- The screenshot-count subquery exposed its inner count() selection to the outer
  query ("references a root from a different tree"); use the subquery itself.
- Cursor sort columns now report java.util.Date (not Timestamp) as their type, so
  the cursor value parser rejected date sorts with a 500.
- Jackson 3 no longer interns field names, so the reference-equality check for the
  unique cursor column always failed, dropping the strict boundary and returning
  duplicate keys across pages; compare by value.
Hibernate 7 maps native-query TIMESTAMP columns to java.time.LocalDateTime
instead of java.util.Date, so casting the translation-memory suggestion row's
updatedAt to Date threw a ClassCastException (500). Read it as LocalDateTime and
convert to Date via the system zone, matching how the entity's own Date field
round-trips.
…lback

Batch chunk processing rolls back to a JDBC savepoint on failure, then commits
the FAILED execution record in the same transaction - which requires clearing
Hibernate's rollback-only mark. Hibernate 7 moved that mark from a boolean on
TransactionDriverControlImpl to TransactionStatus.MARKED_ROLLBACK on the logical
connection, and exposes no public API to clear it. Reach the logical connection
via the JdbcCoordinator SPI and reset its status to ACTIVE, replacing the old
internal-class reflection chain; also unwrap the session to the
SharedSessionContractImplementor SPI instead of the internal SessionImpl.
Rebase on main brought in new tests using Boot-3 APIs. Move the AutoConfigureMockMvc
import to the Boot 4 package, and convert OrganizationFloorAccessTest to Jackson 3
(tools.jackson kotlin module; iterate JsonNode via values() since Jackson 3 added a
member map(Function) that shadows Kotlin's Iterable.map).
liquibase-gradle 2.2.2 uses Project.exec() (removed in Gradle 9) and
liquibase-hibernate6 breaks on Hibernate 7. Bump the plugin to 3.1.0 (first
release with Gradle 9 support) and apply it via the buildscript classpath so
liquibase.Scope resolves at configuration time - the plugins{} block isolates
plugin classloaders. Move liquibase-core to 5.0.3 (already managed by Boot 4)
and the diff ext to liquibase-hibernate7 5.0.3 for Hibernate 7 model
introspection. Adjust the diff DSL for Liquibase 5: changelogFile (kebab-cased
to --changelog-file) and the Boot 4 package for SpringImplicitNamingStrategy.
…efault bug)

liquibase-hibernate7 5.0.3 emits a duplicate addDefaultValue(nextval) changeset
for every sequence-generated @id column, so diffChangelog never matches the
committed schema. Exclude id columns from the migration diff until the upstream
bug is fixed. See liquibase/liquibase-hibernate#919
… 7 diff

Hibernate 7's model introspection is stricter than 6's: it reads column defaults
only from @ColumnDefault (not Kotlin field initializers), and takes column
nullability from @column(nullable)/optional rather than the Kotlin type. That
surfaced pre-existing gaps between the entities and schema.xml in the migration
diff. Declare them explicitly so the model matches the committed schema:

- @ColumnDefault on WebhookConfig.enabled/autoDisableNotified/autoDisabled,
  TranslationMemory.defaultPenalty/writeOnlyReviewed, and KeysDistance.distance
- @column(nullable = false) on SsoTenant.domain and Project.is_public
- optional = false on TaskKey.task and TaskKey.key
The Hibernate 7 migration diff caught two real gaps between the entities and the
schema:

- translation_suggestion's author/key/language/project FKs are declared
  @joincolumn(nullable = false) but the table was created with nullable columns;
  add the NOT NULL constraints.
- the api_key "scopes" and mt_service_config "enabled services" @ElementCollection
  tables lacked the (owner, element) unique constraint Hibernate maps for a Set;
  add it (using Hibernate's generated constraint names so the model matches).
Redisson 4.6.1's Kryo5Codec.createKryo takes (ClassLoader, boolean useReferences)
instead of (ClassLoader). main added this codec (#3807) against the Boot 3
Redisson, so its override no longer matches once merged with the Boot 4 bump.
Update the override to pass the useReferences flag through.
The ee-module Migration Check (not covered by the earlier :data fixes) flagged
the ee_subscription table:

- includedKeys/includedSeats/keysLimit/seatsLimit/isPayAsYouGo lacked
  @ColumnDefault, so Hibernate 7 (which reads defaults only from @ColumnDefault,
  not Kotlin field initializers) saw no default while the schema declares one;
  declare them to match the schema (-1 limits, true pay-as-you-go).
- license_key is @notblank in the entity but the column was created nullable;
  add the NOT NULL constraint.
springdoc 3.0.3 (the newest Boot 4 line; no newer exists) doesn't fully work with
Boot 4 + Jackson 3, and its SpringDocHateoasConfiguration silently backs off, so the
generated schema degraded badly: Kotlin property types became `unknown` and lost
required flags (~395 properties), and paged responses lost their HAL `_embedded`
shape (dropping to a raw `content` field the frontend can't consume). Rebuild what
springdoc leaves out, on the swagger ModelResolver's (Jackson 2) mapper:

- register the Jackson 2 Kotlin module so swagger can read Kotlin types/nullability;
- register SpringDocJackson2HalModule so RepresentationModel/PagedModel render as HAL
  (`_embedded`/`_links`);
- register springdoc's CollectionModelContentConverter to type the `_embedded` items.

Also hide the derived StorageConfig.enabled/contentStorageType from the schema (they
are server-side computed, not request inputs). Regenerated apiSchema.generated.ts:
`unknown` properties 395 -> 0 and `_embedded` restored, verified against a running
Boot 4 backend. The app's own runtime serialization uses Jackson 3 and is unaffected.
…oot 4

Spring Boot 4 / Spring Data JPA 4 no longer expose a spy-able EntityManager bean,
so @MockitoSpyBean EntityManager (used to count queries) fails to load the context
("no beans of type EntityManager"). Verify the cache instead through Hibernate's
statement counter (generate_statistics): a cache hit runs no additional statements.
This drops the EntityManager spy entirely and is robust across Spring versions.
Spring Batch 6 (Spring Boot 4) defaults to a non-persistent ResourcelessJobRepository
and drops the spring.batch.jdbc schema auto-init, which breaks the migration jobs
that rely on persisted job instances to avoid re-running. Switch the batch starter to
spring-boot-starter-batch-jdbc, the sanctioned way to keep storing batch metadata in
the database (restores both the JDBC repository and schema initialization).
See https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide#spring-batch

Also exclude the Spring Batch 6 batch_job_instance_seq (renamed from batch_job_seq)
from the migration diff so it isn't reported as drift.
…ections

Importing a screenshot onto a not-yet-persisted key returned 500 under
Hibernate 7. KeyScreenshotReference has a @IdClass derived identity
(@manytoone @id key/screenshot), and Key.keyScreenshotReferences cascades
PERSIST. Because the reference was added to that collection before being
persisted, persisting it made Hibernate persist the transient key while
generating the reference's composite id, which cascaded back onto the
reference mid-generation. Hibernate 7 then injects the deprecated
SHORT_CIRCUIT_INDICATOR into the Long id field and fails with
PropertyAccessException.

Persisting the reference before adding it to the collections generates the
derived id with no circular cascade.

https://docs.hibernate.org/orm/7.0/migration-guide/
In Redis mode the batch job queue is populated only via the pub/sub
round-trip, so the RedisMessageListenerContainer must actually deliver
messages. redisJobPubsubContainer uses a single-threaded taskExecutor to
keep REMOVE/ADD events in order (#3761). Spring Data Redis 4.1.0 (Boot 4)
defaults subscriptionExecutor to the taskExecutor when it is unset, so the
blocking subscription task claimed the only thread and no thread was left
to dispatch messages — items were delivered only at container shutdown and
the queue stayed empty.

Set a dedicated SimpleAsyncTaskExecutor as the subscription executor so the
single-threaded taskExecutor is used solely for ordered dispatch.
The runE2e task hardcoded Cypress's target to localhost:8201, so running
e2e in a git worktree (which publishes the app on a per-worktree
TOLGEE_E2E_PORT) hit ECONNREFUSED. Derive both CYPRESS_HOST and
CYPRESS_API_URL from TOLGEE_E2E_PORT, defaulting to 8201 so CI is
unchanged.
…ngTest

AutomationCachingWithRedisTest is annotated @redistest, which is meta-
annotated with its own @SpringBootTest. That replaces the parent's
@SpringBootTest, dropping the spring.jpa.properties.hibernate.generate_statistics
property the test relied on to count executed statements, so the counter
stayed 0 and the cache assertion failed.

Enable statistics in @beforeeach instead, so it survives the annotation
override and works for both the Redis and non-Redis variants.
@bdshadow
bdshadow force-pushed the bdshadow/spring-boot-4 branch from 26d4a9d to 248cf63 Compare July 23, 2026 22:00
bdshadow added 3 commits July 24, 2026 10:32
hardDeleteProject never removed task rows, so deleting a project that had
tasks aborted on the task.project_id and task.language_id foreign key
constraints, leaving the project and its organization half-deleted. Delete
tasks (and the rows referencing them: notifications, task_assignees,
task_key) before languages, keys, branches and the project.
canUserViewOrPublic short-circuited to true for admins and supporters
without checking the organization exists. When a user's preferred
organization was soft-deleted, preferred-organization resolution therefore
skipped the refresh path for admins and then failed in getPrivateModel
(which filters deleted organizations), returning no preferred organization
and a 403. Require the organization to exist for admins and supporters too.
Commit 8bc9cb7 aligned the ee_subscription defaults to the schema's -1 /
true to satisfy the Hibernate 7 migration check, changing the runtime
defaults. Restore the values that main uses (0 limits, isPayAsYouGo=false)
on both the field initializer and @ColumnDefault, and migrate the column
defaults so the schema matches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant