eidos#10: findById(String agentId, String tenancyId) had no null guard on tenancyId. The blocking impls silently returned Optional.empty()"default".equals(null) is false, not an exception. The JPA version passed null to JPQL, which also returns empty silently. Both are wrong. The fix is Objects.requireNonNull(tenancyId, "tenancyId") at the top of all four implementations — consistent with how AgentQuery already enforces non-null in its constructor.

The interesting part was the reactive case. Writing the test for JpaReactiveAgentRegistry:

assertThatThrownBy(() -> registry.findById("any", null))
    .isInstanceOf(NullPointerException.class);

This fails — not because the null guard is missing, but because @WithSession is a CDI interceptor that catches all exceptions from the method body and re-emits them as Uni failures. The synchronous Objects.requireNonNull throw never propagates out of the proxy. The test sees no throwable and fails with “Expecting code to raise a throwable.”

The fix is to test via the Uni channel:

@Test
@RunOnVertxContext
void findById_with_null_tenancyId_throws(UniAsserter asserter) {
    asserter.assertFailedWith(() -> registry.findById("any", null), NullPointerException.class);
}

The in-memory reactive registry delegates synchronously — delegate.findById(agentId, tenancyId) is evaluated before Uni.createFrom().item(...) wraps it — so the NPE propagates normally there. The two reactive implementations have different failure propagation semantics depending on whether there’s an interceptor in the call path.

Code review (code-reviewer subagent) caught the gap: the initial diff only fixed three of four implementations. JpaReactiveAgentRegistry was missed entirely — a CRITICAL finding that blocked the commit. Caught and fixed before merge.

Filed as follow-on: eidos#11 (agentId null guard — symmetric with tenancyId), eidos#12 (Javadoc on SPI interface declaring the null contract).


<
Previous Post
The Guard That Did Too Much
>
Next Post
Layer 1 is real code — designing the household domain baseline