fix(apicurio): order schema-registry after OTEL starter — same noop hijack (#343) #23

Merged
hibryda merged 2 commits from fix/apicurio-otel-fallback-no-hijack-343 into main 2026-06-01 00:07:36 +02:00
Owner

#343 (sibling) — apicurio-client has the identical OTEL no-op hijack

Found while ripple-checking PR #22 (the outbox-publisher fix). SchemaRegistryAutoConfiguration.schemaRegistryOpenTelemetry() returns OpenTelemetry.noop() under @ConditionalOnMissingBean with no ordering relative to the OpenTelemetry Spring Boot starter — the same defect class fixed in PR #22.

If a service enables im2be.schema-registry AND carries the starter, this fallback could win the auto-config ordering race, register the no-op, and make the starter's real OpenTelemetry bean (itself @ConditionalOnMissingBean) back off → all OTel export silently disabled. The three SchemaRegistryClient beans (apicurio/confluent/dual) take a required OpenTelemetry constructor param, so the fallback must still provide the no-op when the starter is genuinely absent.

Dormant — no live impact, no rebuild

No aim2be service depends on apicurio-client (verified across user/social-login/family/diary/calendar/admin/notification/identity), so there is no running service affected and no image rebuild is required. Fixed pre-emptively (rule 18/52) so the landmine is defused before apicurio-client is adopted for the Avro schema-registry path.

Fix

Class-level @AutoConfigureAfter(name = "io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration") — orders this auto-config after the starter so the real SDK bean is registered before the fallback's @ConditionalOnMissingBean is evaluated → real bean wins; no-op covers starter-absent/excluded. Mirrors PR #22 exactly.

Tests (SchemaRegistryAutoConfigurationTest)

  • enabled_providesNoopOpenTelemetryWhenStarterAbsent — bare-consumer path still supplies the no-op (required param satisfied).
  • fallbackOrderingIsGuardedByAutoConfigureAfterOfOtelStarter — pins the @AutoConfigureAfter contract (containsExactly).

mvn -pl apicurio-client -am install -DskipITs38/38 unit, 0 warnings. Memora #3651.

## #343 (sibling) — `apicurio-client` has the identical OTEL no-op hijack Found while ripple-checking PR #22 (the outbox-publisher fix). `SchemaRegistryAutoConfiguration.schemaRegistryOpenTelemetry()` returns `OpenTelemetry.noop()` under `@ConditionalOnMissingBean` with **no** ordering relative to the OpenTelemetry Spring Boot starter — the same defect class fixed in PR #22. If a service enables `im2be.schema-registry` AND carries the starter, this fallback could win the auto-config ordering race, register the no-op, and make the starter's real `OpenTelemetry` bean (itself `@ConditionalOnMissingBean`) back off → all OTel export silently disabled. The three `SchemaRegistryClient` beans (apicurio/confluent/dual) take a **required** `OpenTelemetry` constructor param, so the fallback must still provide the no-op when the starter is genuinely absent. ### Dormant — no live impact, no rebuild **No aim2be service depends on `apicurio-client`** (verified across user/social-login/family/diary/calendar/admin/notification/identity), so there is no running service affected and no image rebuild is required. Fixed pre-emptively (rule 18/52) so the landmine is defused before apicurio-client is adopted for the Avro schema-registry path. ### Fix Class-level `@AutoConfigureAfter(name = "io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration")` — orders this auto-config after the starter so the real SDK bean is registered before the fallback's `@ConditionalOnMissingBean` is evaluated → real bean wins; no-op covers starter-absent/excluded. Mirrors PR #22 exactly. ### Tests (`SchemaRegistryAutoConfigurationTest`) - `enabled_providesNoopOpenTelemetryWhenStarterAbsent` — bare-consumer path still supplies the no-op (required param satisfied). - `fallbackOrderingIsGuardedByAutoConfigureAfterOfOtelStarter` — pins the `@AutoConfigureAfter` contract (`containsExactly`). `mvn -pl apicurio-client -am install -DskipITs` → **38/38 unit, 0 warnings**. Memora #3651.
fix(apicurio): order schema-registry after OTEL starter — same noop hijack (#343)
All checks were successful
im2be-platform-libs CI / mvn install (pull_request) Successful in 3m5s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
f048970dab
Sibling of the outbox fix (PR #22): SchemaRegistryAutoConfiguration's
schemaRegistryOpenTelemetry() returns OpenTelemetry.noop() under
@ConditionalOnMissingBean with no ordering relative to the OTEL Spring
Boot starter. If a service enables im2be.schema-registry AND carries the
starter, this fallback could win the auto-config ordering race, register
the no-op, and make the starter's real OpenTelemetry bean back off —
silently disabling all OTel export, exactly as in outbox-publisher.

Currently DORMANT: no aim2be service depends on apicurio-client, so there
is no live impact and no rebuild is required. Fixed pre-emptively (rule
18/52) so the landmine is defused before apicurio-client is adopted.

Fix: class-level @AutoConfigureAfter(name=
"io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration")
so the real SDK bean is registered before the fallback's
@ConditionalOnMissingBean is evaluated. The no-op still covers the
starter-absent/excluded paths, satisfying the SchemaRegistryClient beans'
required OpenTelemetry constructor param.

Tests (SchemaRegistryAutoConfigurationTest): enabled_providesNoopOpenTelemetryWhenStarterAbsent
(bare-consumer path) + fallbackOrderingIsGuardedByAutoConfigureAfterOfOtelStarter
(pins the ordering contract, containsExactly).

mvn -pl apicurio-client -am install -DskipITs → 38/38 unit, 0 warnings.

Superseded by round 2.

Show previous round

hib-pr-reviewer review — PR #23 (affinity-intelligence-rework/im2be-platform-libs)

Round 1 — head f048970dab1b, base main, trigger opened

TL;DR: CONDITIONAL_APPROVE — both unique-to-one findings verified and kept; two minor test-coverage gaps in the new annotation-pinning and user-bean-yield cases; no blocking issues.

Summary

Reconciliation: 2 unique-to-one findings, both verified and kept.

This is the first arbitration run for this PR (no prior Memora history). Neither A nor B agreed on a shared finding — they flagged two distinct gaps in the new test suite added to SchemaRegistryAutoConfigurationTest.java.

Finding A (unique to A) verified at lines 115–124: the test asserts after.name() with containsExactly(...) but there is no assertion on after.value(). The @AutoConfigureAfter annotation exposes both name() and value() axes; a developer adding value = {SomeClass.class} alongside the existing name entry would introduce an undetected ordering constraint. Confirmed valid.

Finding B (unique to B) verified by cross-reading OutboxOpenTelemetryFallbackTest.java (lines 74–84): the parallel outbox test carries a third case (fallbackYieldsToAUserProvidedOpenTelemetryBean) that exercises @ConditionalOnMissingBean yielding to a user-supplied OpenTelemetry bean via withBean("userOpenTelemetry", ...). The new SchemaRegistryAutoConfigurationTest omits this case entirely. The SchemaRegistryAutoConfiguration.schemaRegistryOpenTelemetry() bean has @ConditionalOnMissingBean so the test is achievable in-module without the OTel starter. Confirmed valid.

Both are minor. Production fix is correct. Memora run summary persisted as memory ID 468.

Blast Radius

Changes are scoped to one auto-configuration class and its test in the apicurio-client submodule. The production fix adds @AutoConfigureAfter with a name-only reference that Spring silently ignores when the target class is absent, so blast radius is limited to consumers of apicurio-client that also carry the OTel starter — the fix corrects a silent misbehavior (noop hijack) rather than introducing a new one.

BLAST_SCORE: 2/10

CI status (head f048970dab1b)

Overall: pending

2 checks: 2 pending

Check State Link
im2be-platform-libs CI / mvn verify (main only) (pull_request) pending details
im2be-platform-libs CI / mvn install (pull_request) pending details

Findings (2)

[MINOR] Annotation-pinning test checks name() but not value() — partial contract coverage

apicurio-client/src/test/java/com/aim2be/platform/schema/SchemaRegistryAutoConfigurationTest.java:120

@AutoConfigureAfter exposes two axes: name() (String FQCNs, used here) and value() (Class references). The test at lines 115–124 correctly prevents removing or mutating the name entry, and containsExactly prevents extra string-form names. However, a developer who adds value = {SomeClass.class} alongside the existing name entry introduces an extra ordering constraint that passes this test undetected.

Suggested addition immediately after line 123:

assertThat(after.value())
        .as("@AutoConfigureAfter must use name-only refs (no class literals) because " +
            "the OTel starter is not a compile-time dependency")
        .isEmpty();

This fully pins both axes of the annotation and matches the stated rationale in the comment at lines 110–114.

[MINOR] Missing fallbackYieldsToUserProvidedOpenTelemetryBean test — gap vs. OutboxOpenTelemetryFallbackTest

apicurio-client/src/test/java/com/aim2be/platform/schema/SchemaRegistryAutoConfigurationTest.java:106

OutboxOpenTelemetryFallbackTest carries a third case (fallbackYieldsToAUserProvidedOpenTelemetryBean, lines 74–84) that verifies @ConditionalOnMissingBean on schemaRegistryOpenTelemetry yields when the application registers its own OpenTelemetry bean. That test does NOT require the OTel starter on the classpath — it uses withBean("userOpenTelemetry", OpenTelemetry.class, () -> userOtel) on the runner. This PR's new tests omit it, leaving the @ConditionalOnMissingBean yield-to-user-bean contract untested.

Suggested addition after enabled_providesNoopOpenTelemetryWhenStarterAbsent (line 106):

@Test
void enabled_fallbackYieldsToUserProvidedOpenTelemetryBean() {
    OpenTelemetry userOtel = mock(OpenTelemetry.class);
    contextRunner
            .withPropertyValues("im2be.schema-registry.enabled=true")
            .withBean("userOpenTelemetry", OpenTelemetry.class, () -> userOtel)
            .run(ctx -> {
                assertThat(ctx).hasSingleBean(OpenTelemetry.class);
                assertThat(ctx.getBean(OpenTelemetry.class)).isSameAs(userOtel);
            });
}

This brings SchemaRegistryAutoConfigurationTest into parity with OutboxOpenTelemetryFallbackTest and covers the user-bean-wins deployment path that @ConditionalOnMissingBean is supposed to handle.

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 1 • 2 findings (2m) • 2026-05-31T22:00:25.367Z → 2026-05-31T22:01:52.485Z • posted-as: pr-reviewer-bot • model: auto

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 2._ <details> <summary>Show previous round</summary> <!-- hib-pr-reviewer round:1 --> ## hib-pr-reviewer review — PR #23 (affinity-intelligence-rework/im2be-platform-libs) **Round 1** — head `f048970dab1b`, base `main`, trigger `opened` **TL;DR:** CONDITIONAL_APPROVE — both unique-to-one findings verified and kept; two minor test-coverage gaps in the new annotation-pinning and user-bean-yield cases; no blocking issues. ### Summary **Reconciliation: 2 unique-to-one findings, both verified and kept.** This is the first arbitration run for this PR (no prior Memora history). Neither A nor B agreed on a shared finding — they flagged two distinct gaps in the new test suite added to `SchemaRegistryAutoConfigurationTest.java`. **Finding A** (unique to A) verified at lines 115–124: the test asserts `after.name()` with `containsExactly(...)` but there is no assertion on `after.value()`. The `@AutoConfigureAfter` annotation exposes both `name()` and `value()` axes; a developer adding `value = {SomeClass.class}` alongside the existing `name` entry would introduce an undetected ordering constraint. Confirmed valid. **Finding B** (unique to B) verified by cross-reading `OutboxOpenTelemetryFallbackTest.java` (lines 74–84): the parallel outbox test carries a third case (`fallbackYieldsToAUserProvidedOpenTelemetryBean`) that exercises `@ConditionalOnMissingBean` yielding to a user-supplied `OpenTelemetry` bean via `withBean("userOpenTelemetry", ...)`. The new `SchemaRegistryAutoConfigurationTest` omits this case entirely. The `SchemaRegistryAutoConfiguration.schemaRegistryOpenTelemetry()` bean has `@ConditionalOnMissingBean` so the test is achievable in-module without the OTel starter. Confirmed valid. Both are minor. Production fix is correct. Memora run summary persisted as memory ID 468. ### Blast Radius Changes are scoped to one auto-configuration class and its test in the `apicurio-client` submodule. The production fix adds `@AutoConfigureAfter` with a name-only reference that Spring silently ignores when the target class is absent, so blast radius is limited to consumers of `apicurio-client` that also carry the OTel starter — the fix corrects a silent misbehavior (noop hijack) rather than introducing a new one. **BLAST_SCORE: 2/10** ### CI status (head `f048970dab1b`) **Overall: ⏳ pending** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/117/jobs/1) | | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/117/jobs/0) | ### Findings (2) #### **[MINOR]** Annotation-pinning test checks `name()` but not `value()` — partial contract coverage _apicurio-client/src/test/java/com/aim2be/platform/schema/SchemaRegistryAutoConfigurationTest.java:120_ `@AutoConfigureAfter` exposes two axes: `name()` (String FQCNs, used here) and `value()` (Class references). The test at lines 115–124 correctly prevents removing or mutating the `name` entry, and `containsExactly` prevents extra string-form names. However, a developer who adds `value = {SomeClass.class}` alongside the existing `name` entry introduces an extra ordering constraint that passes this test undetected. Suggested addition immediately after line 123: ```java assertThat(after.value()) .as("@AutoConfigureAfter must use name-only refs (no class literals) because " + "the OTel starter is not a compile-time dependency") .isEmpty(); ``` This fully pins both axes of the annotation and matches the stated rationale in the comment at lines 110–114. #### **[MINOR]** Missing `fallbackYieldsToUserProvidedOpenTelemetryBean` test — gap vs. `OutboxOpenTelemetryFallbackTest` _apicurio-client/src/test/java/com/aim2be/platform/schema/SchemaRegistryAutoConfigurationTest.java:106_ `OutboxOpenTelemetryFallbackTest` carries a third case (`fallbackYieldsToAUserProvidedOpenTelemetryBean`, lines 74–84) that verifies `@ConditionalOnMissingBean` on `schemaRegistryOpenTelemetry` yields when the application registers its own `OpenTelemetry` bean. That test does NOT require the OTel starter on the classpath — it uses `withBean("userOpenTelemetry", OpenTelemetry.class, () -> userOtel)` on the runner. This PR's new tests omit it, leaving the `@ConditionalOnMissingBean` yield-to-user-bean contract untested. Suggested addition after `enabled_providesNoopOpenTelemetryWhenStarterAbsent` (line 106): ```java @Test void enabled_fallbackYieldsToUserProvidedOpenTelemetryBean() { OpenTelemetry userOtel = mock(OpenTelemetry.class); contextRunner .withPropertyValues("im2be.schema-registry.enabled=true") .withBean("userOpenTelemetry", OpenTelemetry.class, () -> userOtel) .run(ctx -> { assertThat(ctx).hasSingleBean(OpenTelemetry.class); assertThat(ctx.getBean(OpenTelemetry.class)).isSameAs(userOtel); }); } ``` This brings `SchemaRegistryAutoConfigurationTest` into parity with `OutboxOpenTelemetryFallbackTest` and covers the user-bean-wins deployment path that `@ConditionalOnMissingBean` is supposed to handle. ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 1 • 2 findings (2m) • 2026-05-31T22:00:25.367Z → 2026-05-31T22:01:52.485Z • posted-as: pr-reviewer-bot • model: auto</sub> </details>
test(apicurio): pin @AutoConfigureAfter value() + user-bean-yield case (#343 R1)
All checks were successful
im2be-platform-libs CI / mvn install (pull_request) Successful in 3m38s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
1db54dc302
R1 CONDITIONAL_APPROVE findings (kept=2, both MINOR test-coverage, no blockers):
(1) the ordering-guard test pinned name() but not value() — a stray
    value={SomeClass.class} would add an ordering constraint undetected →
    added assertThat(after.value()).isEmpty() (name-only refs; the OTel
    starter is not a compile-time dep).
(2) missing the user-bean-yield case present in OutboxOpenTelemetryFallbackTest
    → added enabled_fallbackYieldsToUserProvidedOpenTelemetryBean (a
    user-supplied OpenTelemetry bean wins; @ConditionalOnMissingBean yields).

mvn -pl apicurio-client install -DskipITs → 39/39 unit, 0 warnings.

hib-pr-reviewer review — PR #23 (affinity-intelligence-rework/im2be-platform-libs)

Round 2 — head 1db54dc30210, base main, trigger synchronize

TL;DR: NO_NEW_FINDINGS — No new findings this round.

Summary

[quorum-converged] A=0 = B=0. Both prior-round findings are resolved in this synchronize push:

  1. Round-1 finding (line 120, name() only) — fixed: fallbackOrderingIsGuardedByAutoConfigureAfterOfOtelStarter now asserts both after.name() (containsExactly the OTel starter class name) and after.value().isEmpty() (lines 120–127).
  2. Round-1 finding (line 106, missing fallback-yields test) — fixed: enabled_fallbackYieldsToUserProvidedOpenTelemetryBean is added at lines 130–143, matching the OutboxOpenTelemetryFallbackTest parity requirement.

The production change (@AutoConfigureAfter(name = "…OpenTelemetryAutoConfiguration")) is correct and safe: using name rather than value avoids a hard compile-time dependency on the OTEL starter, Spring silently ignores unknown class names in @AutoConfigureAfter, and the @ConditionalOnMissingBean fallback path remains intact for starter-absent deployments. The Javadoc update accurately describes the ordering invariant. No new actionable issues found.

CI status (head 1db54dc30210)

Overall: pending

2 checks: 2 pending

Check State Link
im2be-platform-libs CI / mvn verify (main only) (pull_request) pending details
im2be-platform-libs CI / mvn install (pull_request) pending details

Findings

No new findings this round.

Quorum converged on empty findings (A + B both returned 0).

Verdict

NO_NEW_FINDINGS


hib-pr-reviewer • round 2 • 0 findings • 2026-05-31T22:02:59.283Z → 2026-05-31T22:04:00.318Z • posted-as: pr-reviewer-bot • model: auto • [bookkeeping fallback]

<!-- hib-pr-reviewer round:2 --> ## hib-pr-reviewer review — PR #23 (affinity-intelligence-rework/im2be-platform-libs) **Round 2** — head `1db54dc30210`, base `main`, trigger `synchronize` **TL;DR:** NO_NEW_FINDINGS — No new findings this round. ### Summary [quorum-converged] A=0 = B=0. Both prior-round findings are resolved in this synchronize push: 1. **Round-1 finding (line 120, `name()` only)** — fixed: `fallbackOrderingIsGuardedByAutoConfigureAfterOfOtelStarter` now asserts both `after.name()` (containsExactly the OTel starter class name) and `after.value().isEmpty()` (lines 120–127). 2. **Round-1 finding (line 106, missing fallback-yields test)** — fixed: `enabled_fallbackYieldsToUserProvidedOpenTelemetryBean` is added at lines 130–143, matching the OutboxOpenTelemetryFallbackTest parity requirement. The production change (`@AutoConfigureAfter(name = "…OpenTelemetryAutoConfiguration")`) is correct and safe: using `name` rather than `value` avoids a hard compile-time dependency on the OTEL starter, Spring silently ignores unknown class names in `@AutoConfigureAfter`, and the `@ConditionalOnMissingBean` fallback path remains intact for starter-absent deployments. The Javadoc update accurately describes the ordering invariant. No new actionable issues found. ### CI status (head `1db54dc30210`) **Overall: ⏳ pending** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/118/jobs/1) | | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/118/jobs/0) | ### Findings **No new findings this round.** _Quorum converged on empty findings (A + B both returned 0)._ ### Verdict **NO_NEW_FINDINGS** --- <sub>hib-pr-reviewer • round 2 • 0 findings • 2026-05-31T22:02:59.283Z → 2026-05-31T22:04:00.318Z • posted-as: pr-reviewer-bot • model: auto • [bookkeeping fallback]</sub>
hibryda deleted branch fix/apicurio-otel-fallback-no-hijack-343 2026-06-01 00:07:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
affinity-intelligence-rework/im2be-platform-libs!23
No description provided.