feat(outbox): PR-PLATFORM-1-FOLLOWUP-EVENTID — Function<UUID,byte[]> publish overload for event_id lockstep #6

Merged
hibryda merged 6 commits from feat/platform-1-followup-eventid-overload into main 2026-05-28 08:47:44 +02:00
Owner

Project: affinity-intelligence-rework/im2be-platform-libsBranch: feat/platform-1-followup-eventid-overloadmainPR-PLATFORM-1-FOLLOWUP-EVENTID2026-05-28

TL;DR

APPROVE candidate — adds the OutboxPublisher.publish(String, Function<UUID,byte[]>, int, UUID) deferred-serialisation overload so the outbox row's event_id and the payload's embedded event_id stay in lockstep (fixes Memora #3555 / the R1 finding on social-login PR #10). Implements the independently-reviewed design (Memora #3556, APPROVE_WITH_REFINEMENTS @ 88%). All 6 hardening refinements applied. 80 unit + 4 IT tests pass, zero warnings.

Summary

The existing publish(String, byte[], int, UUID) generates the outbox-row event_id internally — callers can't make their payload's embedded event_id match, so Kafka↔outbox correlation is broken (operators can't trace a Kafka message back to its outbox row for retry/DLQ). The AFTER_COMMIT relay closes over the original byte[], so post-publish UPDATEs don't help; all caller-side workarounds are blocked (Memora #3555).

The new overload hands the outbox-issued UUIDv7 to a Function<UUID, byte[]> serialiser, which embeds it in the payload before the library captures the bytes. Both row + payload then agree.

2 commits: (1) production — overload + dual defensive-clone + @Deprecated byte[] overload + 6 Javadoc contract claims + version bump 1.0.0→1.1.0-SNAPSHOT; (2) tests — 12 unit + 1 IT.

Design note: extracted a shared private persistAndRegisterRelay(...) helper so both overloads delegate after their defensive clone — eliminates flow divergence (improvement over the review's verbatim "duplicate the flow" suggestion).

Findings

R0 (initial open). Pre-emptive observations:

Severity File:Line Title Body
INFO OutboxPublisher.java byte[] overload deprecated, NOT removed @Deprecated(since="1.1", forRemoval=false). Retained for opaque already-encoded payloads with no event_id field. One production caller (social-login OAuthUserInfoProvider) migrates in its R3; library-internal byte[]-overload tests carry method-scoped @SuppressWarnings("deprecation") with justification.
INFO pom.xml + 5 modules version 1.1.0-SNAPSHOT GA release tag is a post-merge operator step — run the full 8-service build under the new @Deprecated + rule-62 (zero-warnings) before tagging, to confirm no dependent service's build breaks on a deprecation warning.
INFO OutboxPublisher.java both overloads now defensively clone rule-63 ripple — the byte[] overload also gained Arrays.copyOf so neither path is vulnerable to caller buffer mutation. T12 regression-proves it.

Verdict

APPROVE candidate. Implements the reviewed design in full; no deviations except the persistAndRegisterRelay DRY helper (strict improvement). The IT lockstep test is the load-bearing safety net.

Verification

JAVA_HOME=$HOME/.local/share/mise/installs/java/temurin-17 mvn -B -pl outbox-publisher -am clean install
# -> BUILD SUCCESS; OutboxPublisherTest 26 tests; OutboxPublisherIT 4 tests; suite 80+4; 0 [WARNING] lines

Test matrix (per Memora #3556 §4): T1 outside-TX-guard-before-fn / T2 row+eventId lockstep / T3 invoked-once / T4 fn-before-save (InOrder) / T5 throw-propagates / T6 null→NPE / T7 empty→IAE / T8 UUIDv7 / T9 return==fn-arg / T10 row==return / T11 AFTER_COMMIT bytes + defensive-clone / T12 byte[]-overload clone regression / IT publishFunctionalLockstepEventIdMatchesPayloadField (end-to-end payload.event_id == row.event_id == return, real PG + EmbeddedKafka).

  • Memora #3555 — the OutboxPublisher API gap (event_id divergence)
  • Memora #3556 — the independent design review (APPROVE_WITH_REFINEMENTS, 88%); Codex MCP was unavailable so pr-review-toolkit:code-reviewer substituted
  • ADR-0014 D-9 — hot-relay + cold-poller semantics (unchanged)
  • Unblocks: social-login PR #10 (R3 event_id migration) + diary PR #6 (HaloService.java:142 same pattern)
  • Follow-up (separate PR): ArchUnit @Transactional-must-be-public rule (Memora #3553 — fired twice in Wave A)

Footer: im2be-platform-libs • PR-PLATFORM-1-FOLLOWUP-EVENTID • R0 (initial) • tally 2 commits / +521 lines / 13 new tests / 0 warnings / v1.1.0-SNAPSHOT • 2026-05-28

> **Project:** `affinity-intelligence-rework/im2be-platform-libs` • **Branch:** `feat/platform-1-followup-eventid-overload` → `main` • **PR-PLATFORM-1-FOLLOWUP-EVENTID** • **2026-05-28** ## TL;DR `APPROVE` candidate — adds the `OutboxPublisher.publish(String, Function<UUID,byte[]>, int, UUID)` deferred-serialisation overload so the outbox row's `event_id` and the payload's embedded `event_id` stay in lockstep (fixes Memora #3555 / the R1 finding on social-login PR #10). Implements the independently-reviewed design (Memora #3556, APPROVE_WITH_REFINEMENTS @ 88%). All 6 hardening refinements applied. 80 unit + 4 IT tests pass, zero warnings. ## Summary The existing `publish(String, byte[], int, UUID)` generates the outbox-row `event_id` internally — callers can't make their payload's embedded `event_id` match, so Kafka↔outbox correlation is broken (operators can't trace a Kafka message back to its outbox row for retry/DLQ). The AFTER_COMMIT relay closes over the original byte[], so post-publish UPDATEs don't help; all caller-side workarounds are blocked (Memora #3555). The new overload hands the outbox-issued UUIDv7 to a `Function<UUID, byte[]>` serialiser, which embeds it in the payload before the library captures the bytes. Both row + payload then agree. **2 commits**: (1) production — overload + dual defensive-clone + `@Deprecated` byte[] overload + 6 Javadoc contract claims + version bump 1.0.0→1.1.0-SNAPSHOT; (2) tests — 12 unit + 1 IT. Design note: extracted a shared private `persistAndRegisterRelay(...)` helper so both overloads delegate after their defensive clone — eliminates flow divergence (improvement over the review's verbatim "duplicate the flow" suggestion). ## Findings R0 (initial open). Pre-emptive observations: | Severity | File:Line | Title | Body | |---|---|---|---| | **INFO** | `OutboxPublisher.java` | byte[] overload deprecated, NOT removed | `@Deprecated(since="1.1", forRemoval=false)`. Retained for opaque already-encoded payloads with no event_id field. One production caller (social-login `OAuthUserInfoProvider`) migrates in its R3; library-internal byte[]-overload tests carry method-scoped `@SuppressWarnings("deprecation")` with justification. | | **INFO** | `pom.xml` + 5 modules | version 1.1.0-SNAPSHOT | GA release tag is a post-merge operator step — run the full 8-service build under the new `@Deprecated` + rule-62 (zero-warnings) before tagging, to confirm no dependent service's build breaks on a deprecation warning. | | **INFO** | `OutboxPublisher.java` | both overloads now defensively clone | rule-63 ripple — the byte[] overload also gained `Arrays.copyOf` so neither path is vulnerable to caller buffer mutation. T12 regression-proves it. | ## Verdict `APPROVE` candidate. Implements the reviewed design in full; no deviations except the `persistAndRegisterRelay` DRY helper (strict improvement). The IT lockstep test is the load-bearing safety net. ## Verification ```bash JAVA_HOME=$HOME/.local/share/mise/installs/java/temurin-17 mvn -B -pl outbox-publisher -am clean install # -> BUILD SUCCESS; OutboxPublisherTest 26 tests; OutboxPublisherIT 4 tests; suite 80+4; 0 [WARNING] lines ``` Test matrix (per Memora #3556 §4): T1 outside-TX-guard-before-fn / T2 row+eventId lockstep / T3 invoked-once / T4 fn-before-save (InOrder) / T5 throw-propagates / T6 null→NPE / T7 empty→IAE / T8 UUIDv7 / T9 return==fn-arg / T10 row==return / T11 AFTER_COMMIT bytes + defensive-clone / T12 byte[]-overload clone regression / IT `publishFunctionalLockstepEventIdMatchesPayloadField` (end-to-end payload.event_id == row.event_id == return, real PG + EmbeddedKafka). ## Related - **Memora #3555** — the OutboxPublisher API gap (event_id divergence) - **Memora #3556** — the independent design review (APPROVE_WITH_REFINEMENTS, 88%); Codex MCP was unavailable so `pr-review-toolkit:code-reviewer` substituted - **ADR-0014 D-9** — hot-relay + cold-poller semantics (unchanged) - Unblocks: social-login PR #10 (R3 event_id migration) + diary PR #6 (`HaloService.java:142` same pattern) - Follow-up (separate PR): ArchUnit `@Transactional`-must-be-public rule (Memora #3553 — fired twice in Wave A) --- **Footer:** im2be-platform-libs • PR-PLATFORM-1-FOLLOWUP-EVENTID • R0 (initial) • tally `2 commits / +521 lines / 13 new tests / 0 warnings / v1.1.0-SNAPSHOT` • 2026-05-28
Adds a deferred-serialisation publish overload so the outbox row's
event_id (UUIDv7 issued by the library) and the payload's embedded
event_id stay in lockstep — fixes the operator-correlation gap from
Memora #3555 + the R1 finding on social-login PR #10. Implements the
independently-reviewed design (Memora #3556, APPROVE_WITH_REFINEMENTS).

New API:
  UUID publish(String topic, Function<UUID,byte[]> payloadFn,
               int schemaVersion, UUID aggregateId)
payloadFn receives the outbox-issued eventId and returns the encoded
payload with that id embedded, so caller + row agree on event_id.

Hardening refinements (all from the design review):
- Defensive Arrays.copyOf of the payload on BOTH overloads (rule-63
  ripple). The AFTER_COMMIT closure captures the byte[] by reference;
  the clone ensures a caller mutating its source buffer between
  publish-return and commit cannot change what is persisted/published.
  The cold poller is unaffected (reads from the bytea column).
- Shared private persistAndRegisterRelay(...) helper — both overloads
  delegate to it after their respective defensive clone, eliminating
  flow divergence between the two paths.
- @Deprecated(since="1.1", forRemoval=false) on the byte[] overload
  with Javadoc pointing at the new overload + Memora #3555. Retained
  (not removed) for callers serialising opaque already-encoded bytes
  with no event_id field.
- Six explicit Javadoc contract claims on the new overload: invocation
  timing (synchronous, before persist), invocation count (exactly once),
  lockstep invariant (MUST embed eventId), thread-safety (calling thread
  only), purity (no I/O / no REQUIRES_NEW writes), exception semantics
  (propagates unmodified, rolls back per caller rollbackFor).

Version: 1.0.0-SNAPSHOT → 1.1.0-SNAPSHOT across all 5 modules + parent
(matches @Deprecated since=1.1). GA release tag is a post-merge operator
step (run the full 8-service build before tagging).

Critical contracts a future maintainer MUST preserve (also in Javadoc):
1. payloadFn MUST embed the supplied eventId — else this overload has
   no advantage over byte[].
2. payloadFn invoked exactly once, synchronously, BEFORE persist — never
   retried, never from AFTER_COMMIT, never on a Kafka client thread.
3. The defensive byte[] clone must NOT be optimised away.
4. The active-TX check runs BEFORE payloadFn — no side effects outside
   an active transaction.

Refs: ADR-0014 D-9 (hot-relay + poller, unchanged), Memora #3555
(the API gap), Memora #3556 (design review).
test(outbox): PR-PLATFORM-1-FOLLOWUP-EVENTID — 12 unit + 1 IT lockstep coverage for the Function overload
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 2s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
ec15a216ba
Covers the new publish(String, Function<UUID,byte[]>, int, UUID) overload
per the design review's test matrix (Memora #3556 §4). 12 new unit tests
(OutboxPublisherTest 14 → 26) + 1 IT (OutboxPublisherIT 3 → 4).

Unit tests (T1-T12):
- T1  outside-TX guard fires BEFORE payloadFn invoked (counter == 0)
- T2  persists PENDING row; eventId given to payloadFn == return == saved row eventId
- T3  payloadFn invoked exactly once
- T4  payloadFn invoked BEFORE repository.save (Mockito InOrder)
- T5  RuntimeException from payloadFn propagates isSameAs; no row saved
- T6  payloadFn returning null → NPE "payloadFn returned null"; no row saved
- T7  payloadFn returning empty array → IAE "zero-length"; no row saved
- T8  eventId handed to payloadFn is UUIDv7 (version()==7)
- T9  return value == eventId passed to payloadFn
- T10 saved row eventId == returned eventId (ArgumentCaptor)
- T11 AFTER_COMMIT hot publish sends the returned bytes + defensive-clone
      isolation (caller mutates source after publish → persisted + sent
      bytes unaffected)
- T12 byte[] overload defensive-clone regression (rule-63 ripple proof)

IT (the load-bearing lockstep proof, Memora #3556 §4):
- publishFunctionalLockstepEventIdMatchesPayloadField — publishes a JSON
  envelope embedding the supplied eventId via the Function overload,
  waits for SENT, reads the persisted row, polls EmbeddedKafka for the
  record, parses the payload's event_id, and asserts it equals BOTH the
  outbox row event_id AND the publish() return value. Proves the
  Kafka↔outbox correlation invariant end-to-end against real PG +
  embedded Kafka.

Verification: mvn -pl outbox-publisher clean install → BUILD SUCCESS,
80 unit + 4 IT tests pass, ZERO [WARNING] lines (rule 62). The 4 existing
byte[]-overload tests carry method-scoped @SuppressWarnings("deprecation")
with justification (they deliberately exercise the deprecated path,
retained forRemoval=false for opaque payloads).

Refs: Memora #3555 (the gap), #3556 (design review test matrix).

Superseded by round 2.

Show previous round

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

Round 1 — head ec15a216ba0c, base main, trigger opened

TL;DR: BLOCKED — 1 build-breaking unused import (Propagation, Xlint:all + failOnWarning), 1 minor validation gap in the deprecated overload, 2 info-level observations; 0 findings dropped.

Summary

Arbitration — Round 1

All 4 findings are unique-to-one-reviewer; none are agreed. Each was verified against the HEAD checkout before a keep/drop decision.

B-1 (blocking) KEPTimport org.springframework.transaction.annotation.Propagation at line 23 is confirmed present. Grep confirms its only usage is {@link Propagation#REQUIRES_NEW REQUIRES_NEW} inside a Javadoc comment at line 240; that is not a live bytecode reference. Parent pom.xml lines 100–103 confirm <failOnWarning>true</failOnWarning> + -Xlint:all. The -Xlint:-processing carve-out does not cover [imports], so javac will emit an unused-import warning that failOnWarning promotes to a compile error. This is build-blocking.

A-1 (minor) KEPT — Lines 183–185 of OutboxPublisher.java confirm the deprecated byte[] overload performs only a null-check on payload; no payload.length == 0 guard. Lines 296–298 confirm the new Function overload does guard. The asymmetry is real.

A-2 (info) KEPT — IT lines 224–227 fetch rowEventId via findById(new OutboxRecord.PK(aggregateId, returned)); returned IS the eventId component of the PK, so by construction rowEventId.toString()returned.toString(). The assertion at 241–242 is therefore logically identical to 243–245. Reviewer B described the extra round-trip as "intentional triple-assertion value" but did not rebut the structural identity — the DB call isolates no distinct failure mode. Kept as info.

B-2 (info) KEPT@Deprecated(since = "1.1", forRemoval = false) at line 176 confirmed; parent POM version confirmed as 1.1.0-SNAPSHOT. Informational inconsistency.

Summary: 1 blocking + 1 minor + 2 info findings kept; 0 dropped.

Blast Radius

The diff spans 9 files across 5 Maven modules but substantive logic changes are confined to outbox-publisher/src/main/java/.../OutboxPublisher.java (+152/-4) and its test files. The new exported publish(String, Function<UUID,byte[]>, int, UUID) overload extends the module's public API surface. Other module changes are version-bump-only (pom.xml coordinates).

BLAST_SCORE: 3/10

Risk Indicators

Indicator Value
Sensitive functions publish, persistAndRegisterRelay
Migration touched
Test delta
Dependency changes

CI status (head ec15a216ba0c)

Overall: ✗ failure

2 checks: 2 pending

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

Findings (3)

[BLOCKING] Unused Propagation import will fail the build under Rule 62 (failOnWarning=true + -Xlint:all)

outbox-publisher/src/main/java/com/aim2be/platform/outbox/OutboxPublisher.java:23

Grep-confirmed: import org.springframework.transaction.annotation.Propagation; (line 23) is used only inside a {@link} Javadoc tag at line 240 ({@link Propagation#REQUIRES_NEW REQUIRES_NEW}). Javadoc comments are not compiled to bytecode — javac does not count them as live references. The parent pom.xml (lines 100–103) enforces <failOnWarning>true</failOnWarning> and -Xlint:all (with only -Xlint:-processing excluded). The [imports] lint category is not exempted; an unused-import warning will be promoted to a compile error.

Fix — drop the import and use the fully-qualified name in the Javadoc:

// line 23: DELETE
import org.springframework.transaction.annotation.Propagation;

// line 240: change to
 * {@link org.springframework.transaction.annotation.Propagation#REQUIRES_NEW REQUIRES_NEW} boundary.

No other usage of Propagation exists in the file.

[INFO] Redundant findById round-trip for rowEventId; sentAt check absent after SENT

outbox-publisher/src/test/java/com/aim2be/platform/outbox/OutboxPublisherIT.java:224

Lines 224–227 fetch rowEventId via repository.findById(new OutboxRecord.PK(aggregateId, returned)). Because returned is the eventId component of the composite PK, the retrieved record's eventId is structurally guaranteed to equal returned. Consequently the assertion at lines 241–242 (payloadEventId.isEqualTo(rowEventId.toString())) is logically identical to the assertion at lines 243–245 (payloadEventId.isEqualTo(returned.toString())); the extra DB call isolates no distinct failure mode.

The sibling happyPathPublishCommitsAndKafkaReceives IT asserts row.getSentAt().isNotNull() after awaiting SENT; the new lockstep test skips this. Adding assertThat(row.getSentAt()).isNotNull() inside the untilAsserted() lambda at line 217 would make the two ITs consistent in post-SENT validation and would replace the extra round-trip with a meaningful new assertion.

[INFO] @Deprecated(since = "1.1") does not match the artifact version bumped to 1.1.0-SNAPSHOT

outbox-publisher/src/main/java/com/aim2be/platform/outbox/OutboxPublisher.java:176

Line 176: @Deprecated(since = "1.1", forRemoval = false). All five pom.xml files (parent + four submodules) bump to 1.1.0-SNAPSHOT. The since value "1.1" is informational only (IDE tooltips, javap output) and does not affect compilation or runtime, but consumers and static-analysis tools that parse it will see a mismatch against the artifact coordinate 1.1.0.

Consider:

@Deprecated(since = "1.1.0", forRemoval = false)

Verdict

BLOCKED


hib-pr-reviewer • round 1 • 3 findings (1b/2i) • 2026-05-28T06:19:24.887Z → 2026-05-28T06:21:11.465Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 2._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-platform-libs) **Round 1** — head `ec15a216ba0c`, base `main`, trigger `opened` **TL;DR:** BLOCKED — 1 build-breaking unused import (Propagation, Xlint:all + failOnWarning), 1 minor validation gap in the deprecated overload, 2 info-level observations; 0 findings dropped. ### Summary ## Arbitration — Round 1 All 4 findings are unique-to-one-reviewer; none are agreed. Each was verified against the HEAD checkout before a keep/drop decision. **B-1 (blocking) KEPT** — `import org.springframework.transaction.annotation.Propagation` at line 23 is confirmed present. Grep confirms its only usage is `{@link Propagation#REQUIRES_NEW REQUIRES_NEW}` inside a Javadoc comment at line 240; that is not a live bytecode reference. Parent `pom.xml` lines 100–103 confirm `<failOnWarning>true</failOnWarning>` + `-Xlint:all`. The `-Xlint:-processing` carve-out does not cover `[imports]`, so javac will emit an unused-import warning that `failOnWarning` promotes to a compile error. **This is build-blocking.** **A-1 (minor) KEPT** — Lines 183–185 of OutboxPublisher.java confirm the deprecated `byte[]` overload performs only a null-check on `payload`; no `payload.length == 0` guard. Lines 296–298 confirm the new `Function` overload does guard. The asymmetry is real. **A-2 (info) KEPT** — IT lines 224–227 fetch `rowEventId` via `findById(new OutboxRecord.PK(aggregateId, returned))`; `returned` IS the eventId component of the PK, so by construction `rowEventId.toString()` ≡ `returned.toString()`. The assertion at 241–242 is therefore logically identical to 243–245. Reviewer B described the extra round-trip as "intentional triple-assertion value" but did not rebut the structural identity — the DB call isolates no distinct failure mode. Kept as info. **B-2 (info) KEPT** — `@Deprecated(since = "1.1", forRemoval = false)` at line 176 confirmed; parent POM version confirmed as `1.1.0-SNAPSHOT`. Informational inconsistency. Summary: 1 blocking + 1 minor + 2 info findings kept; 0 dropped. ### Blast Radius The diff spans 9 files across 5 Maven modules but substantive logic changes are confined to `outbox-publisher/src/main/java/.../OutboxPublisher.java` (+152/-4) and its test files. The new exported `publish(String, Function<UUID,byte[]>, int, UUID)` overload extends the module's public API surface. Other module changes are version-bump-only (pom.xml coordinates). **BLAST_SCORE: 3/10** ### Risk Indicators | Indicator | Value | |---|---| | Sensitive functions | `publish`, `persistAndRegisterRelay` | | Migration touched | — | | Test delta | — | | Dependency changes | — | ### CI status (head `ec15a216ba0c`) **Overall: ✗ failure** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/56/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/56/jobs/1) | ### Related PRs - PR-PLATFORM-1 ### Findings (3) #### **[BLOCKING]** Unused `Propagation` import will fail the build under Rule 62 (`failOnWarning=true` + `-Xlint:all`) _outbox-publisher/src/main/java/com/aim2be/platform/outbox/OutboxPublisher.java:23_ Grep-confirmed: `import org.springframework.transaction.annotation.Propagation;` (line 23) is used **only** inside a `{@link}` Javadoc tag at line 240 (`{@link Propagation#REQUIRES_NEW REQUIRES_NEW}`). Javadoc comments are not compiled to bytecode — javac does not count them as live references. The parent `pom.xml` (lines 100–103) enforces `<failOnWarning>true</failOnWarning>` and `-Xlint:all` (with only `-Xlint:-processing` excluded). The `[imports]` lint category is not exempted; an unused-import warning will be promoted to a compile error. **Fix** — drop the import and use the fully-qualified name in the Javadoc: ```java // line 23: DELETE import org.springframework.transaction.annotation.Propagation; // line 240: change to * {@link org.springframework.transaction.annotation.Propagation#REQUIRES_NEW REQUIRES_NEW} boundary. ``` No other usage of `Propagation` exists in the file. #### **[INFO]** Redundant `findById` round-trip for `rowEventId`; `sentAt` check absent after SENT _outbox-publisher/src/test/java/com/aim2be/platform/outbox/OutboxPublisherIT.java:224_ Lines 224–227 fetch `rowEventId` via `repository.findById(new OutboxRecord.PK(aggregateId, returned))`. Because `returned` **is** the eventId component of the composite PK, the retrieved record's `eventId` is structurally guaranteed to equal `returned`. Consequently the assertion at lines 241–242 (`payloadEventId.isEqualTo(rowEventId.toString())`) is logically identical to the assertion at lines 243–245 (`payloadEventId.isEqualTo(returned.toString())`); the extra DB call isolates no distinct failure mode. The sibling `happyPathPublishCommitsAndKafkaReceives` IT asserts `row.getSentAt().isNotNull()` after awaiting SENT; the new lockstep test skips this. Adding `assertThat(row.getSentAt()).isNotNull()` inside the `untilAsserted()` lambda at line 217 would make the two ITs consistent in post-SENT validation and would replace the extra round-trip with a meaningful new assertion. #### **[INFO]** `@Deprecated(since = "1.1")` does not match the artifact version bumped to `1.1.0-SNAPSHOT` _outbox-publisher/src/main/java/com/aim2be/platform/outbox/OutboxPublisher.java:176_ Line 176: `@Deprecated(since = "1.1", forRemoval = false)`. All five `pom.xml` files (parent + four submodules) bump to `1.1.0-SNAPSHOT`. The `since` value `"1.1"` is informational only (IDE tooltips, `javap` output) and does not affect compilation or runtime, but consumers and static-analysis tools that parse it will see a mismatch against the artifact coordinate `1.1.0`. Consider: ```java @Deprecated(since = "1.1.0", forRemoval = false) ``` ### Verdict **BLOCKED** --- <sub>hib-pr-reviewer • round 1 • 3 findings (1b/2i) • 2026-05-28T06:19:24.887Z → 2026-05-28T06:21:11.465Z • posted-as: pr-reviewer-bot</sub> </details>
R2 verdict findings (kept=3; B-1 mechanism reframed after verification):

(1) BLOCKING B-1 OutboxPublisher.java:23 — "unused Propagation import will
    fail the build". Verified FALSE mechanism: javac has NO unused-import
    -Xlint category, CI runs only `mvn install` (no checkstyle/spotbugs),
    and the R1 build was clean (0 warnings) WITH the import present. javac
    does not process Javadoc {@link}, so failOnWarning cannot trip on it.
    Applied the clean fix regardless: removed the Javadoc-only import and
    fully-qualified {@link ...Propagation#REQUIRES_NEW} — no dead-in-bytecode
    import remains and any future checkstyle UnusedImports passes.
(2) MINOR A-1 OutboxPublisher.java:184 — deprecated byte[] overload lacked
    the payload.length==0 guard the Function overload (line 296) already has.
    Added the guard (IllegalArgumentException "payload must not be
    zero-length") + @throws Javadoc + @param "not empty". A zero-byte event
    is never a valid outbox payload; the guard fires before the TX-check,
    co-located with the other arg validation.
(3) INFO B-2 OutboxPublisher.java:176 — @Deprecated(since="1.1") mismatched
    artifact coordinate 1.1.0-SNAPSHOT. Changed since to "1.1.0" and aligned
    the @deprecated prose ("since v1.1.0").

Fix: all three confined to OutboxPublisher.java. No public API-shape change —
the new overload signature is untouched; the byte[] overload only gains a
fail-fast guard for an input that was never valid (no existing test or caller
passes an empty payload).

Verification:
- mvn -B -pl outbox-publisher -am clean verify → BUILD SUCCESS
- failOnWarning=true + -Xlint:all → 0 compiler [WARNING] lines
- OutboxPublisherTest 27, unit suite 81, OutboxPublisherIT 4 — 0 failures
test(outbox-publisher): R2 — T13 byte[] empty-guard + refine IT lockstep per finding A-2
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 2s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
20ee9be77b
R2 test changes (findings A-1 + A-2):

(1) A-1 T13 publishByteArrayEmptyPayloadThrowsIae — proves the new byte[]
    empty-guard fires (IAE containing "zero-length", no row saved). Run
    inside a TX so the assertion isolates the empty-guard from the
    outside-TX guard. Symmetric with T7 (Function overload).
    OutboxPublisherTest 26 → 27.
(2) A-2 OutboxPublisherIT.publishFunctionalLockstepEventIdMatchesPayloadField
    — removed the tautological second findById(PK(aggregateId, returned))
    round-trip + the redundant payload==rowEventId assertion: findById on the
    compound PK can only return a row whose eventId == returned, so it
    isolated no distinct failure mode. Folded a meaningful row.getSentAt() !=
    null assertion into the await() block (consistent with the happyPath IT).
    The lockstep proof (payload event_id == publish() return) is preserved;
    payload event_id == row event_id holds transitively and is documented
    in-test.

Verification:
- mvn -B -pl outbox-publisher -am clean verify → BUILD SUCCESS
- OutboxPublisherTest 27, OutboxPublisherIT 4 — 0 failures

Superseded by round 3.

Show previous round

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

Round 2 — head 20ee9be77b2f, base main, trigger synchronize

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

Summary

[quorum-converged] A=0 = B=0. ## Round 2 review — PR #6 Function<UUID,byte[]> overload

All three round-1 findings are resolved:

  • R1-1 (Propagation import): removed; the @link at line 246 uses the fully-qualified name, no import needed.
  • R1-2 (IT sentAt check absent / redundant findById): assertThat(row.getSentAt()).isNotNull() added at IT:224; explanation comment at IT:238-244 correctly argues why a second findById is unnecessary.
  • R1-3 (@Deprecated(since="1.1") version mismatch): annotation now reads @Deprecated(since = "1.1.0", forRemoval = false).

One new minor finding in the added unit-test file: tripFastConfig() at OutboxPublisherTest.java:232 is defined as a static helper with the comment "Used by the integration test to verify the 11th publish short-circuits", but no integration test (nor any other test file in the package) calls this method. The @SuppressWarnings("unused") annotation acknowledges its dead state. The comment is a stale forward-reference that will mislead future maintainers searching for a non-existent IT.

CI status (head 20ee9be77b2f)

Overall: ✗ failure

2 checks: 2 pending

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

Findings

No new findings this round.

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

Verdict

NO_NEW_FINDINGS


hib-pr-reviewer • round 2 • 0 findings • 2026-05-28T06:28:41.336Z → 2026-05-28T06:33:15.192Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 3._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-platform-libs) **Round 2** — head `20ee9be77b2f`, base `main`, trigger `synchronize` **TL;DR:** NO_NEW_FINDINGS — No new findings this round. ### Summary [quorum-converged] A=0 = B=0. ## Round 2 review — PR #6 Function<UUID,byte[]> overload **All three round-1 findings are resolved:** - R1-1 (`Propagation` import): removed; the `@link` at line 246 uses the fully-qualified name, no import needed. ✅ - R1-2 (IT `sentAt` check absent / redundant `findById`): `assertThat(row.getSentAt()).isNotNull()` added at IT:224; explanation comment at IT:238-244 correctly argues why a second `findById` is unnecessary. ✅ - R1-3 (`@Deprecated(since="1.1")` version mismatch): annotation now reads `@Deprecated(since = "1.1.0", forRemoval = false)`. ✅ **One new minor finding** in the added unit-test file: `tripFastConfig()` at `OutboxPublisherTest.java:232` is defined as a `static` helper with the comment _"Used by the integration test to verify the 11th publish short-circuits"_, but no integration test (nor any other test file in the package) calls this method. The `@SuppressWarnings("unused")` annotation acknowledges its dead state. The comment is a stale forward-reference that will mislead future maintainers searching for a non-existent IT. ### CI status (head `20ee9be77b2f`) **Overall: ✗ failure** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/57/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/57/jobs/1) | ### Findings **No new findings this round.** _Quorum converged on empty findings (A + B both returned 0)._ ### Verdict **NO_NEW_FINDINGS** --- <sub>hib-pr-reviewer • round 2 • 0 findings • 2026-05-28T06:28:41.336Z → 2026-05-28T06:33:15.192Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]</sub> </details>
test(outbox-publisher): R3 — remove dead tripFastConfig helper + stale forward-reference comment
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 3s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
873d4690c9
R3 finding (kept=1; R2 prose flag, dropped from the structured table due to the
reviewer's bookkeeping-fallback path — addressed regardless per rule 18/62):

(1) MINOR OutboxPublisherTest.java:232 — tripFastConfig() was a static helper
    annotated @SuppressWarnings("unused") with a comment claiming it is "Used by
    the integration test to verify the 11th publish short-circuits". Grep
    confirms ZERO callers (only its own definition matched). The referenced IT
    was never written; the suppression masked the dead state and the comment was
    a stale forward-reference that would mislead future maintainers. Removed the
    method + its Javadoc. The circuit-open behaviour it gestured at is already
    covered by hotPublishOnCircuitOpenSkipsKafka (manual OPEN → Kafka skipped)
    and asyncCallbackErrorFeedsCircuitBreakerOnError (async failures feed the
    breaker). CircuitBreakerConfig (line 62, 245) + Duration imports remain used.

Pre-existing — predates this PR's keystone changes. Fixed per rule 18 (encountered
issue, <15 min) + rule 62 #7 (dead code / unnecessary @SuppressWarnings in a
file this PR touches).

Verification:
- mvn -B -pl outbox-publisher -am clean install → BUILD SUCCESS
- failOnWarning=true + -Xlint:all → 0 compiler [WARNING] lines
- OutboxPublisherTest 27, unit suite 81, OutboxPublisherIT 4 — 0 failures

Superseded by round 4.

Show previous round

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

Round 3 — head 873d4690c90b, base main, trigger synchronize

TL;DR: CONDITIONAL_APPROVE — kept 1 unique-to-B minor finding (FQN Pattern usage, verified at line 231); no blocking or major issues; all prior-round findings confirmed fixed.

Summary

Arbiter — Round 3 reconciliation

Agreed findings: None (A=0, B=1).

Unique-to-B finding (line 231 FQN java.util.regex.Pattern): Verified via Read on OutboxPublisherIT.java lines 1–35 (import block) and lines 220–244 (usage site). The import block contains no import java.util.regex.Pattern; — the class is referenced exclusively via its fully-qualified name on line 231. The finding is factually accurate. Severity minor is correct: the code compiles and runs correctly, but the inline FQN is non-idiomatic and makes the already-dense regex chain harder to scan.

Reviewer A raised no new findings after a full read of all three files; nothing in A's narrative contradicts or disputes the B finding.

Kept: 1 unique-to-B finding, verified.
Dropped: 0.

Blast Radius

Changes are confined to one module (outbox-publisher): a new Function<UUID,byte[]> overload in OutboxPublisher.java, extended unit tests, and an additional IT test. The five pom.xml version bumps are mechanical and affect sibling modules only as consumers of the parent version string. No shared protocol contracts or migration files are touched.

BLAST_SCORE: 2/10

CI status (head 873d4690c90b)

Overall: ✗ failure

2 checks: 2 pending

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

Findings (1)

[MINOR] FQN java.util.regex.Pattern used without import — non-idiomatic Java

outbox-publisher/src/test/java/com/aim2be/platform/outbox/OutboxPublisherIT.java:231

Line 231 calls java.util.regex.Pattern.compile(...) via its fully-qualified class name. The import block (lines 1–35) has no import java.util.regex.Pattern;, confirming this is intentional FQN usage rather than a missing import that was silently resolved.

The code is functionally correct, but FQN notation inside method bodies is non-idiomatic in Java test code and makes the already-dense regex chain harder to read at a glance.

Fix: add import java.util.regex.Pattern; alongside the other java.util.* imports and replace line 231 with:

final var matcher = Pattern
        .compile("\"event_id\":\"([^\"]+)\"")
        .matcher(json);

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 3 • 1 finding (1m) • 2026-05-28T06:38:47.454Z → 2026-05-28T06:39:16.683Z • posted-as: pr-reviewer-bot

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 4._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-platform-libs) **Round 3** — head `873d4690c90b`, base `main`, trigger `synchronize` **TL;DR:** CONDITIONAL_APPROVE — kept 1 unique-to-B minor finding (FQN Pattern usage, verified at line 231); no blocking or major issues; all prior-round findings confirmed fixed. ### Summary ## Arbiter — Round 3 reconciliation **Agreed findings:** None (A=0, B=1). **Unique-to-B finding (line 231 FQN `java.util.regex.Pattern`):** Verified via `Read` on `OutboxPublisherIT.java` lines 1–35 (import block) and lines 220–244 (usage site). The import block contains no `import java.util.regex.Pattern;` — the class is referenced exclusively via its fully-qualified name on line 231. The finding is factually accurate. Severity `minor` is correct: the code compiles and runs correctly, but the inline FQN is non-idiomatic and makes the already-dense regex chain harder to scan. **Reviewer A** raised no new findings after a full read of all three files; nothing in A's narrative contradicts or disputes the B finding. **Kept:** 1 unique-to-B finding, verified. **Dropped:** 0. ### Blast Radius Changes are confined to one module (`outbox-publisher`): a new `Function<UUID,byte[]>` overload in `OutboxPublisher.java`, extended unit tests, and an additional IT test. The five pom.xml version bumps are mechanical and affect sibling modules only as consumers of the parent version string. No shared protocol contracts or migration files are touched. **BLAST_SCORE: 2/10** ### CI status (head `873d4690c90b`) **Overall: ✗ failure** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/58/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/58/jobs/1) | ### Findings (1) #### **[MINOR]** FQN `java.util.regex.Pattern` used without import — non-idiomatic Java _outbox-publisher/src/test/java/com/aim2be/platform/outbox/OutboxPublisherIT.java:231_ Line 231 calls `java.util.regex.Pattern.compile(...)` via its fully-qualified class name. The import block (lines 1–35) has no `import java.util.regex.Pattern;`, confirming this is intentional FQN usage rather than a missing import that was silently resolved. The code is functionally correct, but FQN notation inside method bodies is non-idiomatic in Java test code and makes the already-dense regex chain harder to read at a glance. **Fix:** add `import java.util.regex.Pattern;` alongside the other `java.util.*` imports and replace line 231 with: ```java final var matcher = Pattern .compile("\"event_id\":\"([^\"]+)\"") .matcher(json); ``` ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 3 • 1 finding (1m) • 2026-05-28T06:38:47.454Z → 2026-05-28T06:39:16.683Z • posted-as: pr-reviewer-bot</sub> </details>
test(outbox-publisher): R4 — import java.util.regex.Pattern instead of inline FQN in IT
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 3s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
98a8ea4828
R4 finding (kept=1; CONDITIONAL_APPROVE, unique-to-B):

(1) MINOR OutboxPublisherIT.java:231 — the lockstep IT referenced
    java.util.regex.Pattern via its fully-qualified name inline in the method
    body. Non-idiomatic and inconsistent with the file's other java.util.* /
    java.time.* imports (rule 10 — match existing conventions). Added
    `import java.util.regex.Pattern;` and use the short name in the matcher
    chain. Functionally identical (regex unchanged). This corrects my own R1
    lockstep-test code.

Verification:
- mvn -B -pl outbox-publisher -am clean verify → BUILD SUCCESS
- OutboxPublisherTest 27, unit suite 81, OutboxPublisherIT 4 — 0 failures
- failOnWarning=true + -Xlint:all → 0 compiler [WARNING] lines

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

Round 4 — head 98a8ea482801, base main, trigger synchronize

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

Summary

[quorum-converged] A=0 = B=0. Round 4 — All prior findings resolved; no new actionable findings.

Verified the current state of every open finding:

Round Finding Status
R1 Propagation unused import at OutboxPublisher.java:23 Resolved — import is absent from the file (last import at line 24 is TransactionSynchronizationManager)
R1 Redundant findById + missing sentAt check at OutboxPublisherIT.java:224 Resolvedawait() now uses findById(new OutboxRecord.PK(aggregateId, returned)) directly; both SENT and sentAt != null are asserted (lines 224–225)
R1 @Deprecated(since = "1.1") version mismatch Resolved — annotation is now @Deprecated(since = "1.1.0", forRemoval = false) matching the artifact version
R3 FQN java.util.regex.Pattern without import Resolvedimport java.util.regex.Pattern; added at line 13; Pattern.compile(…) used at line 232

The 13 new unit tests (T1–T13) for the Function<UUID, byte[]> overload cover all documented contracts: outside-TX guard fires before payloadFn, exactly-once invocation, before-save ordering, exception propagation, null return, empty-array return, UUIDv7 invariant, return-value equality, and defensive clone for both overloads. The end-to-end IT lockstep test correctly uses the unique marker sentinel to correlate the Kafka message to the specific publish call without false-positives from prior test records.

CI status (head 98a8ea482801)

Overall: ✗ failure

2 checks: 2 pending

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

Findings

No new findings this round.

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

Verdict

NO_NEW_FINDINGS


hib-pr-reviewer • round 4 • 0 findings • 2026-05-28T06:41:46.989Z → 2026-05-28T06:44:56.075Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]

## hib-pr-reviewer review — PR #6 (affinity-intelligence-rework/im2be-platform-libs) **Round 4** — head `98a8ea482801`, base `main`, trigger `synchronize` **TL;DR:** NO_NEW_FINDINGS — No new findings this round. ### Summary [quorum-converged] A=0 = B=0. **Round 4 — All prior findings resolved; no new actionable findings.** Verified the current state of every open finding: | Round | Finding | Status | |-------|---------|--------| | R1 | `Propagation` unused import at `OutboxPublisher.java:23` | ✅ **Resolved** — import is absent from the file (last import at line 24 is `TransactionSynchronizationManager`) | | R1 | Redundant `findById` + missing `sentAt` check at `OutboxPublisherIT.java:224` | ✅ **Resolved** — `await()` now uses `findById(new OutboxRecord.PK(aggregateId, returned))` directly; both `SENT` and `sentAt != null` are asserted (lines 224–225) | | R1 | `@Deprecated(since = "1.1")` version mismatch | ✅ **Resolved** — annotation is now `@Deprecated(since = "1.1.0", forRemoval = false)` matching the artifact version | | R3 | FQN `java.util.regex.Pattern` without import | ✅ **Resolved** — `import java.util.regex.Pattern;` added at line 13; `Pattern.compile(…)` used at line 232 | The 13 new unit tests (T1–T13) for the `Function<UUID, byte[]>` overload cover all documented contracts: outside-TX guard fires before `payloadFn`, exactly-once invocation, before-save ordering, exception propagation, null return, empty-array return, UUIDv7 invariant, return-value equality, and defensive clone for both overloads. The end-to-end IT lockstep test correctly uses the unique `marker` sentinel to correlate the Kafka message to the specific publish call without false-positives from prior test records. ### CI status (head `98a8ea482801`) **Overall: ✗ failure** 2 checks: 2 pending | Check | State | Link | |---|---|---| | im2be-platform-libs CI / mvn install (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/59/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/59/jobs/1) | ### Findings **No new findings this round.** _Quorum converged on empty findings (A + B both returned 0)._ ### Verdict **NO_NEW_FINDINGS** --- <sub>hib-pr-reviewer • round 4 • 0 findings • 2026-05-28T06:41:46.989Z → 2026-05-28T06:44:56.075Z • posted-as: pr-reviewer-bot • [bookkeeping fallback]</sub>
hibryda deleted branch feat/platform-1-followup-eventid-overload 2026-05-28 08:47:44 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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