The branch was supposed to be closed. Handover said so. Seven items swept, committed, pushed upstream.

Except it wasn’t. When I resumed, git status showed eleven uncommitted files — the whole ExpiringExclusionPolicy example sitting untracked in the examples directory, the CDI fix missing from three test modules, the Javadoc improvements staged but never committed. I’d apparently built everything in a previous session and then just… not committed it.

Dates in the field

ExpiringExclusionPolicy implements the ExclusionPolicy SPI by encoding expiry dates directly in the excludedUsers field: alice:2026-08-01,bob:2026-07-15. A user is denied if today is strictly before their expiry date; on the expiry date itself, they’re allowed. The encoding is backward-compatible with the default CommaSeparatedExclusionPolicy — tokens without a colon are treated as permanent exclusions, so switching policies on a WorkItem created with the old format doesn’t break anything. Existing plain CSV entries just become permanent.

There’s a less obvious case: what happens when the date token is unparseable (alice:not-a-date)? The choice was permanent exclusion as the fail-safe. Silently allowing felt wrong in a security-adjacent field.

The Clock injection is the interesting design detail. The class carries no CDI annotations (intentionally — it’s an example, not a replacement for the default), so there’s no container to inject through in tests. Two constructors: a package-private default that creates Clock.systemDefaultZone(), and a package-private test constructor that takes a Clock. Eleven tests, all deterministic, covering the boundary case where today exactly equals the expiry date.

The CDI issue we fixed across three test modules came from casehub-platform being declared runtime scope. That’s needed because MockPreferenceProvider is production-deployed, but it pulls MockGroupMembershipProvider into the CDI scan at test time. That bean conflicts with NoOpGroupMembershipProvider (@DefaultBean) — two candidates, no @Alternative, Quarkus ARC throws an AmbiguousResolutionException. Fix: quarkus.arc.exclude-types=io.casehub.platform.mock.MockGroupMembershipProvider in each test application.properties.

When the label doesn’t match the work

Issue #234 was labeled S · Low and included in the sweep. I brought Claude in to investigate before writing any code.

Three blockers surfaced quickly. First: casehub-connectors-core:0.2-SNAPSHOT isn’t in the local Maven cache and the connectors#6 branch hasn’t merged to main — casehub-work can’t compile against it. Second: the connectors#6 design spec made an explicit decision (D3) that routing flows InboundMessage → Qhorus → WorkItem. A direct observer in casehub-work would either duplicate that path or contradict it, and the architectural choice was never made explicit. Third: the issue acknowledged that not all inbound messages should create WorkItems but left the classification mechanism undefined.

The S · Low label described the work as someone imagined it before looking closely. The implementation might be simple once those questions are answered. “Simple to write” and “ready to write” are different things.

We labeled it blocked, escalated complexity to High, documented the blockers in a comment, and removed it from the sweep umbrella. The lesson formalised into a protocol: sweep items with real blockers must be documented and removed, never silently skipped.

Twelve tests passing, branch clean, #235 closed minus one.


<
Previous Post
When caught exceptions commit
>
Next Post
Seven issues off the board, one trust gate wired