feat(error-event-publisher): durable error-event Kafka stream via outbox (ADR-0015, #332) #18

Merged
hibryda merged 2 commits from feat/error-event-publisher into main 2026-05-31 00:31:38 +02:00
Owner

Summary

New 6th platform-libs module — the foundation for the #332 error-event Kafka stream (ADR-0015, ACCEPTED). Serializes observability.v1.ErrorEvent and publishes it to im2be.error.event via the ADR-0014 outbox, in addition to the existing OtelErrorEvents span-event + counter — giving a durable, 100% (unsampled), replayable, queryable cross-service error log.

Design (per ADR-0015)

  • ErrorEventPublisher — best-effort facade; publish(ErrorEvent) never throws (catches RuntimeException, logs, swallows: observability must not break the caller; D-3).
  • ErrorEventOutboxWriter@Transactional(REQUIRES_NEW) so the error-event row survives a rolling-back business tx (the D-3 hazard); a separate bean so the tx proxy applies (not self-invoked). Writes raw protobuf bytes (D-2 — no Apicurio; registry-outage resilience) via the deferred-serialisation OutboxPublisher overload (ErrorEvent has no event_id to embed → supplied id ignored). Partition key = trace_id→128-bit UUID (random fallback).
  • ErrorEventPropertiesim2be.error-event.{enabled(default off),topic}.
  • ErrorEventAutoConfiguration@ConditionalOnProperty(enabled) + @ConditionalOnBean(OutboxPublisher); opt-in per service.

Verification

  • mvn -pl error-event-publisher test → Tests run: 6, Failures: 0, Errors: 0 (best-effort swallow, null no-op, delegation, raw-bytes payload, trace→UUID determinism + random fallback).
  • mvn -pl error-event-publisher -am install → BUILD SUCCESS, zero warnings, javadoc:jar clean.

Blast radius

Additive new module; no change to existing modules (only the parent <modules> list + README). Default off — no behaviour change until a service sets im2be.error-event.enabled=true. Score 3/10.

Next (separate, post-merge+deploy)

flux-applications KafkaTopic CRs (im2be.error.event + .DLT) — operator kubectl apply gated; then per-service OtelErrorEvents.record(...) wiring.

## Summary New 6th platform-libs module — the foundation for the #332 **error-event Kafka stream** ([ADR-0015](../src/branch/main/docs/decisions/0015-error-event-kafka-stream.md), ACCEPTED). Serializes `observability.v1.ErrorEvent` and publishes it to `im2be.error.event` via the ADR-0014 outbox, *in addition to* the existing `OtelErrorEvents` span-event + counter — giving a durable, 100% (unsampled), replayable, queryable cross-service error log. ## Design (per ADR-0015) - **`ErrorEventPublisher`** — best-effort facade; `publish(ErrorEvent)` **never throws** (catches `RuntimeException`, logs, swallows: observability must not break the caller; D-3). - **`ErrorEventOutboxWriter`** — `@Transactional(REQUIRES_NEW)` so the error-event row **survives a rolling-back business tx** (the D-3 hazard); a *separate bean* so the tx proxy applies (not self-invoked). Writes **raw protobuf bytes** (D-2 — no Apicurio; registry-outage resilience) via the deferred-serialisation `OutboxPublisher` overload (`ErrorEvent` has no `event_id` to embed → supplied id ignored). Partition key = `trace_id`→128-bit UUID (random fallback). - **`ErrorEventProperties`** — `im2be.error-event.{enabled(default off),topic}`. - **`ErrorEventAutoConfiguration`** — `@ConditionalOnProperty(enabled)` + `@ConditionalOnBean(OutboxPublisher)`; opt-in per service. ## Verification - `mvn -pl error-event-publisher test` → Tests run: 6, Failures: 0, Errors: 0 (best-effort swallow, null no-op, delegation, raw-bytes payload, trace→UUID determinism + random fallback). - `mvn -pl error-event-publisher -am install` → BUILD SUCCESS, **zero warnings**, javadoc:jar clean. ## Blast radius Additive new module; no change to existing modules (only the parent `<modules>` list + README). Default off — no behaviour change until a service sets `im2be.error-event.enabled=true`. Score 3/10. ## Next (separate, post-merge+deploy) flux-applications `KafkaTopic` CRs (`im2be.error.event` + `.DLT`) — operator `kubectl apply` gated; then per-service `OtelErrorEvents.record(...)` wiring.
feat(error-event-publisher): durable cross-service error-event Kafka stream (ADR-0015)
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 1m50s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
56198fcfc4
New 6th platform-libs module realizing ADR-0015 (#332 error-event stream). Serializes
observability.v1.ErrorEvent and publishes it to im2be.error.event via the ADR-0014
outbox, alongside the existing OtelErrorEvents span-event + counter.

- ErrorEventPublisher — best-effort facade; publish(ErrorEvent) NEVER throws
  (catches RuntimeException, logs, swallows — observability must not break the
  caller's error-handling path; ADR-0015 D-3).
- ErrorEventOutboxWriter — @Transactional(REQUIRES_NEW) so the error-event row
  commits independently of (survives) a rolling-back business tx (the D-3 hazard);
  a SEPARATE bean so the tx proxy applies (not self-invoked). Writes RAW protobuf
  bytes (ADR-0015 D-2 — no Apicurio; registry-outage resilience) via the
  deferred-serialisation OutboxPublisher overload (ErrorEvent has no event_id to
  embed → the supplied id is ignored). Partition key = trace_id→128-bit UUID
  (random fallback), co-locating a trace's cross-service errors.
- ErrorEventProperties (im2be.error-event.enabled default off, .topic).
- ErrorEventAutoConfiguration — @ConditionalOnProperty(enabled) +
  @ConditionalOnBean(OutboxPublisher); opt-in per service.
- Tests: best-effort swallow, null no-op, delegation, raw-bytes payload,
  trace→UUID determinism + random fallback. opentelemetry-api added test-scope
  (mocking OutboxPublisher runs its OTel-referencing static init).

Verification:
- mvn -pl error-event-publisher test → Tests run: 6, Failures: 0, Errors: 0
- mvn -pl error-event-publisher -am install → BUILD SUCCESS, zero warnings,
  javadoc:jar clean (no -1.1 warning — #17 pin holds).

Superseded by round 2.

Show previous round

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

Round 1 — head 56198fcfc49b, base main, trigger opened

TL;DR: NEEDS_WORK — kept 5 findings (2 agreed pairs + 2 unique-to-one each verified, + 1 agreed): 1 major silent non-wiring defect, 4 minor issues including swallowed stack trace, untested REQUIRES_NEW boundary, regex recompile, and missing startup validation.

Summary

Arbitration — Round 1

No prior Memora history for this PR (first run). Memora persist attempted but blocked by tag-allowlist restrictions in this environment — proceeding with reconciliation output only.

Verification performed (all 5 source files read at HEAD):

Finding Status Action
A1 = B1 — Missing @AutoConfigureAfter Agreed + verified: @AutoConfiguration at line 19 has no after= attribute; @ConditionalOnBean(OutboxPublisher.class) at line 30 is present with no ordering guard KEEP — major
A2 = B3 — String.matches() regex recompile Agreed + verified: line 80 of ErrorEventOutboxWriter.java is && traceId.matches("[0-9a-fA-F]{32}") KEEP — minor
B2 unique — LOG.warn swallows stack trace Verified: lines 47-49 of ErrorEventPublisher.java pass e.toString() as the {} format arg, SLF4J will not append the stack trace KEEP — minor
A3 unique — Transactional proxy untested Verified: ErrorEventOutboxWriterTest.java line 34 uses new ErrorEventOutboxWriter(...), write() is called on the plain instance at line 41 — proxy never engaged KEEP — minor
A4 = B4 — @Validated / @NotBlank missing Agreed + verified: ErrorEventProperties.java has no @Validated on the class and no @NotBlank on the topic field KEEP — minor

Final tally: 5 kept (1 major, 4 minor). Zero dropped.

Blast Radius

This is an entirely new module (all 10 files are additions) exporting a public autoconfigured API surface (ErrorEventPublisher, ErrorEventOutboxWriter, ErrorEventProperties). The blast radius is self-contained — the module wraps the existing outbox-publisher and is opt-in via a property flag — but the major wiring defect affects every service that activates both im2be.error-event.enabled=true and im2be.outbox.enabled=true, potentially silently disabling the error stream across all adopters.

BLAST_SCORE: 4/10

CI status (head 56198fcfc49b)

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 (5)

[MAJOR] Missing @AutoConfigureAfter(OutboxAutoConfiguration.class)@ConditionalOnBean(OutboxPublisher.class) silently evaluates false under non-deterministic classpath ordering

error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventAutoConfiguration.java:19

ErrorEventAutoConfiguration (line 19) carries only @AutoConfiguration with no after= directive. Spring Boot evaluates @ConditionalOnBean against bean definitions already registered at the moment each auto-configuration is processed. Because this module and OutboxAutoConfiguration ship in separate JARs with separate .imports files, Spring Boot may (and commonly does, since alphabetical ordering puts 'E' before 'O' in merged import lists) process ErrorEventAutoConfiguration first. At that point OutboxPublisher is not yet a registered bean definition, @ConditionalOnBean(OutboxPublisher.class) at line 30 returns false, and neither errorEventOutboxWriter nor errorEventPublisher is created — with no warning or startup failure. A service that sets both im2be.error-event.enabled=true and im2be.outbox.enabled=true silently gets no error-event publishing.

Fix (idiomatic Spring Boot 2.7+): Change line 19 from:

@AutoConfiguration

to:

@AutoConfiguration(after = com.aim2be.platform.outbox.OutboxAutoConfiguration.class)

Alternatively, add @AutoConfigureAfter(OutboxAutoConfiguration.class) as a separate annotation. The Spring Boot docs explicitly state: "we strongly recommend that you use @AutoConfigureAfter and @AutoConfigureBefore annotations to create an ordering between auto-configurations when @ConditionalOnBean is used."

[MINOR] String.matches() recompiles the hex-validation regex on every aggregateId() call

error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventOutboxWriter.java:80

Line 80: && traceId.matches("[0-9a-fA-F]{32}") calls Pattern.compile(regex) internally on each invocation, allocating a new Pattern object per call to aggregateId(). The length == W3C_TRACE_ID_HEX_LEN pre-check on line 79 passes for every well-formed W3C trace ID (the common case), so the regex allocation is on the hot path — once per write().

Fix: Hoist the pattern to a private static final constant:

private static final Pattern TRACE_ID_HEX_PATTERN =
        Pattern.compile("[0-9a-fA-F]{32}");

and replace line 80 with:

        && TRACE_ID_HEX_PATTERN.matcher(traceId).matches()) {

The existing W3C_TRACE_ID_HEX_LEN length pre-check remains useful and should be kept.

[MINOR] LOG.warn passes e.toString() as a {} placeholder argument — full stack trace is swallowed from log output

error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventPublisher.java:47

Lines 47-49:

LOG.warn(
    "error-event publish dropped (best-effort) for service={} errorClass={}: {}",
    event.getServiceName(), event.getErrorClass(), e.toString());

SLF4J appends a Throwable's full stack trace only when the Throwable is passed as the last argument with no corresponding {} placeholder. Here the trailing {} causes SLF4J to consume e.toString() as a plain string argument; the stack trace is discarded entirely. During an outage triage the log will show only the exception message (e.g. IllegalStateException: db down) with no indication of where in the outbox stack the failure originated.

Fix: Remove the final {} placeholder and pass e directly as the trailing argument:

LOG.warn("error-event publish dropped (best-effort) for service={} errorClass={}",
        event.getServiceName(), event.getErrorClass(), e);

SLF4J recognises the trailing Throwable and appends the full cause chain and stack frames automatically.

[MINOR] ADR-0015 D-3 guarantee (error survives business-tx rollback) is untested — unit tests bypass the Spring transactional proxy

error-event-publisher/src/test/java/com/aim2be/platform/errorevent/ErrorEventOutboxWriterTest.java:34

All three tests instantiate the writer with new ErrorEventOutboxWriter(outbox, TOPIC) at line 34 and call writer.write(event) directly at line 41. A plain-new instance carries no Spring AOP proxy, so @Transactional(propagation = REQUIRES_NEW) on write() is a no-op in these tests. The module's primary correctness claim — that the outbox row commits independently when an outer transaction rolls back (ADR-0015 D-3) — is verified nowhere in the test suite.

The existing Mockito tests are valuable for payload/key logic and should be retained. Recommend adding at least one @DataJpaTest (or @SpringBootTest slice) integration test that:

  1. Begins an outer REQUIRED transaction.
  2. Calls errorEventOutboxWriter.write(event) through the Spring-managed bean (not new).
  3. Forces the outer transaction to roll back.
  4. Asserts the outbox row persisted (query in a fresh, clean transaction).

[MINOR] ErrorEventProperties lacks @Validated + @NotBlank on topic — blank misconfiguration surfaces at runtime, not startup

error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventProperties.java:9

ErrorEventProperties (lines 9-39) has no @Validated annotation on the class and no JSR-303 constraint on the topic field. An operator who accidentally sets im2be.error-event.topic= (blank) or im2be.error-event.topic= (whitespace) will get a blank topic silently propagated into ErrorEventOutboxWriter; the misconfiguration surfaces only at the first write() call as a RuntimeException that ErrorEventPublisher.publish() swallows with a single log line — the error stream fails silently with no indication of misconfiguration. The sibling OutboxProperties correctly carries @Validated.

Fix: Add @Validated to the class and @NotBlank to the topic field:

import jakarta.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "im2be.error-event")
public class ErrorEventProperties {

    @NotBlank
    private String topic = "im2be.error.event";

Spring Boot will then reject the application context at startup with a clear BindValidationException, consistent with OutboxProperties.

Verdict

NEEDS_WORK


hib-pr-reviewer • round 1 • 5 findings (1M/4m) • 2026-05-30T22:17:25.096Z → 2026-05-30T22:19:49.977Z • posted-as: pr-reviewer-bot • model: auto

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 2._ <details> <summary>Show previous round</summary> ## hib-pr-reviewer review — PR #18 (affinity-intelligence-rework/im2be-platform-libs) **Round 1** — head `56198fcfc49b`, base `main`, trigger `opened` **TL;DR:** NEEDS_WORK — kept 5 findings (2 agreed pairs + 2 unique-to-one each verified, + 1 agreed): 1 major silent non-wiring defect, 4 minor issues including swallowed stack trace, untested REQUIRES_NEW boundary, regex recompile, and missing startup validation. ### Summary ## Arbitration — Round 1 No prior Memora history for this PR (first run). Memora persist attempted but blocked by tag-allowlist restrictions in this environment — proceeding with reconciliation output only. **Verification performed** (all 5 source files read at HEAD): | Finding | Status | Action | |---------|--------|--------| | A1 = B1 — Missing `@AutoConfigureAfter` | Agreed + verified: `@AutoConfiguration` at line 19 has no `after=` attribute; `@ConditionalOnBean(OutboxPublisher.class)` at line 30 is present with no ordering guard | **KEEP — major** | | A2 = B3 — `String.matches()` regex recompile | Agreed + verified: line 80 of `ErrorEventOutboxWriter.java` is `&& traceId.matches("[0-9a-fA-F]{32}")` | **KEEP — minor** | | B2 unique — `LOG.warn` swallows stack trace | Verified: lines 47-49 of `ErrorEventPublisher.java` pass `e.toString()` as the `{}` format arg, SLF4J will not append the stack trace | **KEEP — minor** | | A3 unique — Transactional proxy untested | Verified: `ErrorEventOutboxWriterTest.java` line 34 uses `new ErrorEventOutboxWriter(...)`, `write()` is called on the plain instance at line 41 — proxy never engaged | **KEEP — minor** | | A4 = B4 — `@Validated` / `@NotBlank` missing | Agreed + verified: `ErrorEventProperties.java` has no `@Validated` on the class and no `@NotBlank` on the `topic` field | **KEEP — minor** | Final tally: **5 kept** (1 major, 4 minor). Zero dropped. ### Blast Radius This is an entirely new module (all 10 files are additions) exporting a public autoconfigured API surface (`ErrorEventPublisher`, `ErrorEventOutboxWriter`, `ErrorEventProperties`). The blast radius is self-contained — the module wraps the existing `outbox-publisher` and is opt-in via a property flag — but the major wiring defect affects every service that activates both `im2be.error-event.enabled=true` and `im2be.outbox.enabled=true`, potentially silently disabling the error stream across all adopters. **BLAST_SCORE: 4/10** ### CI status (head `56198fcfc49b`) **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/102/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/102/jobs/1) | ### Findings (5) #### **[MAJOR]** Missing `@AutoConfigureAfter(OutboxAutoConfiguration.class)` — `@ConditionalOnBean(OutboxPublisher.class)` silently evaluates false under non-deterministic classpath ordering _error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventAutoConfiguration.java:19_ `ErrorEventAutoConfiguration` (line 19) carries only `@AutoConfiguration` with no `after=` directive. Spring Boot evaluates `@ConditionalOnBean` against bean *definitions already registered* at the moment each auto-configuration is processed. Because this module and `OutboxAutoConfiguration` ship in separate JARs with separate `.imports` files, Spring Boot may (and commonly does, since alphabetical ordering puts 'E' before 'O' in merged import lists) process `ErrorEventAutoConfiguration` first. At that point `OutboxPublisher` is not yet a registered bean definition, `@ConditionalOnBean(OutboxPublisher.class)` at line 30 returns `false`, and neither `errorEventOutboxWriter` nor `errorEventPublisher` is created — with no warning or startup failure. A service that sets both `im2be.error-event.enabled=true` and `im2be.outbox.enabled=true` silently gets no error-event publishing. **Fix (idiomatic Spring Boot 2.7+):** Change line 19 from: ```java @AutoConfiguration ``` to: ```java @AutoConfiguration(after = com.aim2be.platform.outbox.OutboxAutoConfiguration.class) ``` Alternatively, add `@AutoConfigureAfter(OutboxAutoConfiguration.class)` as a separate annotation. The Spring Boot docs explicitly state: *"we strongly recommend that you use @AutoConfigureAfter and @AutoConfigureBefore annotations to create an ordering between auto-configurations when @ConditionalOnBean is used."* #### **[MINOR]** `String.matches()` recompiles the hex-validation regex on every `aggregateId()` call _error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventOutboxWriter.java:80_ Line 80: `&& traceId.matches("[0-9a-fA-F]{32}")` calls `Pattern.compile(regex)` internally on each invocation, allocating a new `Pattern` object per call to `aggregateId()`. The `length == W3C_TRACE_ID_HEX_LEN` pre-check on line 79 passes for every well-formed W3C trace ID (the common case), so the regex allocation is on the hot path — once per `write()`. **Fix:** Hoist the pattern to a `private static final` constant: ```java private static final Pattern TRACE_ID_HEX_PATTERN = Pattern.compile("[0-9a-fA-F]{32}"); ``` and replace line 80 with: ```java && TRACE_ID_HEX_PATTERN.matcher(traceId).matches()) { ``` The existing `W3C_TRACE_ID_HEX_LEN` length pre-check remains useful and should be kept. #### **[MINOR]** `LOG.warn` passes `e.toString()` as a `{}` placeholder argument — full stack trace is swallowed from log output _error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventPublisher.java:47_ Lines 47-49: ```java LOG.warn( "error-event publish dropped (best-effort) for service={} errorClass={}: {}", event.getServiceName(), event.getErrorClass(), e.toString()); ``` SLF4J appends a Throwable's full stack trace only when the Throwable is passed as the **last argument with no corresponding `{}` placeholder**. Here the trailing `{}` causes SLF4J to consume `e.toString()` as a plain string argument; the stack trace is discarded entirely. During an outage triage the log will show only the exception message (e.g. `IllegalStateException: db down`) with no indication of where in the outbox stack the failure originated. **Fix:** Remove the final `{}` placeholder and pass `e` directly as the trailing argument: ```java LOG.warn("error-event publish dropped (best-effort) for service={} errorClass={}", event.getServiceName(), event.getErrorClass(), e); ``` SLF4J recognises the trailing `Throwable` and appends the full cause chain and stack frames automatically. #### **[MINOR]** ADR-0015 D-3 guarantee (error survives business-tx rollback) is untested — unit tests bypass the Spring transactional proxy _error-event-publisher/src/test/java/com/aim2be/platform/errorevent/ErrorEventOutboxWriterTest.java:34_ All three tests instantiate the writer with `new ErrorEventOutboxWriter(outbox, TOPIC)` at line 34 and call `writer.write(event)` directly at line 41. A plain-`new` instance carries no Spring AOP proxy, so `@Transactional(propagation = REQUIRES_NEW)` on `write()` is a no-op in these tests. The module's primary correctness claim — that the outbox row commits independently when an outer transaction rolls back (ADR-0015 D-3) — is verified nowhere in the test suite. The existing Mockito tests are valuable for payload/key logic and should be retained. Recommend adding at least one `@DataJpaTest` (or `@SpringBootTest` slice) integration test that: 1. Begins an outer `REQUIRED` transaction. 2. Calls `errorEventOutboxWriter.write(event)` **through the Spring-managed bean** (not `new`). 3. Forces the outer transaction to roll back. 4. Asserts the outbox row persisted (query in a fresh, clean transaction). #### **[MINOR]** `ErrorEventProperties` lacks `@Validated` + `@NotBlank` on `topic` — blank misconfiguration surfaces at runtime, not startup _error-event-publisher/src/main/java/com/aim2be/platform/errorevent/ErrorEventProperties.java:9_ `ErrorEventProperties` (lines 9-39) has no `@Validated` annotation on the class and no JSR-303 constraint on the `topic` field. An operator who accidentally sets `im2be.error-event.topic=` (blank) or `im2be.error-event.topic= ` (whitespace) will get a blank topic silently propagated into `ErrorEventOutboxWriter`; the misconfiguration surfaces only at the first `write()` call as a `RuntimeException` that `ErrorEventPublisher.publish()` swallows with a single log line — the error stream fails silently with no indication of misconfiguration. The sibling `OutboxProperties` correctly carries `@Validated`. **Fix:** Add `@Validated` to the class and `@NotBlank` to the `topic` field: ```java import jakarta.validation.constraints.NotBlank; import org.springframework.validation.annotation.Validated; @Validated @ConfigurationProperties(prefix = "im2be.error-event") public class ErrorEventProperties { @NotBlank private String topic = "im2be.error.event"; ``` Spring Boot will then reject the application context at startup with a clear `BindValidationException`, consistent with `OutboxProperties`. ### Verdict **NEEDS_WORK** --- <sub>hib-pr-reviewer • round 1 • 5 findings (1M/4m) • 2026-05-30T22:17:25.096Z → 2026-05-30T22:19:49.977Z • posted-as: pr-reviewer-bot • model: auto</sub> </details>
fix(error-event-publisher): address #18 R1 (1 major autoconfig-order + 4 minor)
Some checks failed
im2be-platform-libs CI / mvn install (pull_request) Failing after 1m11s
im2be-platform-libs CI / mvn verify (main only) (pull_request) Has been skipped
e0a9067494
R1 verdict NEEDS_WORK; all 5 findings (kept=5):

(MAJOR) ErrorEventAutoConfiguration lacked ordering → @ConditionalOnBean(OutboxPublisher)
  could evaluate before OutboxAutoConfiguration registered the bean (separate JARs,
  'E' before 'O' in merged imports) → error-event SILENTLY not wired even when enabled.
  Fix: @AutoConfiguration(after = OutboxAutoConfiguration.class).
(MINOR) aggregateId() recompiled the hex regex per call (hot path) → hoisted to a
  static final Pattern TRACE_ID_HEX_PATTERN.
(MINOR) ErrorEventPublisher.warn passed e.toString() as a {} arg → stack trace
  swallowed. Now passes `e` as the trailing arg with no {} → SLF4J logs the trace.
(MINOR) REQUIRES_NEW boundary untested (plain-new tests bypass the proxy). Added
  ErrorEventOutboxWriterTxTest: a @SpringBootTest under @EnableTransactionManagement
  with a recording PlatformTransactionManager proving write() runs through the proxy
  in a PROPAGATION_REQUIRES_NEW tx (the part in this module's control; rollback
  survival is then Spring's documented REQUIRES_NEW guarantee).
(MINOR) ErrorEventProperties not validated → blank topic surfaced at runtime. Added
  @Validated + @NotBlank on topic (+ spring-boot-starter-validation) → fail-fast at
  startup, parity with OutboxProperties.

Verification:
- mvn -pl error-event-publisher test → Tests run: 7, Failures: 0, Errors: 0
- mvn -pl error-event-publisher install → BUILD SUCCESS, zero warnings, javadoc clean

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

Round 2 — head e0a906749494, base main, trigger synchronize

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

Summary

[quorum-converged] A=0 = B=0. ## Round 2 review — all five prior-round findings addressed

Finding-by-finding status:

Round 1 finding Status
ErrorEventAutoConfiguration.java:19 — missing @AutoConfigureAfter Fixed: @AutoConfiguration(after = OutboxAutoConfiguration.class) at line 20
ErrorEventOutboxWriter.java:80 — regex recompiled per call Fixed: TRACE_ID_HEX_PATTERN hoisted as static final Pattern
ErrorEventPublisher.java:47 — stack trace swallowed by e.toString() Fixed: e is the third variadic argument (no matching {}), SLF4J logs the full stack
ErrorEventOutboxWriterTest.java:34 — D-3 guarantee (REQUIRES_NEW proxy) untested Fixed: new ErrorEventOutboxWriterTxTest wires the writer as a Spring bean under @EnableTransactionManagement + RecordingTransactionManager and asserts PROPAGATION_REQUIRES_NEW
ErrorEventProperties.java:9@Validated / @NotBlank absent Fixed: both annotations present, spring-boot-starter-validation added to pom

All five conditions are satisfied. No new actionable findings were identified in this round.

CI status (head e0a906749494)

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-30T22:26:57.523Z → 2026-05-30T22:31:05.533Z • posted-as: pr-reviewer-bot • model: auto • [bookkeeping fallback]

## hib-pr-reviewer review — PR #18 (affinity-intelligence-rework/im2be-platform-libs) **Round 2** — head `e0a906749494`, base `main`, trigger `synchronize` **TL;DR:** NO_NEW_FINDINGS — No new findings this round. ### Summary [quorum-converged] A=0 = B=0. ## Round 2 review — all five prior-round findings addressed **Finding-by-finding status:** | Round 1 finding | Status | |---|---| | `ErrorEventAutoConfiguration.java:19` — missing `@AutoConfigureAfter` | ✅ Fixed: `@AutoConfiguration(after = OutboxAutoConfiguration.class)` at line 20 | | `ErrorEventOutboxWriter.java:80` — regex recompiled per call | ✅ Fixed: `TRACE_ID_HEX_PATTERN` hoisted as `static final Pattern` | | `ErrorEventPublisher.java:47` — stack trace swallowed by `e.toString()` | ✅ Fixed: `e` is the third variadic argument (no matching `{}`), SLF4J logs the full stack | | `ErrorEventOutboxWriterTest.java:34` — D-3 guarantee (REQUIRES_NEW proxy) untested | ✅ Fixed: new `ErrorEventOutboxWriterTxTest` wires the writer as a Spring bean under `@EnableTransactionManagement` + `RecordingTransactionManager` and asserts `PROPAGATION_REQUIRES_NEW` | | `ErrorEventProperties.java:9` — `@Validated` / `@NotBlank` absent | ✅ Fixed: both annotations present, `spring-boot-starter-validation` added to pom | All five conditions are satisfied. No new actionable findings were identified in this round. ### CI status (head `e0a906749494`) **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/103/jobs/0) | | im2be-platform-libs CI / mvn verify (main only) (pull_request) | ⏳ pending | [details](/affinity-intelligence-rework/im2be-platform-libs/actions/runs/103/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-30T22:26:57.523Z → 2026-05-30T22:31:05.533Z • posted-as: pr-reviewer-bot • model: auto • [bookkeeping fallback]</sub>
hibryda deleted branch feat/error-event-publisher 2026-05-31 00:31:38 +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!18
No description provided.