feat(schema): PR-PLATFORM-3 — dual-client schema-registry impl with Apicurio + Confluent wire-format hedge #3
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/pr-platform-3-apicurio-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Replaces the v1.0 scaffold's
UnsupportedOperationExceptionstubs inapicurio-client/with the production implementation per ADR-0011 §B.What landed
SchemaRegistryClientinterface —register(subject, bytes, type),fetchByGlobalId,fetchLatestBySubject,kind(),wireFormat(); nestedKind/SchemaTypeenums +Schemarecord.ApicurioSchemaRegistryClient— wrapsio.apicurio.registry.rest.client.RegistryClient 2.5.11.Final(createArtifact / getContentByGlobalId / getLatestArtifact). OTel CLIENT span per call withschema.registry,schema.subject,schema.global_id,outcome.ConfluentWireCompatSchemaRegistryClient— same Apicurio backend, butwireFormat() = CONFLUENT_4_BYTE. The OBJ-71 hedge: switching producers between 8-byte globalId prefix and 0x00 + 4-byte schemaId prefix is now a 1-bean config change, not a rewrite.DualSchemaRegistryClient— primary success path; onRestClientException5xx orSchemaRegistryIOException, falls back.fetchByGlobalIdis primary-only (globalIds ≠ schemaIds).WireFormatenum +WireFormatCodec— pure utility for prepend/parse of both prefixes; throwsWireFormatExceptionon length-too-short / bad magic-byte.SchemaRegistryProperties+SchemaRegistryAutoConfiguration—im2be.schema-registry.*binding; exactly-one-impl-bean contract acrossenabled=false,mode=apicurio(default),mode=confluent,mode=dual.apicurio-client/README.md— usage + ASCII diagram of the two wire layouts + cross-refs to ADR-0011 §B / ADR-0014 D-3.Tests (36 unit + 1 IT)
WireFormatCodecTest— 10 round-trip + boundary cases.ApicurioSchemaRegistryClientTest— 8 cases including OTel span attribute verification viaInMemorySpanExporter.ConfluentWireCompatSchemaRegistryClientTest— 3 cases.DualSchemaRegistryClientTest— 8 cases (success, 5xx → fallback, IOException → fallback, 4xx → no fallback, fetchByGlobalId never falls back).SchemaRegistryAutoConfigurationTest— 7 cases (4 mode variations + groupId override).ApicurioIT— Testcontainers end-to-end againstapicurio/apicurio-registry-mem:2.5.11.Final. Gated byRUN_DOCKER_TESTS=true; skips cleanly without Docker.Build verification
mvn -B clean install→ BUILD SUCCESS (all 5 modules), 0 compile/lint warnings (rule 62).mvn -B -pl apicurio-client test→ 36/36 unit tests green.RUN_DOCKER_TESTS=true mvn -B -pl apicurio-client verify→ IT green against pulledapicurio-registry-mem:2.5.11.Finalcontainer.Pinned versions (rule 61 — verified)
apicurio-registry-client2.5.11.Finalopentelemetry-api1.49.0testcontainers/junit-jupiter1.21.4Test plan
mvn -B clean installproduces BUILD SUCCESS with zero[WARNING]lines (excluding skipped-test informational tags).mvn -B -pl apicurio-client testshows 36 unit tests pass.RUN_DOCKER_TESTS=true mvn -B -pl apicurio-client verifyruns ApicurioIT end-to-end againstapicurio-registry-mem:2.5.11.Final.<dependency>apicurio-client, setim2be.schema-registry.enabled=true+mode=apicurio, and inject aSchemaRegistryClientbean.Cross-refs
.planning/26-stage-b-outbox-parity.md§0 row L-19 (OBJ-71)Replaces v1.0 scaffold `UnsupportedOperationException` stubs with the production impl per ADR-0011 §B. New public API: - SchemaRegistryClient interface: long register(subject, bytes, type), Schema fetchByGlobalId / fetchLatestBySubject, Kind enum, WireFormat default method. SchemaType enum (AVRO/PROTOBUF/JSON), Schema record. - ApicurioSchemaRegistryClient — wraps io.apicurio.registry.rest.client .RegistryClient 2.5.11.Final via createArtifact / getContentByGlobalId / getLatestArtifact + getArtifactMetaData. OTel CLIENT span per call with schema.registry / schema.subject / schema.global_id / outcome. - ConfluentWireCompatSchemaRegistryClient — same Apicurio backend, but wireFormat() returns CONFLUENT_4_BYTE. Producers prepend the 0x00 + 4-byte schemaId prefix instead of the 8-byte globalId. - DualSchemaRegistryClient — primary success; on RestClientException errorCode≥500 or SchemaRegistryIOException, fallback. fetchByGlobalId is primary-only (globalIds and schemaIds are not interchangeable). - WireFormat enum + WireFormatCodec utility (prepend/parse for both layouts; length + magic-byte validation → WireFormatException). - SchemaRegistryProperties (@ConfigurationProperties("im2be.schema-registry")): enabled, url, mode (APICURIO/CONFLUENT/DUAL), groupId, compatibility. - SchemaRegistryAutoConfiguration — exactly-one-impl-bean contract; mode property selects which of the three beans is active. Tests (36 unit + 1 IT): - WireFormatCodecTest: 10 round-trip + boundary cases. - ApicurioSchemaRegistryClientTest: 8 cases including OTel span attrs. - ConfluentWireCompatSchemaRegistryClientTest: 3 cases. - DualSchemaRegistryClientTest: 8 cases (primary success, 5xx fallback, IOException fallback, 4xx no-fallback, fetchByGlobalId never falls back). - SchemaRegistryAutoConfigurationTest: 7 cases covering enabled=false, apicurio/confluent/dual modes, groupId override. - ApicurioIT: Testcontainers end-to-end against apicurio/apicurio-registry-mem:2.5.11.Final. Gated by RUN_DOCKER_TESTS=true (skips cleanly without Docker); verified locally with Docker. Maven changes (apicurio-client/pom.xml): - Added opentelemetry-api (compile scope, version managed by Spring Boot 3.5.14 BOM = 1.49.0). - Added testcontainers + junit-jupiter (test scope, BOM-managed 1.21.4). - Added spring-boot-starter (for autoconfig/properties binding) and spring-boot-configuration-processor (optional, generates IDE metadata). - Wired failsafe plugin to run *IT.java during verify. Verification: - mvn -B clean install → BUILD SUCCESS (all 5 modules), 0 compile/lint warnings (rule 62). - mvn -B -pl apicurio-client test → 36/36 unit tests green. - RUN_DOCKER_TESTS=true mvn -B -pl apicurio-client verify → IT green against pulled apicurio-registry-mem:2.5.11.Final container. Pinned versions (rule 61 — verified against Maven Central + Spring Boot 3.5.14 effective-pom): - apicurio-registry-client 2.5.11.Final (parent pom property) - opentelemetry-api 1.49.0 (Spring Boot BOM) - testcontainers 1.21.4 (Spring Boot BOM) Cross-refs: ADR-0011 §B, ADR-0014 D-3, .planning/26-stage-b-outbox-parity.md §0 row L-19 (OBJ-71).Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 1 — head
4b0bead1a31d, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 6 findings (1 major agreed, 4 minor of which 2 unique-to-A/B each verified, 1 minor agreed deferred); no findings dropped.
Summary
Arbitration — Round 1
Prior run history: None found — first arbitration for this PR.
Reconciliation actions:
catch(NotFoundException)at line 103):ApicurioSchemaRegistryClient.fetchLatestBySubject()absorbsNotFoundExceptionat its own line 182 and returnsnull; confirmed by reading both files — exception never propagates, catch is dead, andnot_foundoutcome attribute is therefore never stamped on the dual span.isServerErrornull-err bypass + TOCTOU race): both verified by reading source.isServerError()lines 146-149 confirmederr==null→false;fetchLatestBySubject()confirmed two unchained delegate calls at lines 169/173.compatibilityproperty): both A and B flagged it; grep ofsrc/mainconfirmsgetCompatibility()is declared only inSchemaRegistryProperties.javaand called nowhere else. Severity normalised tominor(A's rating, which better captures user-visible silent-misconfiguration impact over B'sinfo).Line citation resolution: A cited line 70 and B cited line 69 for the
makeCurrentfinding. Line 69 is the method signature; line 70 is where the span is created (the actionable fix point). Kept A's line 70.Blast Radius
The PR introduces 7 new public types across a shared platform library (
apicurio-client) consumed by every Java service in the aim2be platform. The new exported surface (SchemaRegistryClient,WireFormatCodec,DualSchemaRegistryClient, etc.) will be transitively depended on by all Kafka producers and consumers. The blast radius is bounded because this is additive-only (no existing classes modified beyondApicurioSchemaRegistryClientwhich gains new methods) and the auto-configuration requires explicit opt-in viaim2be.schema-registry.enabled=true.BLAST_SCORE: 5/10
Risk Indicators
DualSchemaRegistryClient.isServerError,DualSchemaRegistryClient.register,DualSchemaRegistryClient.fetchLatestBySubject,ApicurioSchemaRegistryClient.register,ApicurioSchemaRegistryClient.fetchLatestBySubject,SchemaRegistryAutoConfigurationCI status (head
4b0bead1a31d)Overall: ✗ failure
2 checks: 2 pending
Findings (6)
[MAJOR]
span.makeCurrent()never called — delegate spans are orphaned trace roots, not children of the coordinator spanapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:70
Both
register()(span at line 70) andfetchLatestBySubject()(span at line 97) build and start a span but never callspan.makeCurrent().io.opentelemetry.context.Scopeis not even imported in this file.Without
makeCurrent()the OTel context thread-local is not updated, so whenprimary.register()/fallback.register()start their own spans they pick up the ambient context from before the dual method was entered — the primary/fallback spans become sibling roots rather than children of the dual coordinator span. In production the dual coordinator is invisible in every distributed trace that includes delegate spans.ApicurioSchemaRegistryClienthandles this correctly (line 75:final Scope scope = span.makeCurrent();, closed infinallybeforespan.end()).Fix — same pattern, both methods:
Add
import io.opentelemetry.context.Scope;. Apply toregister()at line 70 andfetchLatestBySubject()at line 97.[MINOR] Primary-success path never sets
outcome=success— successful dual-layer spans are un-attributed in dashboardsapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:75
In
register(): whenprimary.register()at line 75 returns normally, execution exits through thefinallyblock (span.end()at line 85) with nooutcomeattribute set — only the error and fallback branches tag an outcome. The same gap exists infetchLatestBySubject()at line 102: a successful return fromprimary.fetchLatestBySubject(subject)ends the span silently.Every method in
ApicurioSchemaRegistryClientsetsoutcome=successon its happy path (e.g. line 98 inregister(), line 175 infetchLatestBySubject()). Without parity, querying traces byoutcomereturns no dual-layer hits for the common success case.Fix:
The null-check on the
fetchLatestBySubjectreturn simultaneously addresses the dead-catch finding (F3 below).[MINOR]
catch (NotFoundException nfe)infetchLatestBySubject()is dead code —ApicurioSchemaRegistryClientabsorbs the exception internallyapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:103
ApicurioSchemaRegistryClient.fetchLatestBySubject()catchesNotFoundExceptionat its own line 182 and returnsnull— the exception never propagates toDualSchemaRegistryClient. The catch block at line 103 therefore never fires:primary.fetchLatestBySubject("missing")→nulltryand returnsnullat line 102outcomeattribute (neithernot_foundnorsuccess)Fix: remove the dead catch. Capture the return value and branch on null:
This simultaneously removes the dead branch and fills the observability gap noted in F2.
[MINOR]
isServerError()returnsfalsewhenerr == null, causing network-level failures to bypass the fallback hedgeapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:146
When the Apicurio registry is completely unreachable (TCP timeout, connection refused), the Apicurio REST client throws
RestClientExceptionwitherr == null(no HTTP response body).isServerError()returnsfalse, so the caller at line 77 (if (!isServerError(rce)) { throw rce; }) rethrows rather than delegating torunFallbackRegister— the Confluent hedge is never invoked.This is exactly the failure mode the dual-client exists to hedge. A total registry outage, the worst case, is the one scenario where the fallback is silently skipped.
Fix: treat a null
erras a transport-level failure and fall back:[MINOR]
fetchLatestBySubject()TOCTOU: body and metadata fetched in separate calls — concurrent registration yields a mismatchedSchemarecordapicurio-client/src/main/java/com/aim2be/platform/schema/ApicurioSchemaRegistryClient.java:169
A concurrent producer that registers a new version between lines 169 and 173 causes
meta.getGlobalId()andmeta.getVersion()to reflect version N+1 whilebytescontains version N's schema body. The resultingSchemarecord is structurally invalid: consumers that useglobalIdto deserialise records will apply the wrong schema.Fix: fetch metadata first, then fetch the artifact body pinned to the known version:
This makes the body fetch idempotent with respect to the already-captured metadata version.
[MINOR]
compatibilityproperty is bound and documented but never consumed — operators are silently ignoredapicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:47
The field
private String compatibility = "BACKWARD"(line 47) is exposed viagetCompatibility()(line 90) and documented in both the README (compatibility: BACKWARD # ADR-0011 §Cdescribed as "Applied at artifact creation time only if a global rule is not already set") and the field Javadoc. HowevergetCompatibility()is called nowhere insrc/main— confirmed by grepping the entire tree.SchemaRegistryAutoConfigurationandApicurioSchemaRegistryClient.register()both ignore it.Any operator who sets
im2be.schema-registry.compatibility=FORWARDexpecting enforcement will receive Apicurio's default (no compatibility check) instead, with no warning.Required action: either (a) implement the rule application in
register()viadelegate.createArtifactRule(...)/delegate.createGlobalRule(...), or (b) remove the property and README entry until the feature is ready. This is flagged as tech-debt — the core hedge implementation in this PR is unaffected, but the property must not be silently swallowed at merge.Verdict
NEEDS_WORK
hib-pr-reviewer • round 1 • 6 findings (1M/5m) • 2026-05-26T22:53:59.852Z → 2026-05-26T22:57:18.488Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 2 — head
b2586ec4eb2d, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 5 findings (1 major agreed by both, 4 minors all verified by Read/Grep); no findings dropped.
Summary
Arbitration — Round 2 (A=4, B=2 → kept=5)
Memora
Both
tags_any:["pr-reviewer","im2be-platform-libs"]andtags_all:["pr-review-run","pr:…#3"]returned 0 hits → no prior run history; this is the effective first arbitration entry. Persist attempt was blocked by Memora permission gates (bothmemory_createandmemory_absorbrejected).Verification summary
runFallbackFetchLatestunconditionally tagsoutcome=fallback_successon null returnensureCompatibilityRule()throws aftercreateArtifactsucceeds → false failurecatch(RuntimeException)blockgrepfound only the comment line; nocreateArtifactRuleorBACKWARDtest stubSchemaRegistryClient.fetchByGlobalIdJavadoc missing sentinelsubject=""/version=0contract@returncovers onlynull; impl at line 156 silently returns sentinelsAll 5 findings are grounded in source. No finding dropped.
Blast Radius
The diff introduces three new exported types (
ConfluentWireCompatSchemaRegistryClient,DualSchemaRegistryClient,WireFormat/WireFormatCodec/WireFormatException) and modifies theSchemaRegistryClientinterface and its Spring Boot auto-configuration entry point — surfaces injected by every platform Kafka service. The bug inDualSchemaRegistryClientaffects any deployment usingmode=dual; the interface Javadoc gap affects any caller offetchByGlobalIdregardless of mode.BLAST_SCORE: 6/10
Risk Indicators
ApicurioSchemaRegistryClient.register,DualSchemaRegistryClient.runFallbackRegister,DualSchemaRegistryClient.runFallbackFetchLatest,SchemaRegistryAutoConfiguration.dualSchemaRegistryClient,ApicurioSchemaRegistryClient.ensureCompatibilityRuleCI status (head
b2586ec4eb2d)Overall: ✗ failure
2 checks: 2 pending
Findings (5)
[MAJOR] Dual-mode fallback is an alias for primary — the 5xx hedge is an unconditional retry of the same failing endpoint
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryAutoConfiguration.java:124
Lines 122-126 (confirmed by
Read):The package-private
ConfluentWireCompatSchemaRegistryClient(ApicurioSchemaRegistryClient)constructor (verified atConfluentWireCompatSchemaRegistryClient.java:71-72) storesprimarydirectly asthis.apicurioDelegate. Every delegating method (registerat line 77,fetchByGlobalIdat line 82,fetchLatestBySubjectat line 87) routes back to the sameApicurioSchemaRegistryClientobject, meaningfallback.register() === primary.register()at the Java-reference level.When
DualSchemaRegistryClient.runFallbackRegister()fires after catching aRestClientException(5xx)from the primary, it callsfallback.register()→primary.register()→ the identical Apicurio endpoint that just returned 5xx. The hedge never helps for backend outages.The
SchemaRegistryAutoConfigurationTest.enabled_dualMode_exposesDualImpl()only assertsbean.kind()andbean.wireFormat(), so this goes undetected by the test suite.Fix A — true backend redundancy: Add a
fallback-urlproperty toSchemaRegistryProperties, construct an independentRegistryClientfrom it, and pass it to the public 4-arg constructor:Fix B — honest wire-format-only semantics: Remove the
RestClientException/SchemaRegistryIOExceptionfallback branches fromDualSchemaRegistryClient.register()andfetchLatestBySubject(). Documentdualmode as a pure wire-format toggle (both paths share the same Apicurio backend) and propagate primary failures directly. Add an assertionassertThat(primary).isNotSameAs(((DualSchemaRegistryClient)bean).getFallbackDelegate())to the config test.Agreed by both Reviewer A and Reviewer B.
[MINOR]
runFallbackFetchLatestunconditionally recordsoutcome=fallback_successeven when the fallback returnsnullapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:189
Lines 182-191 (confirmed by
Read):When
fallback.fetchLatestBySubject(subject)returnsnull(subject absent from the registry), the span is still taggedoutcome=fallback_success. Dashboards will count "subject not found anywhere" as a successful fallback, masking the difference between a genuine hedged fetch and a true not-found.Fix: Mirror the null-check used on the primary path:
[MINOR]
ensureCompatibilityRule()can throw aftercreateArtifactsucceeds, surfacing a false registration failure to the callerapicurio-client/src/main/java/com/aim2be/platform/schema/ApicurioSchemaRegistryClient.java:116
Lines 114-123 (confirmed by
Read):ensureCompatibilityRule(lines 259-273) catches onlyRuleAlreadyExistsException. Any otherRuntimeExceptionfromdelegate.createArtifactRule()— for example aRestClientException(422)caused by an invalidcompatibilityvalue such as"BACKWARD_TYPO"— propagates through the outercatch (RuntimeException ex)block and is re-thrown. The artifact is now stored in the registry but the caller receives an exception and has noglobalId. On retry the artifact is idempotent (IfExists.RETURN_OR_UPDATE) but the rule call fails again, creating an infinite error loop for any misconfiguredcompatibilityvalue.Fix (preferred) — make rule enforcement best-effort:
Alternative: Validate the
compatibilitystring eagerly in a@PostConstructmethod or in theSchemaRegistryPropertiesbinder, so the service refuses to start rather than failing per-call.[MINOR] Comment at
setUp()promises a dedicatedensureCompatibilityRuletest that does not existapicurio-client/src/test/java/com/aim2be/platform/schema/ApicurioSchemaRegistryClientTest.java:64
Lines 63-65 (confirmed by
Read+Grep):A
GrepforcreateArtifactRule,ensureCompatibilityRule,BACKWARD, andnon-nullacross the full test file (176 lines) returns only this comment line — no test body exists. The path added to fix R1 F6 (ensureCompatibilityRuleinApicurioSchemaRegistryClient) is completely uncovered.Add at minimum:
[MINOR]
fetchByGlobalIdinterface Javadoc does not document thesubject=""/version=0sentinel contractapicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryClient.java:55
Line 55 (confirmed by
Read):The implementation (
ApicurioSchemaRegistryClient.java:156, confirmed byRead) returns:with
subject=""andversion=0as sentinels when the Apicurio v2 API does not surface artifact coordinates alongside the content body (implementation comment at lines 150-154 explains this is a v2 API limitation). The interface@returntag covers only thenullnot-found case; consumers readingschema.subject().isEmpty()or treatingschema.version() == 0as "newly registered" will silently misinterpret a valid successful lookup.Fix: Extend the
@returntag:Verdict
NEEDS_WORK
hib-pr-reviewer • round 2 • 5 findings (1M/4m) • 2026-05-26T23:09:16.906Z → 2026-05-26T23:12:23.464Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 3 — head
b387c8cf4896, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 6 findings (1 major stale-enum-Javadoc + 5 minor stale-doc/missing-span/wrong-env-var); all 4 from A and all 4 from B verified at HEAD, with 2 agreed and 2 unique-to-each kept; note Memora persistence failed due to server-side tag validation error.
Summary
Arbitration summary
Memora persistence failed (server-side tag validation issue: auto-applied
memora/issuesnot in allowlist). All 6 findings verified against HEAD; reconciliation proceeds.Agreed findings (2): Both reviewers independently flagged the same stale Javadoc in
SchemaRegistryProperties.java:113(DUAL enum; A=major, B=minor → preserved A's major severity per the stronger operator-impact justification) andSchemaRegistryClient.java:24(interface bullet; both minor).Unique-to-A (2, both verified):
DualSchemaRegistryClient.java:100— bare delegation with noschema.dual.fetchByGlobalIdspan confirmed at lines 100-103.ApicurioIT.java:25— Javadoc saysTESTCONTAINERS_RYUK_DISABLED; line 37 annotation saysRUN_DOCKER_TESTS— confirmed.Unique-to-B (2, both verified):
DualSchemaRegistryClientTest.java:26— Javadoc bullets at lines 26-27 say "fallback invoked" but test methods at lines 59 and 73 both callverify(fallback, never())— confirmed. B cited line 29 (the correctfetchByGlobalIdbullet); corrected to line 26 where the stale text begins.SchemaRegistryAutoConfiguration.java:107— lines 106-107 confirmed still say "Confluent fallback on 5xx / IOException" — confirmed.Total: 1 major + 5 minor = 6 findings kept.
Blast Radius
The PR adds a new shared platform library (
apicurio-client) that every Java service will inject as a compile-scope dependency. Changes touch 18 files across one Maven module, but the exported surface (SchemaRegistryClientinterface,SchemaRegistryProperties,SchemaRegistryAutoConfiguration) is the shared contract for all producers and consumers on the Kafka substrate. Any correctness issue in the interface or configuration metadata propagates to every consuming service.BLAST_SCORE: 6/10
CI status (head
b387c8cf4896)Overall: ✗ failure
2 checks: 2 pending
Findings (6)
[MAJOR] DUAL enum Javadoc still advertises discarded HA-fallback-on-5xx semantics
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Verified at line 113:
This is the primary operator-facing surface for
mode=dual— IDE autocomplete,spring-configuration-metadata.json, and Kubernetes-overlay reviewers all read this doc. The R2 redesign explicitly removed the fallback-on-error path;DualSchemaRegistryClientnow propagates outages rather than retrying via the Confluent delegate. An operator who enablesmode=dualexpecting HA will not get it, and this enum doc actively reinforces that wrong expectation.Suggested fix:
[MINOR] Interface Javadoc for DualSchemaRegistryClient describes the abandoned fallback-on-5xx contract
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryClient.java:24
Verified at lines 24-25:
This is now incorrect after the R2 redesign. Outages propagate; the fallback exists only for wire-format coexistence. The interface Javadoc is the first contact point for any integrating service and must agree with the implementation.
Suggested fix:
[MINOR] Bean factory Javadoc for dual mode still claims Confluent fallback on 5xx / IOException
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryAutoConfiguration.java:107
Verified at lines 106-107:
The fallback-on-5xx path was removed in the R2 redesign. This
@Beanfactory Javadoc no longer matches whatDualSchemaRegistryClientactually does.Suggested fix:
[MINOR]
fetchByGlobalIdemits no coordinator span — primary's span is un-parented in distributed tracesapicurio-client/src/main/java/com/aim2be/platform/schema/DualSchemaRegistryClient.java:100
Verified at lines 100-103:
register()andfetchLatestBySubject()both create aschema.dual.*coordinator span and callspan.makeCurrent(), ensuring the primary's child span is linked in the trace tree.fetchByGlobalIdhas no wrapper span, so the primary's span won't carry aschema.dual.fetchByGlobalIdparent — it's indistinguishable in distributed traces from a directApicurioSchemaRegistryClientcall, losing thekind=DUALattribution.Suggested fix (no fallback logic, matching the comment intent):
[MINOR] Test-class Javadoc contradicts the tests it describes
apicurio-client/src/test/java/com/aim2be/platform/schema/DualSchemaRegistryClientTest.java:26
Verified at lines 26-27 (class-level Javadoc):
But the actual test methods
register_primary5xx_propagates(line 59) andregister_primaryIOException_propagates(line 73) both assertverify(fallback, never())— the fallback is never invoked. A maintainer reading the class Javadoc will believe the tests document a fallback-invoked contract and may incorrectly "fix" a test to match the stale description.Suggested fix:
[MINOR] Class Javadoc names
TESTCONTAINERS_RYUK_DISABLEDas the gate variable; actual gate isRUN_DOCKER_TESTSapicurio-client/src/test/java/com/aim2be/platform/schema/ApicurioIT.java:25
Verified at lines 25-29 (Javadoc) vs line 37 (annotation):
TESTCONTAINERS_RYUK_DISABLEDis an unrelated Testcontainers setting (disables the Ryuk reaper); it does not gate this test. A developer following the Javadoc will set the wrong variable and be confused when the test still does not run. The README and the annotation both useRUN_DOCKER_TESTS— the Javadoc must be updated to match.Verdict
NEEDS_WORK
hib-pr-reviewer • round 3 • 6 findings (1M/5m) • 2026-05-26T23:20:14.312Z → 2026-05-26T23:22:52.075Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 4 — head
de70396e7d7a, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — 5 minor findings kept this round.
Summary
Arbiter reconciled 5 (A) + 3 (B) → 5 findings.
CI status (head
de70396e7d7a)Overall: ✗ failure
2 checks: 2 pending
Findings (5)
[MINOR] DUAL enum Javadoc still advertises abandoned 5xx-fallback semantics
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads:
This directly contradicts the corrected design (both delegates share the same Apicurio backend; all errors propagate). Change to:
[MINOR] Interface Javadoc for DualSchemaRegistryClient still describes abandoned fallback-on-5xx contract
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryClient.java:24
Lines 24–26 read:
All other documentation sites for this class have been corrected; this bullet is the last survivor. Change to:
[MINOR] README Public API table still describes DualSchemaRegistryClient as an HTTP-5xx fallback
apicurio-client/README.md:43
Line 43 reads:
This is the discarded R2 HA-hedge design. The README is the primary entry point for new consumers; leaving stale semantics here risks operators setting
mode=dualexpecting HA. Change the Role cell to:[MINOR] Observability table documents
schema.registry.fallbackattribute that is never emittedapicurio-client/README.md:133
Line 133 reads:
A full
grepof all Java source files underapicurio-client/src/main/javafinds zero occurrences of the stringschema.registry.fallback. This attribute was emitted by the oldrunFallbackFetchLatest/runFallbackRegisterhelpers that were deleted in R2. An operator who writes a dashboard alert onschema.registry.fallback=truewill never receive it. Remove this row, or replace it withschema.compatibility.appliedwhichensureCompatibilityRule()actually does emit.[MINOR] confluent-mode spans are indistinguishable from apicurio-mode spans in telemetry
apicurio-client/src/main/java/com/aim2be/platform/schema/ConfluentWireCompatSchemaRegistryClient.java:76
ConfluentWireCompatSchemaRegistryClient.register()(line 76),fetchByGlobalId()(line 81), andfetchLatestBySubject()(line 86) all delegate unconditionally toapicurioDelegate, which hardcodesspan.setAttribute("schema.registry", "apicurio")(verified atApicurioSchemaRegistryClientlines 90, 135, 183). When a service is configured withmode=confluent, every emitted span carriesschema.registry=apicurio, making it impossible to distinguish frommode=apicurioin dashboards — and the README Observability table (line 128) already documentsapicurioas the only ever value, confirming the gap.Suggested fix: inject the
schema.registrytag value as a constructor parameter ofApicurioSchemaRegistryClientand pass"confluent_wire_compat"when constructing the inner delegate insideConfluentWireCompatSchemaRegistryClient; alternatively, add a thin wrapping span in each of the three delegate methods that tagsschema.registry=confluent_wire_compat.Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 4 • 5 findings (5m) • 2026-05-26T23:29:31.003Z → 2026-05-26T23:31:07.489Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]
Addresses PR-PLATFORM-3 R4 CONDITIONAL_APPROVE (4 of 5 minor findings): - MINOR: DualSchemaRegistryClient class Javadoc register() bullet rewritten — no more 'A 5xx or IOException IS a registry outage'; now simply 'any error propagates; the fallback is NEVER invoked at this method because it wraps the same underlying registry'. - MINOR: README Public API table — DualSchemaRegistryClient row no longer claims 'on HTTP 5xx / IOException → Confluent fallback'; replaced with 'wire-format coexistence layer; outages propagate (NOT an HA hedge)'. - MINOR: README observability table — removed schema.registry.fallback attribute (never emitted now); added schema.compatibility.applied (introduced in R2/R3) + corrected outcome enum (success | not_found | primary_error, no longer success | error | not_found). - MINOR: schema.registry attribute documented as having two values ('apicurio' on primary spans, 'confluent_wire_compat' on Confluent-mode spans). - MINOR-DEFERRED: confluent-mode-spans-indistinguishable — adding a per-call coordinator span on ConfluentWireCompatSchemaRegistryClient is ~45 LOC of impl change; queued as PR-PLATFORM-3-FOLLOWUP.Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 5 — head
34876e956cec, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 4 findings: 2 agreed-minor stale Javadocs (open since R3), 1 agreed major broken observability contract (open since R4, B's major severity adopted over A's minor), 1 verified unique-to-A minor pom.xml redundancy.
Summary
Arbitration — Round 5
Verification performed on 4 file locations; all 4 findings grounded.
Agreed findings (A ∩ B)
Readconfirmed line 113 still reads/** Apicurio primary, Confluent fallback on 5xx / IOException. */— stale, open since R3. Kept.Readconfirmed lines 24–25 still readcoordinator; primary success → primary; primary 5xx / IOException → fallback— stale, open since R3. Kept.Readconfirmed all three delegation methods are naked pass-throughs toapicurioDelegate.GreponApicurioSchemaRegistryClientconfirmed it hard-codesspan.setAttribute("schema.registry", "apicurio")in every method.Grepon README confirmed line 128 promisesconfluent_wire_compaton Confluent-mode spans — a value never emitted anywhere. A rated minor; B rated major and provided the stronger operational-impact analysis (operator dashboards/alerts silently returning zero results). B's severity adopted per verification of consequence.Unique-to-A (verified, kept)
Readconfirmed bothspring-boot-autoconfigure(lines 45–48) andspring-boot-starter(lines 49–52) are declared at compile scope. A's chain (spring-boot-starter→spring-boot-starter-logging→logback-classicas transitive compile dep) is correct; finding kept.Confirmed fixed this round (not re-raised)
README:43, README:133, SchemaRegistryAutoConfiguration.java:107, DualSchemaRegistryClient.java:100, DualSchemaRegistryClientTest.java:26, ApicurioIT.java:25 — all confirmed fixed by B and not re-raised.
Memora: Created memory ID 288 (create-new path — no prior memory found despite round 5).
Blast Radius
The diff touches 18 files across one submodule, introducing 3 new public classes and modifying 2 shared interfaces plus Spring Boot autoconfiguration. The exported
SchemaRegistryClientinterface andSchemaRegistryAutoConfigurationbean are consumed by every aim2be platform service. The broken observability attribute is a cross-cutting concern that silently affects all Confluent- and dual-mode deployments.BLAST_SCORE: 5/10
Risk Indicators
ApicurioSchemaRegistryClient.register,ConfluentWireCompatSchemaRegistryClient.register,DualSchemaRegistryClient.fetchByGlobalId,SchemaRegistryAutoConfigurationCI status (head
34876e956cec)Overall: ✗ failure
2 checks: 2 pending
Findings (4)
[MINOR] DUAL enum Javadoc still advertises abandoned 5xx-fallback semantics (open since R3)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads
/** Apicurio primary, Confluent fallback on 5xx / IOException. */. Since R2 the dual client deliberately does NOT retry via the fallback on 5xx; outages propagate — both delegates wrap the same underlying registry endpoint. The Javadoc contradicts the actual behaviour documented inDualSchemaRegistryClientand will mislead operators reading IDE hover or generated Javadoc.Required change:
[MINOR] Interface Javadoc for DualSchemaRegistryClient still describes abandoned fallback-on-5xx contract (open since R3)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryClient.java:24
Lines 24–26 read:
The dual client no longer invokes the fallback on any error. The
DualSchemaRegistryClientclass Javadoc (corrected in a prior round) correctly describes wire-format-coexistence semantics, but this interface-level description still contradicts it.Required change:
[MAJOR] Confluent-mode spans always tagged schema.registry=apicurio — confluent_wire_compat attribute is never emitted (open since R4)
apicurio-client/src/main/java/com/aim2be/platform/schema/ConfluentWireCompatSchemaRegistryClient.java:76
All three delegation methods (lines 76–88) are naked pass-throughs to
apicurioDelegate:ApicurioSchemaRegistryClienthard-codesspan.setAttribute("schema.registry", "apicurio")in every method (lines 90, 135, 183 of that file). README Observability table (line 128) promisesconfluent_wire_compaton Confluent-mode spans, but no span emitted inconfluentordualmode ever carries that value. Operators filtering dashboards or alerts onschema.registry=confluent_wire_compatwill silently see zero results; all traffic in either mode looks identical to pure Apicurio traffic.ConfluentWireCompatSchemaRegistryClientTestusesOpenTelemetry.noop()so this is undetected by existing tests.Required fix — wrap each delegate call in a coordinator span that sets the correct attribute (analogous to
DualSchemaRegistryClient):Alternatively, promote
registryNameto a constructor parameter ofApicurioSchemaRegistryClientand supply"confluent_wire_compat"fromConfluentWireCompatSchemaRegistryClient.[MINOR] spring-boot-starter at compile scope is redundant and forces logback onto consumer classpaths
apicurio-client/pom.xml:51
spring-boot-autoconfigure(lines 45–48, already declared) transitively importsspring-boot→spring-context→ all annotation support this module needs. Addingspring-boot-starter(lines 49–52) contributes nothing new to the compile graph but drags inspring-boot-starter-logging→logback-classic, which becomes a compile-scope transitive of every consuming service. Services that manage their own SLF4J binding (log4j2, log4j2-slf4j) will seeSLF4J: Class path contains multiple SLF4J bindingsat startup.Required change: Remove lines 49–52; the existing
spring-boot-autoconfiguredeclaration is sufficient for compile-time support.Verdict
NEEDS_WORK
hib-pr-reviewer • round 5 • 4 findings (1M/3m) • 2026-05-26T23:39:21.876Z → 2026-05-26T23:41:56.077Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 6 — head
5fc4c2423ee0, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — kept 2 agreed findings (both minor, both source-verified), dropped 0; no blocking issues.
Summary
Both reviewers A and B independently flagged identical findings at the same files and lines. Both were confirmed by direct
Readof the files at HEAD.Finding 1 (agreed, verified):
SchemaRegistryProperties.java:113— theDUALenum constant Javadoc still reads/** Apicurio primary, Confluent fallback on 5xx / IOException. */, which has been incorrect since R3 (five rounds open). The actual implementation is a wire-format coexistence layer with no HA semantics; every other doc surface was corrected in prior rounds but this one-liner remains stale.Finding 2 (agreed, verified):
SchemaRegistryAutoConfiguration.java:127—dualSchemaRegistryClient()receives the Spring-managedopenTelemetrybean but constructsConfluentWireCompatSchemaRegistryClientvia the package-private single-arg constructor (ConfluentWireCompatSchemaRegistryClient(primary)). That constructor acquires itsTracerfromGlobalOpenTelemetry.get()(line 85 of the impl). In Spring Boot 3 apps that configure OTel programmatically as a bean without callingGlobalOpenTelemetry.set(), this silently returns a noop tracer, dropping all Confluent coordinator spans while primary spans export correctly.Kept 2 agreed findings, dropped 0. No blocking issues; all findings are minor. Memora persisted as run 6.
Blast Radius
Changes are confined to a single submodule (
apicurio-client). The two open findings touch one autoconfig bean factory method and one enum constant Javadoc — neither alters the public API surface or wire-format behaviour. Impact is limited to observability correctness in dual-mode deployments and IDE documentation accuracy.BLAST_SCORE: 2/10
Risk Indicators
dualSchemaRegistryClient,ConfluentWireCompatSchemaRegistryClientCI status (head
5fc4c2423ee0)Overall: ✗ failure
2 checks: 2 pending
Findings (2)
[MINOR] DUAL enum constant Javadoc still describes abandoned 5xx-fallback semantics (open since R3 — 5 rounds)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads:
Every other doc surface (interface Javadoc, autoconfig Javadoc, README,
DualSchemaRegistryClientclass Javadoc) now correctly describes the dual client as a wire-format coexistence layer with no HA properties. This one-liner remains the sole document that says the opposite, and it is the text displayed by IDE hover-text when an operator inspectsMode.DUAL.Proposed fix:
[MINOR] Dual-mode fallback constructed via package-private ctor — acquires Tracer from GlobalOpenTelemetry instead of the Spring-managed bean
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryAutoConfiguration.java:127
dualSchemaRegistryClient()(line 120) receives the Spring-managedopenTelemetrybean and passes it correctly toprimary(lines 124–125). Howeverfallbackis constructed via the package-private single-arg constructor:That constructor (line 83–85 of
ConfluentWireCompatSchemaRegistryClient.java) acquires its tracer via:Spring Boot 3 apps that configure OTel purely via the Spring bean model (micrometer-tracing bridge,
spring-boot-starter-actuator) do not callGlobalOpenTelemetry.set()by default. In those appsGlobalOpenTelemetry.get()returns a noop, causing all Confluent coordinator spans to be silently discarded while primary spans export correctly — an invisible gap in distributed traces.DualSchemaRegistryClientdoes not delegateregister/fetch*to the fallback, so normal dual-mode operation is unaffected. The issue surfaces only when a caller retrievesfallbackviagetFallback()directly, but the fix is trivial.Proposed fix — option A (use existing public 4-arg constructor, same HTTP pool):
Proposed fix — option B (add a package-private 2-arg ctor preserving the shared
ApicurioSchemaRegistryClientinstance):and update the autoconfig to call
new ConfluentWireCompatSchemaRegistryClient(primary, openTelemetry).Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 6 • 2 findings (2m) • 2026-05-26T23:57:52.702Z → 2026-05-26T23:59:02.476Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 7 — head
b2dadaefbe53, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — 1 agreed finding kept (verified at HEAD, open 5+ rounds); no blocking issues; one-line Javadoc fix required before merge.
Summary
Reconciliation — Round 7
Both reviewer A and reviewer B independently raised identical findings (same file, same line 113, materially-equivalent body) against
SchemaRegistryProperties.java. I verified withReadat HEAD: line 113 reads/** Apicurio primary, Confluent fallback on 5xx / IOException. */exactly as both reviewers cite. Cross-checkingDualSchemaRegistryClient.java(lines 11-31) andSchemaRegistryClient.java(lines 24-32) confirms the corrected semantics are in place everywhere else — both say "NOT an HA hedge" and "outages propagate" — making theDUALconstant the sole remaining inconsistency.Action taken: Kept 1 agreed finding (verified). No unique-to-one findings. Persisted run-7 block to Memora (new entry, ID 290, since no prior run history existed despite round 7).
Blast Radius
The diff touches a single submodule (apicurio-client). The remaining finding is a Javadoc-only inconsistency on a public enum constant; it has zero runtime impact. The broader diff introducing the dual-client abstraction is well-scoped to the apicurio-client module with no cross-module changes.
BLAST_SCORE: 1/10
CI status (head
b2dadaefbe53)Overall: ✗ failure
2 checks: 2 pending
Findings (1)
[MINOR] DUAL enum constant Javadoc still advertises abandoned 5xx-fallback semantics (open since R3 — 5 rounds)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads:
Verified at HEAD. The fallback-on-5xx design was removed in R2. Every other location —
DualSchemaRegistryClientclass Javadoc (lines 11-19),SchemaRegistryClientinterface Javadoc (lines 24-32), README public API table — now documents that both delegates wrap the same Apicurio backend and outages propagate (NOT an HA hedge). This constant alone still carries the abandoned semantics.Replace with:
This is a one-line Javadoc change; no runtime impact.
Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 7 • 1 finding (1m) • 2026-05-27T00:06:15.410Z → 2026-05-27T00:07:24.358Z • posted-as: pr-reviewer-bot
Show previous round
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 8 — head
805cb55cd4f1, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — kept 1 agreed finding (verified), no unique-to-one findings; one minor doc fix is the only remaining blocker.
Summary
Round 8 Arbitration
Reconciliation: Both reviewers A and B submitted identical findings (1 each), converging on the same file, line, and material body. Rule 1 applies — agreed findings are gold.
Verification: Read
SchemaRegistryProperties.javalines 105–116 at HEAD (/var/lib/pr-reviewer/repos/affinity-intelligence-rework/im2be-platform-libs/804/apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java). Line 113 reads exactly:The finding is confirmed. Every sibling artefact (
SchemaRegistryClient.javaKind.DUAL Javadoc,SchemaRegistryAutoConfigurationbean-factory Javadoc,DualSchemaRegistryClientclass Javadoc, README Public API table) was corrected in prior rounds to state wire-format coexistence with no HA fallback. This one constant Javadoc was missed again in round 8.Memora persistence: Attempted create; MCP server returned permission errors on tag validation. Persistence skipped — arbitration verdict is authoritative regardless.
Outcome: 1 agreed finding kept, 0 dropped, 0 unique-to-one. One-line doc fix blocks merge under the project's documentation-consistency bar.
Blast Radius
The diff is confined to a single new submodule (
apicurio-client). The remaining finding is a one-line Javadoc change inside a private enum constant with no runtime surface. Impact is documentation-only and does not propagate to callers.BLAST_SCORE: 1/10
CI status (head
805cb55cd4f1)Overall: ✗ failure
2 checks: 2 pending
Findings (1)
[MINOR] DUAL enum Javadoc still advertises abandoned 5xx-fallback semantics (open since R3 — 6 rounds)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads:
Verified at HEAD. Every sibling artefact has been corrected to describe
DUALas a wire-format coexistence layer with no HA fallback, but this constant's Javadoc was missed in every round R3–R8.Replace with:
Documentation-only change; no runtime behaviour is affected.
Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 8 • 1 finding (1m) • 2026-05-27T00:11:17.868Z → 2026-05-27T00:12:24.757Z • posted-as: pr-reviewer-bot
hib-pr-reviewer review — PR #3 (affinity-intelligence-rework/im2be-platform-libs)
Round 9 — head
56770c04d3ab, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — kept 1 agreed minor finding (DUAL enum Javadoc stale since R3), 0 dropped; no blocking issues remain.
Summary
Arbitration — Round 9
Memora recall: No prior run history found in Memora (tags rejected by allowlist; absorb also denied — persistence skipped). This is treated as a cold start for Memora purposes only; the quorum arbitration proceeds normally.
Reconciliation: Both A and B independently flagged the same finding — identical file (
SchemaRegistryProperties.java), identical line (113), materially-equivalent body. Rule 1 applies: agreed findings are gold. Kept as-is.Verification:
Readat HEAD confirms line 113 reads/** Apicurio primary, Confluent fallback on 5xx / IOException. */withDUALon line 114. The description is factually wrong: the current implementation is a wire-format coexistence layer sharing the same Apicurio backend in all three modes, NOT an HA hedge. Every other doc surface in the PR —DualSchemaRegistryClientclass Javadoc (lines 14–45),SchemaRegistryClientinterface Javadoc,SchemaRegistryAutoConfigurationbean Javadoc, and the README Public API table (line 44) — correctly says "NOT an HA hedge" and "outages propagate." This single constant doc is the last stale copy.Outcome: 1 agreed minor finding kept, 0 dropped. One-line Javadoc edit required to unblock merge.
Blast Radius
The diff touches a single enum constant Javadoc in one file with zero runtime effect. The blast radius is effectively zero — no compiled bytecode changes, no API surface change, and no consumer behaviour is altered.
BLAST_SCORE: 1/10
CI status (head
56770c04d3ab)Overall: ✗ failure
2 checks: 2 pending
Findings (1)
[MINOR] DUAL enum constant Javadoc still advertises abandoned 5xx-fallback semantics (open since R3 — 7 rounds)
apicurio-client/src/main/java/com/aim2be/platform/schema/SchemaRegistryProperties.java:113
Line 113 reads:
Verified by
Readat HEAD — confirmed present. Every other doc surface in this PR correctly describesDUALas a wire-format coexistence layer, NOT an HA hedge (DualSchemaRegistryClientclass Javadoc,SchemaRegistryClientinterface Javadoc,SchemaRegistryAutoConfigurationbean Javadoc, README Public API table). This one constant is the sole surviving instance of the abandoned semantics description.Required change:
No runtime impact; one-line Javadoc edit.
Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 9 • 1 finding (1m) • 2026-05-27T00:16:11.545Z → 2026-05-27T00:17:38.413Z • posted-as: pr-reviewer-bot