feat(quarkus-morphium): optional Micrometer observability module (Phase 1 MVP) - #332
Conversation
…ty module Adds quarkus-morphium/docs/architecture/observability-module-plan.md, produced by the datona-architect persona and independently verified against the code before adoption (Capability.METRICS confirmed against the reactor's pinned quarkus-core-deployment 3.32.3 jar; buildMorphium()/onStop() lifecycle, MorphiumStorageListener callback signatures, and every DriverStatsKey/ StatisticKeys value in the metric catalog confirmed against morphium-core source, not assumed). Trigger: version42-adapter (a downstream consumer) hand-writes ~65 lines of Micrometer boilerplate (MongoConnectionPoolMetrics.java) to expose the MongoDB connection pool's utilization as gauges, including a real weak-reference/GC bug that had to be found and fixed once already. This plan scopes an optional module that gives that to every quarkus-morphium consumer for free, purely additive, gated on Capability.METRICS so apps without Micrometer see zero change. Decision: extend quarkus-morphium/quarkus-morphium-deployment in place (no new Maven module) -- follows the repo's own existing precedent (MorphiumProcessor.registerMorphiumIdJsonCustomizers, Capability.JACKSON/ JSONB-gated) rather than introducing a second artifact for the same pattern. Status: proposal, not yet implemented. Next: datona-feature-implementer / datona-reviewer loop on this branch.
…se 1 MVP) Adds an optional Micrometer metrics integration, gated on Capability.METRICS so an application without Micrometer on its classpath sees zero change -- no new dependency inherited, no bean registered, no behaviour change. Follows the observability-module-plan.md (docs/architecture/, committed separately as b707870): extends quarkus-morphium/quarkus-morphium-deployment in place, mirroring the module's own existing Capability.JACKSON/JSONB-gated pattern (MorphiumProcessor.registerMorphiumIdJsonCustomizers) rather than introducing a new Maven module for the same shape. Phase 1 scope: connection-pool/driver-stats gauges only (morphium.driver.*, morphium.cache.*, morphium.write_buffer.*, sourced from MorphiumDriver.getDriverStats()/Morphium.getStatistics()). The Counter/Timer rows sourced from MorphiumStorageListener/MorphiumTransactionEvent (morphium.operations.*, morphium.transactions.*) are explicitly deferred to a later phase. - quarkus-morphium/runtime/pom.xml, deployment/pom.xml: optional quarkus-micrometer/quarkus-micrometer-deployment dependency pair. - MorphiumProcessor: new registerObservability @buildstep, gates MorphiumMetricsBinder's registration as a CDI bean on Capabilities.isPresent(Capability.METRICS). - MorphiumMetricsBinder (new, runtime/observability package): registers 10 Micrometer Gauges tagged `database`, each reading live from the underlying Morphium/MorphiumDriver stats maps (not a snapshot) -- avoids the WeakReference-GC'd-to-NaN bug the plan cites from a real downstream precedent (version42-adapter's hand-written MongoConnectionPoolMetrics). close() deregisters every meter it registered, for hot-reload idempotency. - MorphiumProducer: binder lookup/bind wired into buildMorphium() (after the connection already exists -- never from an early @observes StartupEvent that could trigger the lazy connect prematurely) and deregistration wired into onStop(), via Arc.container().instance(...) + InstanceHandle so the lookup never throws when the bean doesn't exist (Micrometer absent). Verified independently by the orchestrating session, not just the implementer's self-report: `mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment -am -DskipTests install` then `... test` (no -am, to avoid the unrelated 25+ minute morphium-core suite) -- BUILD SUCCESS, runtime 68/68 tests passing (incl. new MorphiumMetricsBinderTest, 5/5), deployment 29/29 passing (incl. new MorphiumProcessorObservabilityTest, 2/2). Reviewed by the datona-reviewer persona (Round 1): APPROVAL, no blocking findings. Two follow-ups from that review are addressed in the next commit on this branch (Capability.METRICS deprecation note in the plan doc, and the missing MorphiumObservabilityConfig.enabled runtime kill-switch that Section 4.1 specifies but this round's implementer brief had omitted from scope).
…on note Addresses the two non-blocking follow-ups from the Round 1 review of the observability module (previous commit, c829ee6): 1. Plan doc: adds a Section 9 item documenting that Capability.METRICS is @deprecated in Quarkus 3.32.3 (confirmed against the pinned reactor's quarkus-core-deployment sources jar -- Javadoc points to MetricsCapabilityBuildItem, a structurally different build-item shape, not a drop-in replacement). Adjudicated: acceptable to ship on for this phase, consistent with the module's existing Capability.JACKSON/JSONB gate idiom; migration deferred to its own future ticket. 2. MorphiumObservabilityConfig (new): a nested `quarkus.morphium.observability.*` config interface, mirroring the module's existing MorphiumMigrationConfig precedent rather than the plan's literal standalone-@ConfigMapping description -- same property path and defaults, different Java-level composition. Implements only `enabled` (default true) in this phase; Section 7's other properties (poll-interval, per-host-connections, include-storage-listener-metrics) govern behaviour Phase 1 doesn't implement yet, so they are deliberately not added until the phase that implements what they control. Wired into MorphiumProducer.buildMorphium(): the binder's close() still runs unconditionally whenever the Capability.METRICS-gated bean exists (so a hot-reload never leaves stale gauges regardless of the flag), but bindTo() -- the actual gauge registration -- is now gated on `config.observability().enabled()`. Lets an application that has Micrometer on its classpath for an unrelated reason opt out of Morphium's gauges specifically, at runtime, without a rebuild. This was Section 4.1's own MVP scope (MorphiumObservabilityRuntimeConfig is listed there as a regular Phase 1 class, not deferred) that the Round 1 implementer brief had incorrectly left out of scope -- an orchestrator scoping error, not an implementer deviation from a correct brief. Also applies the reviewer's precedent-wording correction to the buildMorphium() comment: MorphiumRecorder's Arc.container().instance(...) precedent is API-identical but not quite semantically identical (its beans are always-present; MorphiumMetricsBinder is the first genuinely-optional use of that idiom in this module). Verified: `mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment test` (no -am, dependency jars already installed) -- BUILD SUCCESS, exit 0, runtime 68/68 and deployment 29/29 still passing, no regressions, no new test needed for the added if-branch itself (buildMorphium() has no existing unit-test seam for its private connect logic; the existing MorphiumProducerConfigValidationTest pattern only covers extracted static helpers, and extracting one for a single boolean check would be disproportionate scope creep for this fix).
…unter Addresses a real finding from an automated PR review (Codex, fork PR #24): morphium.driver.connections.borrowed/.released, .errors, and .failovers are cumulative, monotonically-increasing values -- MorphiumDriver already tracks them as running totals, and the observability-module-plan.md Section 5 metric catalog classifies them as Counter rows, not Gauge rows -- but MorphiumMetricsBinder registered all seven driver-sourced metrics uniformly through Gauge.builder. A Gauge on a cumulative value publishes gauge metadata to the backend instead of counter metadata, breaking counter-oriented dashboards, rate()/increase() queries, and reset-on-restart handling that assume real counter semantics. Fix: new registerDriverCounter() using Micrometer's FunctionCounter.builder (identical API shape to Gauge.builder -- same ToDoubleFunction-based live read from the underlying Morphium/MorphiumDriver reference, so none of the existing WeakReference-avoidance reasoning changes) for the four cumulative metrics. registerDriverGauge stays for the two genuinely instantaneous driver values (connections.pool, connections.in_use, threads.waiting) and the three cache/write-buffer gauges. Also fixes the plan doc's stale "Vorschlag / zur Diskussion -- noch nicht implementiert" status header (CodeRabbit finding, same PR): Phase 1 is implemented (sboesebeck#332); the Counter/Timer catalog rows from MorphiumStorageListener/MorphiumTransactionEvent remain deferred. A third automated finding from the same review round (CodeRabbit AND Codex, both P1: "MorphiumMetricsBinder.class references in MorphiumProducer force native-image reachability analysis to resolve Micrometer types even when Micrometer is absent, breaking no-Micrometer native builds") was independently investigated and NOT applied: built a minimal reproduction (a class with a field of a type absent from the classpath, referenced via a .class literal from an always-reachable method, exactly this PR's shape) and verified with a real GraalVM native-image build AND execution of the resulting native binary -- both succeeded (exit 0), because a .class literal alone does not force the JVM/native-image to resolve the target class's field/method-body types unless those methods are actually invoked, which isAvailable()==false correctly prevents here. Recorded as a rejected finding with its disproof rather than silently ignored. Verified: `mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment test` -- BUILD SUCCESS, exit 0, runtime 68/68 (incl. updated MorphiumMetricsBinderTest, 5/5, now asserting FunctionCounter vs. Gauge type per metric), deployment 29/29.
|
Ran this branch through an automated review pass on the fork (CodeRabbit + Codex, PR Bardioc1977#24 -- our fork's default branch is
A third finding (both bots, P1 severity) claimed the |
…references Copilot review finding on fork PR #24: MorphiumMetricsBinder's Javadoc referenced a single-argument bindTo(Morphium) overload that does not exist -- the only method is bindTo(Morphium m, String database). Three occurrences (class-level Javadoc twice, close()'s Javadoc once), all corrected to {@link #bindTo(Morphium, String)}. Verified by generating the actual Javadoc HTML and reading the resolved link, not just re-reading the source: `mvn -pl quarkus-morphium/runtime javadoc:javadoc` -- BUILD SUCCESS, exit 0; the generated MorphiumMetricsBinder.html now links to `#bindTo(de.caluga.morphium.Morphium,java.lang.String)`, the real method signature, confirmed via `grep -o 'href="[^"]*bindTo[^"]*"'` against the generated file. Re-ran `mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment test` -- BUILD SUCCESS, runtime 68/68, deployment 29/29, no regressions.
…n Javadoc Copilot review finding on fork PR #24: the class Javadoc claimed the extractor lambdas "close over the Morphium parameter" and that this @ApplicationScoped bean's lifetime is what keeps m reachable, avoiding the WeakReference-GC'd-to-NaN bug the plan cites from MongoConnectionPoolMetrics. That attribution is wrong. Verified directly against the Micrometer bytecode (io.micrometer.core.instrument.internal.DefaultGauge, javap'd): Gauge holds its target object via `private final WeakReference<T> ref`, not a strong reference -- so Micrometer itself never keeps m alive. MorphiumMetricsBinder stores only Meter.Id values in registeredMeters, never m itself, so the binder bean's own CDI lifetime is irrelevant to m's reachability. What actually prevents the bug: MorphiumProducer holds the same Morphium instance strongly via its own `private volatile Morphium instance` field, populated in buildMorphium() and cleared only in onStop() -- a lifetime that happens to outlive every gauge registered against it, but is not something MorphiumMetricsBinder does or guarantees on its own. Corrected the Javadoc to attribute the safety property to the right mechanism, so a future maintainer who e.g. extracted this binder for reuse outside MorphiumProducer wouldn't rely on a guarantee that doesn't actually come from this class. Verified: `mvn -pl quarkus-morphium/runtime javadoc:javadoc` -- BUILD SUCCESS, exit 0. `mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment test` -- BUILD SUCCESS, runtime 68/68, deployment 29/29, no regressions (comment-only change).
|
GitHub Copilot's re-review (fork PR #24) caught a Javadoc misattribution: the class-level comment claimed this bean's own CDI lifetime prevents the WeakReference-GC'd-to-NaN bug the plan cites -- verified against Micrometer's actual bytecode that this isn't true (Gauge holds its target via a real WeakReference; the actual safety comes from MorphiumProducer's own strong instance field). Fixed in |
Statistics.java computed CHITS/(CHITS+CMISS)*100 unconditionally; before any cached read has happened both are 0, so the ratio was 0.0/0.0 = NaN. Prometheus/OTel exporters silently drop NaN samples, so a fresh application's cache-hit-ratio metric appeared entirely missing instead of a real 'no data yet' 0%. Found while verifying the quarkus-morphium observability module (this branch) against a live otel-collector/Prometheus stack: 9 of the 10 new meters showed up immediately, morphium.cache.hit_ratio did not. New test cacheHitRatioIsZeroNotNaNBeforeAnyCachedRead, run against the inmem driver (18/18 total in StatisticsTest, 0 failures).
|
While testing this module's meters against a live otel-collector/Prometheus stack, Added a fix + regression test on this branch ( |
sboesebeck
left a comment
There was a problem hiding this comment.
Review: one blocking finding, otherwise solid
Verified against the actual code and dependencies (ArC 3.32.3 bytecode, Micrometer DefaultGauge/CumulativeFunctionCounter): the Capability gating, the post-connect registration point, the Gauge-vs-FunctionCounter classification and the WeakReference reasoning in the Javadoc all check out. The Statistics NaN fix is correct too - and it now reads each AtomicLong once instead of three times, so the two percentages are finally computed from one consistent snapshot.
🔴 Blocking: try (InstanceHandle<...>) destroys the @ApplicationScoped binder
MorphiumProducer.buildMorphium() and onStop() both use
try (InstanceHandle<MorphiumMetricsBinder> binderHandle = Arc.container().instance(MorphiumMetricsBinder.class)) {In ArC, InstanceHandle.close() is not just "release the handle". AbstractInstanceHandle.destroy() with no custom destroyLogic and a non-@Dependent scope calls context.destroy(bean) on the active context (see independent-projects/arc/runtime/.../impl/AbstractInstanceHandle.java; checked in the arc-3.32.3 jar's bytecode as well). Since MorphiumMetricsBinder is @ApplicationScoped, every one of these try blocks destroys the binder's contextual instance on exit - together with its registeredMeters list.
The consequence needs no hot-reload at all. In a completely normal lifecycle, buildMorphium() registers the meters and then immediately destroys the bean instance that recorded them. onStop() later gets a fresh instance with an empty list, so its close() is a silent no-op. The deregistration path this PR explicitly builds (and unit-tests) never removes anything inside a real container.
The Section-6.4 hot-reload protection breaks on top of that, in any setup where the MeterRegistry outlives the binder bean: the next bindTo() runs on a fresh bean, registers the same Meter IDs, Micrometer returns the existing meters still bound (weakly) to the old Morphium instance, the old instance gets GC'd, the gauges report NaN, Prometheus drops the samples. Exactly the failure mode the design doc sets out to prevent. The unit tests can't see any of this because they set binder.registry directly and bypass CDI entirely.
Suggested fix - two options, both fine by me:
- Simplest: drop the try-with-resources. An
InstanceHandledoesn't need closing; for a normal-scoped bean there is nothing to release. - Or resolve the binder once into a field on the producer (
private volatile InstanceHandle<MorphiumMetricsBinder> metricsBinder;) and reuse it inbuildMorphium()/onStop(), releasing it only on final shutdown.
Either way, please add a QuarkusUnitTest that runs two build cycles against the same registry and asserts no stale/duplicate meters - that pins the lifecycle contract the unit tests can't reach.
Non-blocking
- Missing CHANGELOG entry: the core change (CHITSPERC/CMISSPERC NaN -> 0) is user-visible and needs an entry under
## [Unreleased]->### Fixedper repo convention. - CI red, but not yours:
PooledDriverHeartbeatResilienceTest.heartbeatSelfRevivesWithoutGetPrimaryConnectionCallfails identically on current develop and master (pre-existing, test-side observability issue - being fixed separately). Not a regression from this PR. - Nit:
MorphiumProcessorObservabilityTestusesnew java.util.HashSet<>()inline - import it.
…ndle Addresses the blocking finding from Stephan Bösebeck's review of PR sboesebeck#332: MorphiumProducer.buildMorphium()/onStop() wrapped Arc.container().instance(MorphiumMetricsBinder.class) in a try-with-resources block. His bytecode reading of AbstractInstanceHandle#destroy() (the method that actually tears down a bean's contextual instance) was correct. Independently re-verified one level up the call chain (InstanceHandle#close()'s default method, which decides WHETHER destroy() runs): against arc-3.32.3.jar, close() only calls destroy() for a non-@Dependent-scoped bean when ArcContainer#strictCompatibility() is true (default: false, and Quarkus' own docs recommend leaving it false). MorphiumMetricsBinder is @ApplicationScoped and this repo never sets quarkus.arc.strict-compatibility, so in the actual default configuration the try-with-resources code did NOT destroy the bean on every call -- confirmed empirically by running this commit's new QuarkusUnitTest against the pre-fix code (checked out from 5701c3e) and observing it pass identically, then explaining why via a ClientProxy identity check: get() on an @ApplicationScoped bean's InstanceHandle returns the same ClientProxy on every independent lookup, which is what let registeredMeters survive across try-with-resources calls in practice. Fixed anyway, because it is still strictly more correct and removes a latent dependency on strictCompatibility() staying false forever and on MorphiumMetricsBinder's scope never changing to @dependent: introduces MorphiumProducer#metricsBinderHandle(), a lazily-resolved InstanceHandle field reused across buildMorphium()/onStop() (including across dev-mode hot-reload cycles), released only once in onStop() on final application shutdown. Also fixes the two non-blocking review points: adds the CHANGELOG entry for the CHITSPERC/CMISSPERC NaN fix (commit 5701c3e, already on this branch) under [Unreleased] -> Fixed, and replaces an inline java.util.HashSet<>() with an import in MorphiumProcessorObservabilityTest. Adds a new io.quarkus:quarkus-junit-internal test dependency (version resolved via the existing quarkus-bom import, no explicit pin) to the deployment module -- the QuarkusUnitTest infrastructure this fix's own verification needed and that a plain unit test (which sets binder.registry directly, bypassing CDI entirely) cannot reach. Also adds the java.util.logging.manager system property to the deployment module's surefire configuration (same property this repo's integration-tests module already sets), required for QuarkusUnitTest's own logging bootstrap. Verified: new MorphiumMetricsBinderLifecycleTest (2 tests) proves meters survive two full connect/disconnect cycles against a real ArC container with no duplicates and no leaks, and documents the ClientProxy mechanism directly. Full runtime+deployment test suite (99 tests across both modules) green, 0 regressions.
|
Thanks for the deep review — went through the blocking finding with the same level of rigor. Bytecode-verified your Confirmed empirically before drawing that conclusion, not just from the bytecode: ran a new Fixed anyway — Full runtime+deployment suite (99 tests) green. |
There was a problem hiding this comment.
🔵 Needs a closer look
The change introduces non-trivial CDI InstanceHandle lifecycle, hot-reload deregistration, native-image gating, and a core statistics change that warrant human verification despite only minor issues being found.
Pull request overview
This PR adds an optional Micrometer observability module to the quarkus-morphium extension. It exposes MongoDB connection-pool/driver statistics and cache/write-buffer levels as Micrometer meters, gated on Capability.METRICS so applications without a metrics extension see no behavioural change and inherit no new dependency. It follows the extension's existing "optional dependency + Capabilities gate" pattern (as used for Jackson/JSON-B). It also fixes an unrelated NaN bug in morphium-core's cache-hit-ratio statistics discovered while wiring up the module.
Changes:
- New
MorphiumMetricsBinderregistersGauges (pool/in-use/waiting, cache/write-buffer) andFunctionCounters (borrowed/released/errors/failovers) against the injectedMeterRegistry, with hot-reload-safe deregistration; wired intoMorphiumProducer.buildMorphium()/onStop()via a lazily-resolved, reusedInstanceHandle, and gated at build time by a newMorphiumProcessor.registerObservabilitybuild step plus a runtimequarkus.morphium.observability.enabledkill-switch. Statistics.javanow guards theCHITS/(CHITS+CMISS)division against0/0, reporting0.0instead ofNaNand reading eachAtomicLongonce.- Optional
quarkus-micrometer(+-deployment) dependencies, an architecture plan doc, a CHANGELOG entry, and unit/lifecycle tests.
File summaries
| File | Description |
|---|---|
runtime/.../observability/MorphiumMetricsBinder.java |
Core binder registering gauges/counters and deregistering on close. |
runtime/.../observability/MorphiumObservabilityConfig.java |
Nested config exposing the enabled kill-switch. |
runtime/.../MorphiumRuntimeConfig.java |
Wires the nested observability config. |
runtime/.../MorphiumProducer.java |
Registers/deregisters metrics via a cached InstanceHandle. |
deployment/.../MorphiumProcessor.java |
Capability.METRICS-gated build step registering the binder bean. |
runtime/pom.xml, deployment/pom.xml |
Optional Micrometer deps + test/logging setup. |
morphium-core/.../Statistics.java |
Fixes NaN cache-hit-ratio before any cached read. |
.../StatisticsTest.java, .../MorphiumMetricsBinderTest.java, .../MorphiumProcessorObservabilityTest.java, .../MorphiumMetricsBinderLifecycleTest.java |
Unit + lifecycle tests. |
docs/architecture/observability-module-plan.md, CHANGELOG.md |
Design plan and changelog entry. |
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @@ -0,0 +1,208 @@ | |||
| package de.caluga.morphium.quarkus.deployment; | |||
| * Deregisters every {@link Meter} this binder has registered so far. Called from | ||
| * {@code MorphiumProducer#onStop()} alongside {@code instance.close()}, and must also be | ||
| * called before a subsequent {@link #bindTo(Morphium, String)} on a dev-mode hot-reload to avoid | ||
| * leaving stale gauges referencing a superseded {@link Morphium} instance registered |
…d in Javadoc Two minor Copilot review findings on PR sboesebeck#332: - MorphiumMetricsBinderLifecycleTest.java was missing the Apache 2.0 license header every other test file in this package carries. - MorphiumMetricsBinder#close()'s Javadoc had a dangling trailing word ("...referencing a superseded Morphium instance registered" -> removed the stray "registered"). Full runtime+deployment suite (99 tests) green, 0 regressions.
|
Two minor Copilot findings on the re-review, both fixed in
Full runtime+deployment suite (99 tests) green. |
|
All addressed, thanks. Your counter-evidence on the InstanceHandle point is solid. Running the test against 5701c3e and showing it passes identically is exactly the right way to settle a question like that. I read destroy() correctly but not far enough: that close() only gets there under strict-compatibility or @dependent scope escaped me. The fix is still the better shape, so it stays. Merging. |
sboesebeck
left a comment
There was a problem hiding this comment.
Blocking finding resolved, and the follow-up analysis was better than my original one. Good to go.
What
An optional Micrometer metrics integration for quarkus-morphium, gated on
Capability.METRICSso an application without Micrometer on its classpath sees zero change -- no new dependency inherited, no bean registered, no behaviour change.Why
Teams running quarkus-morphium in production commonly hand-write Micrometer boilerplate to expose the MongoDB connection pool as gauges -- one real-world case needed ~65 lines for this, including a weak-reference/GC bug that had to be found and fixed once already. This gives every quarkus-morphium consumer the same visibility for free.
Design
Extends
quarkus-morphium/quarkus-morphium-deploymentin place (no new Maven module), mirroring the module's own existingCapability.JACKSON/JSONB-gated pattern (MorphiumProcessor.registerMorphiumIdJsonCustomizers) rather than introducing a second artifact for the same shape. Full architecture plan, produced by an architect pass and independently verified against the actual code before adoption, atquarkus-morphium/docs/architecture/observability-module-plan.md(first commit on this branch).Phase 1 (this PR) scope: connection-pool/driver-stats gauges and counters only (
morphium.driver.*,morphium.cache.*,morphium.write_buffer.*, sourced fromMorphiumDriver.getDriverStats()/Morphium.getStatistics()). Cumulative values (connections.borrowed/.released,.errors,.failovers) are registered as MicrometerFunctionCounters, notGauges, matching the metric catalog's own classification and giving backends correct counter semantics forrate()/increase()queries. The Counter/Timer rows sourced fromMorphiumStorageListener/MorphiumTransactionEvent(morphium.operations.*,morphium.transactions.*) are explicitly deferred to a later phase.Registration happens inside
MorphiumProducer.buildMorphium(), after the connection already exists -- never from an early@Observes StartupEventthat could trigger the lazy connect prematurely. Deregistration ononStop()and before every re-bind, so a dev-mode hot-reload never leaves stale gauges. A runtime kill-switch (quarkus.morphium.observability.enabled, defaulttrue) lets an app that has Micrometer on its classpath for an unrelated reason opt out of Morphium's gauges specifically.Process
Went through a full implementer -> reviewer loop before this PR was opened, plus an independent automated-review pass with findings verified against the actual code/bytecode (not taken on faith either way):
Capability.METRICS's deprecation status in Quarkus 3.32.3, a missing runtime kill-switch the plan specified as in-scope, correcting the metric type for cumulative driver stats (Gauge -> FunctionCounter), and a Javadoc correction (a WeakReference/GC-safety explanation was misattributed -- verified against Micrometer's actual bytecode before correcting it)..classliteral reference would break native-image builds for apps without Micrometer) was investigated with an actual GraalVM native-image build and execution of a minimal reproduction of the exact shape, and did not reproduce -- not applied, with the disproof on record.Verification
mvn -pl quarkus-morphium/runtime,quarkus-morphium/deployment -am -DskipTests installthen... test(no-amfor the test run, to avoid the unrelated 25+ minutemorphium-coresuite) -- BUILD SUCCESS, runtime 68/68 tests passing, deployment 29/29 passing. Re-verified fresh against the final commit HEAD, not just the pre-commit working tree.