feat(ticket): emit ticket-lifecycle events via the Redis outbox (Wave A.2 Phase 1a) #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/redis-outbox-mint"
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?
Wave A.2 Phase 1a — emit ticket-lifecycle events via the Redis outbox
Wires identity-service's Centrifugo ticket mint + the three lifecycle events (
identity.ticket.{minted,expired,revoked}.v1) through the Phase 0bredis-outbox-backend(im2be.outbox.backend=REDIS), so the ticket mint and itsTicketMintedevent are atomic — the mint is itself a Redis op, so a Postgres outbox could not provide mint+event atomicity (ADR-0014 D-3 /docs/decisions/0014-supporting/identity-redis-outbox-backend.md).Mint atomicity (the load-bearing piece)
A single identity-owned Lua
EVAL(mint-and-enqueue.lua) doesSETEX ticket+ outboxHSET/ZADD+:expiry-due ZADD+ expiry-contextSET, fail-closed ordered (refinement #1): outbox writes FIRST, ticketSETEXLAST, so "a ticket without itsTicketMintedevent" is structurally impossible (an orphan event is harmless — consumer-deduped). TYPE-precheck all keys +HEXISTSidempotency mirror the platform-libsoutbox-enqueue.lua. Ticket + expiry-context TTLs usePX(millis) for sub-second precision (and to avoid aDuration.toSeconds()→0truncation footgun).Events
TicketMinted/TicketRevoked= UUIDv7;TicketExpired= deterministicUUIDv3(ticket_id|expires_at)— also the:expiry-duemember, so a double-fire collapses on the same id (exactly-onceTicketExpired).event_idis in lockstep with the outbox row's.@Scheduleddue-time worker (TicketExpiryWorker) drivesfindDueExpiries→decideExpiry, suppressing already-revoked tickets via a Redis-resident revocation marker written atomically in the revokeEVAL(narrows the Phase 0b expiry TOCTOU now that the caller exists). An expiry-context companion key carries(ticket_id, user_id, expires_at, trace_id)so the worker can buildTicketExpiredafter the ticket key has TTL-evicted. The Validate (GETDEL) path writes a consumed-marker so theconsumedfield is accurate.Error handling (rule 2)
The mint catch distinguishes
RedisConnectionFailureException(→TicketMintFailedException, 503, retryable — genuine outage) fromRedisSystemException(Luaerror_reply/WRONGTYPE/bad-arg →IllegalStateException, 500, not retryable — a contract bug, no longer masked as an outage).Hash-tag re-tag DEFERRED (operator-confirmed)
The ticket key is not co-slotted with
{identity:outbox}. The 2026-05-28 audit confirmed standalone Valkey (cluster_enabled:0), where a multi-keyEVALis atomic regardless of slots — so the composed mint works today without changingTICKET_KEY_PREFIX(shared with realtime-service's Validate path).mint-and-enqueue.luadocuments the cluster-readiness TODO + the CROSSSLOT caveat.Dependency
redis-outbox-backend 1.1.0-SNAPSHOT; the transitivespring-boot-starter-data-jpais excluded (Redis-only service, no DataSource), and the PGOutboxAutoConfigurationis gated off forbackend=REDISby the B2 fix (platform-libs PR #9,b594a6c).Verification
mvn clean verify→ BUILD SUCCESS, 0[WARNING].Note: the Forgejo
mvn installCI check is red on the platform repos for all commits (pre-existing runner-toolchain bug, PR-PLATFORM-CI-1) and identity depends on a not-yet-registry-published1.1.0-SNAPSHOT; verified locally green.Wire identity-service's Centrifugo ticket mint + the three lifecycle events (identity.ticket.{minted,expired,revoked}.v1) through the Phase 0b redis-outbox-backend (im2be.outbox.backend=REDIS), so the ticket mint and its TicketMinted event are ATOMIC — the mint is itself a Redis op, so a Postgres outbox could not give mint+event atomicity (ADR-0014 D-3 / 0014-supporting). Mint atomicity (the load-bearing piece): a single identity-owned Lua EVAL (mint-and-enqueue.lua) does SETEX ticket + outbox HSET/ZADD + expiry-due ZADD + expiry-context SET, fail-closed ordered (refinement #1) — outbox writes FIRST, ticket SETEX LAST, so "a ticket without its TicketMinted event" is structurally impossible (an orphan event is harmless, consumer-deduped). TYPE-precheck all keys + HEXISTS idempotency mirror the platform-libs outbox-enqueue.lua. Ticket + expiry-context TTLs use PX (millis) for sub-second precision. Events: TicketMinted/TicketRevoked = UUIDv7; TicketExpired = deterministic UUIDv3(ticket_id|expires_at) — also the :expiry-due member, so a double-fire collapses on the same id. Pointer-based payloads (no inline PII, ADR-0002 §3b); event_id is in lockstep between the outbox row and the payload. A @Scheduled due-time worker (TicketExpiryWorker) drives findDueExpiries→decideExpiry, suppressing already-revoked tickets via a Redis-resident revocation marker written atomically in the revoke EVAL (narrows the Phase 0b expiry TOCTOU). The expiry-context companion key carries (ticket_id,user_id,expires_at,trace_id) so the worker can build TicketExpired after the ticket key has TTL-evicted. The Validate (GETDEL) path writes a consumed-marker so the consumed field is accurate. Error handling (rule 2): the mint catch distinguishes RedisConnectionFailureException (→ TicketMintFailedException, 503, retryable — genuine outage) from RedisSystemException (Lua error_reply / WRONGTYPE / bad-arg → IllegalStateException, 500, NOT retryable — a contract bug, not masked as an outage). Hash-tag re-tag DEFERRED (operator-confirmed): the ticket key is NOT co-slotted with {identity:outbox}; the 2026-05-28 audit confirmed standalone Valkey (cluster_enabled:0) where a multi-key EVAL is atomic regardless of slots, so the composed mint works today without changing TICKET_KEY_PREFIX (shared with realtime-service's Validate path). mint-and-enqueue.lua documents the cluster-readiness TODO + the CROSSSLOT caveat. Dependency: redis-outbox-backend 1.1.0-SNAPSHOT; the transitive spring-boot-starter-data-jpa is excluded (Redis-only service, no DataSource) and the PG OutboxAutoConfiguration is now gated off for backend=REDIS by the B2 fix (platform-libs b594a6c). Tests: 141 unit + 13 Testcontainers-Valkey IT. ITs cover atomic mint+enqueue, fail-closed WRONGTYPE-abort-keeps-no-ticket, UUID lockstep, hot-relay→Kafka, expired-enqueue, already-revoked suppression, UUIDv3 double-fire collapse, consumed-flag, not-due no-op, revoke DEL+marker+enqueue. mvn clean verify → BUILD SUCCESS, 0 [WARNING].Show previous round
hib-pr-reviewer review — PR #5 (affinity-intelligence-rework/im2be-identity-service)
Round 1 — head
506435070dfc, basemain, triggeropenedTL;DR: CONDITIONAL_APPROVE — 2 minor + 2 info findings kept this round.
Summary
Arbiter reconciled 3 (A) + 0 (B) → 4 findings.
CI status (head
506435070dfc)No CI checks reported for this commit.
Findings (4)
[MINOR]
releaseLockcompare-and-delete is non-atomic (TOCTOU race)src/main/java/com/aim2be/identity/ticket/event/TicketExpiryWorker.java:363
Verified at lines 363–374. The comment at line 364 promises "Compare-and-delete so a tick that overran its lock TTL does not delete the new holder's lock" — but the implementation uses two separate Redis round-trips:
Race window: if the lock TTL expires between step 1 and step 2, Replica B acquires the lock with a new owner UUID, and Replica A's
delete()removes Replica B's lock. Result: two replicas sweep concurrently. UUIDv3 dedup preserves event-correctness, but the single-writer guarantee and the stated goal of avoiding wasted work and log noise are defeated.Fix: Atomise with a short inline Lua EVAL (mirrors the mint/revoke scripts already in this PR):
Alternatively:
redis.execute(releaseScript, List.of(SWEEP_LOCK_KEY), owner)via aRedisScript<Long>field.[MINOR] Bare
new ObjectMapper()diverges from the application mapper and is a latent serialisation hazardsrc/main/java/com/aim2be/identity/ticket/event/TicketLifecyclePayload.java:60
Verified at line 60:
This mapper has no
JavaTimeModule, nofindAndRegisterModules(), and none of the custom serialisers registered on the Spring-managedObjectMapper. For the current payload fields (UUID,Long,Boolean,String) Jackson's defaults happen to produce correct JSON. The latent hazard: any future addition ofInstant,LocalDate,OffsetDateTime, or a Jackson-annotated enum will silently produce wrong output — e.g.,Instantserialises as a[seconds, nanos]array instead of the epoch-mslongthe schema declares.Fix: Pass the application
ObjectMapperas a parameter rather than relying on a bare static instance:Callers (
TicketEventEmitter.serialisePayload,TicketExpiryWorker.serialise) already hold the wiredobjectMapper— the change is mechanical and eliminates the divergence.[INFO] Lua variable
ticket_valuecarries the user-context JSON, not the base64url ticket credentialsrc/main/resources/META-INF/scripts/mint-and-enqueue.lua:95
Verified: Line 69 of the script comment reads
ARGV[4] = ticket_value (the user-context JSON blob stored under the ticket key). Line 95 assigns it:Line 152 stores it:
redis.call('SET', ticket_key, ticket_value, 'PX', ticket_ttl_ms)— confirmingticket_valueholds the JSON blob that is the value of the Redis key, not the base64url bearer credential (which is embedded inKEYS[1] = centrifugo:ticket:<base64url>).The name collides with the natural reading of "the value of the ticket" (i.e., the credential). A future Lua editor could mistake ARGV[4] for the bearer credential.
Suggested rename:
user_context_jsonorticket_ctx_json, matching the Java call-site parameter nameuserContextJson.[INFO]
TicketEventEmitteris a bare@Componentwith no@ConditionalOnPropertyguard despite injecting conditional-only beanssrc/main/java/com/aim2be/identity/ticket/event/TicketEventEmitter.java:24
Verified:
TicketEventEmitteris annotated@Component(line 24, no conditional). It injectsRedisOutboxKeys,RedisOutboxRelay, andTicketOutboxScripts— beans that exist only whenim2be.outbox.backend=REDIS(as indicated by@ConditionalOnPropertyonTicketExpiryWorkerat line 71 of that file and byRedisOutboxAutoConfiguration's own conditional).In a deployment where
im2be.outbox.backendis absent or set to a different value,TicketEventEmitterwill fail with a crypticNoSuchBeanDefinitionExceptionat startup rather than a clear validation error.TicketExpiryWorkeralready has the correct guard;TicketEventEmittershould match it:Alternatively, pair this with a
@ConfigurationPropertiesvalidator that rejects unknown backend values at startup.Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 1 • 4 findings (2m/2i) • 2026-05-28T13:52:45.106Z → 2026-05-28T13:56:41.449Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]
R1 verdict CONDITIONAL_APPROVE, findings (kept=4: 2 MINOR / 2 INFO): (1) MINOR TicketExpiryWorker.releaseLock — non-atomic compare-and-delete (get-then-delete TOCTOU): if the sweep-lock TTL lapsed between the GET and the DEL, this replica could delete a DIFFERENT replica's re-acquired lock → two concurrent sweeps (UUIDv3 keeps events correct, but the single-writer guarantee is defeated). (2) MINOR TicketLifecyclePayload — bare new ObjectMapper() (no JavaTimeModule / app serialisers): a latent hazard for any future Instant/date field. (3) INFO mint-and-enqueue.lua ARGV[4] `ticket_value` mis-named (it is the user-context JSON = the key's VALUE, not the base64url credential in KEYS[1]). (4) INFO TicketEventEmitter bare @Component injecting REDIS-only beans → cryptic NoSuchBeanDefinitionException on a non-REDIS profile. Fix: - (1) new identity-owned release-sweep-lock.lua (owner-token compare-and-delete in one EVAL), loaded via TicketOutboxScripts.releaseSweepLock(); releaseLock now runs that EVAL — the GET→DEL race window is closed, single-writer holds. - (2) dropped the static mapper; toJsonBytes(ObjectMapper) takes it as a param; all 3 call sites (TicketEventEmitter, TicketExpiryWorker, the test) pass the Spring-managed mapper they already hold. - (3) renamed ARGV[4] → user_context_json (+ comment block + the SET); confirmed revoke-and-enqueue.lua has no analogous mis-naming. - (4) @ConditionalOnProperty(im2be.outbox.backend=REDIS) on TicketEventEmitter, matching TicketExpiryWorker. Ripple: its sole consumer CentrifugoTokenController is gated on the same property so the mint slice (controller + emitter) wires or omits as ONE unit — a non-REDIS profile cleanly omits the mint endpoint instead of a cryptic bean error. This is also the correct fail-safe: per ADR-0014 D-12 the ticket-lifecycle events are locked at-least-once-via-outbox, so identity must NOT mint a ticket it cannot emit the audit/billing/parental-control event for; identity's only valid config is backend=REDIS. Verification: - mvn -B clean verify → BUILD SUCCESS, 0 [WARNING] - 141 unit + 13 Testcontainers-Valkey IT green (TicketExpiryWorkerIT exercises the atomic releaseLock every sweep).Show previous round
hib-pr-reviewer review — PR #5 (affinity-intelligence-rework/im2be-identity-service)
Round 2 — head
1ff8e6ef8d43, basemain, triggersynchronizeTL;DR: CONDITIONAL_APPROVE — 2 minor + 2 info findings kept this round.
Summary
Arbiter reconciled 3 (A) + 1 (B) → 4 findings.
CI status (head
1ff8e6ef8d43)No CI checks reported for this commit.
Findings (4)
[MINOR]
relay.relayAsync()inside the broadRuntimeExceptioncatch — a post-EVAL relay-submit failure mis-reports a committed mint as failedsrc/main/java/com/aim2be/identity/ticket/event/TicketEventEmitter.java:223
After
evalMint()at line 210 completes the ticket SETEX + outbox HSET/ZADD are durably committed in Redis.relay.relayAsync()at lines 223-224 then submits the hot-relay task. If that submission throws (e.g.RejectedExecutionExceptionfrom a saturated pool) it propagates tocatch (RuntimeException ex)at line 264, which recordsOUTCOME_FAILEDand re-throws — the controller returns 500 and the client never receives the ticket value.The impact: a live ticket sits in Redis with its
TicketMintedevent in the outbox; the cold poller eventually delivers the event to Kafka, producing a phantomTicketMintedthat no client holds. The comment at line 221 already calls this path "fire-and-forget" and explicitly states the cold poller covers relay failures — the exception handler contradicts that contract.Suggested fix: wrap the relay call in its own guarded block:
This matches the stated fire-and-forget contract and leaves the cold poller as the documented fallback.
[MINOR] IT fixture wires a bare
new ObjectMapper()— diverges from the application mappersrc/test/java/com/aim2be/identity/integration/ticket/AbstractTicketOutboxIT.java:93
objectMapper = new ObjectMapper()at line 93 creates a library-default mapper with noJavaTimeModule,Jdk8Module, or application serializers. Lines 108-110 wire this mapper intoTicketEventEmitterandTicketExpiryWorker. At runtime Spring injects the auto-configuredObjectMapperthat carries those modules.Current
TicketLifecyclePayloadandTicketExpiryContextfields are UUID/long/String/boolean and round-trip identically under both mappers — so no tests fail today. However adding anyInstantorLocalDatefield to either payload record would silently produce[year,month,day]arrays in the ITs vs ISO-8601 strings in production, the exact scenario the R2 production fix documented.Suggested fix:
This matches the auto-configured mapper's module set without pulling in the Spring context.
[INFO]
markRevoked()Javadoc implies a pre-enqueue two-step call pattern — stale; method has no production callersrc/main/java/com/aim2be/identity/ticket/event/TicketRevocationMarker.java:90
Lines 88-91 read: 'Called by the revoke path BEFORE the
TicketRevokedenqueue so a concurrent expiry decision observes the revocation.' In practicemarkRevoked()is never called from any production code path — grep confirms it appears only inTicketRevocationMarkerTestandTicketExpiryWorkerIT(for test setup). Production revocation goes throughTicketEventEmitter.revokeAndEnqueue()which writes the marker atomically insiderevoke-and-enqueue.lua.A future developer reading the Javadoc may call
markRevoked()separately beforerevokeAndEnqueue(), producing a redundant non-atomic pre-write that bypasses the Lua guarantee.Suggested Javadoc update: 'For test setup only. In production the revocation marker is written atomically inside
revoke-and-enqueue.luaviaTicketEventEmitter.revokeAndEnqueue(); calling this method independently bypasses the atomic Lua guarantee.'Deferred: the revoke endpoint is out of scope for Phase 1a; Javadoc cleanup belongs to the Wave that adds the revoke RPC.
[INFO]
TicketRevocationMarkeris a bare@Componentwhile all its REDIS-outbox callers carry@ConditionalOnProperty(REDIS)src/main/java/com/aim2be/identity/ticket/event/TicketRevocationMarker.java:42
Line 42 is
@Componentonly. Both production callers in this PR —TicketExpiryWorkerandTicketEventEmitter— carry@ConditionalOnProperty(prefix="im2be.outbox", name="backend", havingValue="REDIS"). In a non-REDIS configuration the bean is still instantiated (injectingStringRedisTemplate) while neither caller is present — an orphaned, unreachable bean.Currently harmless (the service always runs REDIS per
application.properties). The concern is forward-looking: if the bean acquires a@PostConstructside-effect or Redis-Cluster-aware init logic, the missing guard would activate it in configurations that have no use for it.Suggested fix:
(Note:
TicketConsumedMarkeris intentionally unconditional — consumed by the unconditionalConnectProxyServiceImplvalidate path — and should remain ungated.)Deferred: harmless in current always-REDIS topology; worth a follow-on ticket rather than blocking this PR.
Verdict
CONDITIONAL_APPROVE
hib-pr-reviewer • round 2 • 4 findings (2m/2i) • 2026-05-28T14:13:28.193Z → 2026-05-28T14:15:20.847Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]
R2 verdict CONDITIONAL_APPROVE, findings (kept=4: 2 MINOR / 2 INFO): (1) MINOR (real correctness bug) TicketEventEmitter — the relayAsync() submit sat inside the broad catch(RuntimeException). The mint is durably committed BEFORE the relay submit; a submit throw (e.g. RejectedExecutionException on a saturated pool) was recorded OUTCOME_FAILED + re-thrown → controller 500 → client got no ticket while a live ticket + PENDING TicketMinted sat in Redis (phantom event + client retry → second ticket). Contradicts the documented fire-and-forget contract. (2) MINOR AbstractTicketOutboxIT bare new ObjectMapper() — diverges from the app mapper (no module discovery). (3) INFO TicketRevocationMarker.markRevoked() stale Javadoc (implied a two-step pre-enqueue pattern; it is test-only — prod writes the marker atomically in revoke-and-enqueue.lua). (4) INFO TicketRevocationMarker bare @Component while both callers are REDIS-gated → orphaned bean in a non-REDIS config. Fix: - (1) wrapped ONLY the relayAsync() submit in its own try/catch that WARN-logs (ticket id hashed; raw credential/user-context never logged) + swallows — the mint is committed and the cold poller is the documented fallback. A post-commit relay-submit failure is NOT a mint failure, so it no longer records OUTCOME_FAILED; added a distinct OUTCOME_RELAY_DEGRADED label on identity_ticket_lifecycle_events_total so on-call reads it as relay-degraded, not a lost mint. RIPPLE (rule 63): the revoke path had the identical post-commit-submit-in-broad-catch bug — fixed both. New regression IT relaySubmitFailureAfterCommitDoesNotFailTheMint (injects a relay throwing RejectedExecutionException; asserts the mint does NOT throw + the ticket + PENDING TicketMinted are committed — rule 18). - (2) IT fixture now new ObjectMapper().findAndRegisterModules() (app-equivalent). - (3) Javadoc → "For test setup only; prod writes atomically via revoke-and-enqueue.lua; calling independently bypasses the Lua guarantee." - (4) @ConditionalOnProperty(im2be.outbox.backend=REDIS) on TicketRevocationMarker. Verification: - mvn -B clean verify → BUILD SUCCESS, 0 [WARNING] - 141 unit + 14 IT green (TicketEventEmitterMintIT 5→6).Show previous round
hib-pr-reviewer review — PR #5 (affinity-intelligence-rework/im2be-identity-service)
Round 3 — head
ff55b65ae734, basemain, triggersynchronizeTL;DR: NEEDS_WORK — kept 1 agreed major finding (pom.xml SNAPSHOT, no remote repo entry confirmed), dropped 1 unique-to-A finding (TicketConsumedMarker @Nullable premise disproved: class is plain @Component, not @ConditionalOnProperty).
Summary
Arbitration — Round 3
What I verified
Finding 1 — pom.xml SNAPSHOT (agreed A + B): Read pom.xml lines 85-109. The
<platform-libs.version>1.1.0-SNAPSHOT</platform-libs.version>tag is at line 99 (Reviewer A correct; Reviewer B cited line 90, the start of the enclosing comment block). Critically, a fullgrepforrepositoriesanddistributionManagementin pom.xml returned no matches — confirming B's central claim that Maven has no remote repository entry to resolve this SNAPSHOT coordinate, so any clean CI agent will fail. Finding kept; severity elevated to major (B's assessment is grounded: CI build failure is a blocking consequence, not merely a portability concern).Finding 2 —
ConnectProxyServiceImplmissing@Nullable(unique to A): Reviewer A's premise is thatTicketConsumedMarkercarries@ConditionalOnProperty(havingValue="REDIS"), making it an absent bean on non-REDIS deployments. ReadingTicketConsumedMarker.javalines 1-60 directly disproves this: the class is annotated only with@Component(line 32), no@ConditionalOnProperty. It is registered unconditionally whenever Spring boots. TheNoSuchBeanDefinitionExceptionscenario Reviewer A described cannot occur from a missing conditional property, so the@Nullableprescription is unwarranted. Reviewer A appears to have conflated this class withTicketRevocationMarker(which does carry the REDIS guard at line 50). Finding dropped.Outcome
Kept 1 agreed finding (severity raised to major per verification); dropped 1 unique-to-A finding (premise disproved by source read).
Blast Radius
The SNAPSHOT dependency affects all build environments and every downstream consumer of this service's artifacts. The lack of a remote repository entry means the failure is systemic across any CI agent or developer machine that has not manually pre-installed the SNAPSHOT. The remaining code changes are well-scoped to the new ticket-lifecycle outbox subsystem.
BLAST_SCORE: 6/10
Risk Indicators
TicketConsumedMarker.markConsumed,ConnectProxyServiceImpl.doValidate,CentrifugoTokenControllerCI status (head
ff55b65ae734)No CI checks reported for this commit.
Findings (1)
[MAJOR]
platform-libs.version=1.1.0-SNAPSHOTis a mutable SNAPSHOT with no remote repository — clean CI builds will failpom.xml:99
<platform-libs.version>1.1.0-SNAPSHOT</platform-libs.version>(line 99) introduces a mutable SNAPSHOT coordinate into the production POM. A full search of the file confirms there is no<repositories>or<distributionManagement>block; Maven cannot resolve this coordinate from any remote location beyond the local~/.m2cache.Consequences:
~/.m2layer) will fail withCould not resolve … redis-outbox-backend:1.1.0-SNAPSHOT.Concrete fix (pick one):
redis-outbox-backendto a release coordinate (e.g.1.1.0) and publish to the team's shared Nexus/Artifactory; update the version property here.<repositories>stanza pointing at the internal snapshot repo so CI can resolve without pre-installation, and document the prerequisite explicitly in the README.platform-libsin as a Maven multi-module reactor dependency so it builds in the samemvn installinvocation.Verdict
NEEDS_WORK
hib-pr-reviewer • round 3 • 1 finding (1M) • 2026-05-28T14:30:42.817Z → 2026-05-28T14:32:11.389Z • posted-as: pr-reviewer-bot
R3 verdict NEEDS_WORK, 1 MAJOR: (1) MAJOR pom.xml — platform-libs 1.1.0-SNAPSHOT had no remote repository, so a clean/ephemeral build (no pre-baked ~/.m2) could not resolve the coordinate. Fix (operator-chosen "build the Maven registry first" over the Option-B local-install precedent): platform-libs is now published to the Forgejo Maven package registry (PR-PLATFORM-CI-1, platform-libs PR #10); identity adds a matching <repositories id="forgejo-air"> so the SNAPSHOT resolves remotely. The stale "installed in the local ~/.m2" comment is corrected. Verified: a FRESH empty local repo resolved com.aim2be:redis-outbox-backend:1.1.0-SNAPSHOT from the registry (BUILD SUCCESS); registry read is anonymous/public (HTTP 200, no token) so a clean CI resolves with zero creds. mvn clean verify → BUILD SUCCESS, 0 [WARNING], 141 unit + 14 IT.hib-pr-reviewer review — PR #5 (affinity-intelligence-rework/im2be-identity-service)
Round 4 — head
4c7ea60775e7, basemain, triggersynchronizeTL;DR: NO_NEW_FINDINGS — No new findings this round.
Summary
[quorum-converged] A=0 = B=0. # hib-pr-reviewer/quorum/A — round 4
Reviewing PR #5 in
affinity-intelligence-rework/im2be-identity-service(head
4c7ea60, basemain).CI status (head
4c7ea60775e7)No CI checks reported for this commit.
Findings
No new findings this round.
Quorum converged on empty findings (A + B both returned 0).
Verdict
NO_NEW_FINDINGS
hib-pr-reviewer • round 4 • 0 findings • 2026-05-28T15:17:17.754Z → 2026-05-28T15:24:31.359Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]