The Producer That Forgot Its Own Interfaces

When I filed #267 during the MongoDB completion work, the shape of the bug was already clear: CrossTenantProducer hardcodes three JPA concrete types. The cross-tenant path — cleanup jobs, timer rec...

Things in the wrong place

A session spent finding things that weren’t where they were supposed to be — and putting one of them somewhere it also shouldn’t have been.

Every Query Is a Question of Trust

I’d been putting off multi-tenancy. Not because it was hard to understand — add a column, filter by it — but because “add a column” in a system with 20+ stores across three persistence backends mea...

The module that was never just for testing

Five in-memory store implementations have lived in casehub-work-testing since the project’s first weeks. The name said test artifact. The CDI annotation — @Alternative @Priority(1) — said Tier 2 ov...

Reviewing your way to fewer surprises

The last two issues on my S-scale backlog — conditional outcomes (#177) and JSON Merge Patch for templates (#199) — went through four rounds of spec review before a line of code was written. I went...

Delegation API debt from #245

CI had been red on casehubio/work for a while. Not a flaky test — four separate failures, all from the same root cause: WorkItemService.delegate() grew a DeclineTarget parameter in issue #245, and ...

The 35 chapters DESIGN.md didn't know about

I went in thinking this was a documentation restructuring job. DESIGN.md had 27 numbered phases; the plan was to map those phases to ARC42STORIES.MD chapters, write the layer entries from code, del...

The Sweep and What Stuck

The issue-235-sxs-sweep branch was a batch of seven small-to-medium items. Six shipped. One (#234 — route inbound messages to WorkItem creation) came off the list: blocked on casehub-connectors-cor...

The date in the field

The ExclusionPolicy javadoc had a forward reference to issue #185 where proper extension guidance should be. I wanted that resolved: what the activation pattern looks like, what the excludedUsers f...

The string that looked like a string

The problem with "legal_review" is that it looks exactly like "legal-review". One underscore. WorkBroker does exact string matching — if the engine puts "legal-review" in a case definition and the ...

Extend Was Incomplete

BreachDecision.Extend has been in the sealed hierarchy since we added SlaBreachPolicy — a record that says “give them more time.” Until today, returning it from your policy did nothing. The breach ...

SNAPSHOT Drift: Ten Imports, Two Lessons

The CI failure that kicked off this session was a compile error in casehub-work-ledger: cannot find symbol: class ActorType, location: package io.casehub.ledger.api.model. The cause took thirty sec...

What a Void SPI Costs You

EscalationPolicy.escalate() returned void. That’s a small thing until you need to do something after the policy runs.

The Decision the Policy Returns

EscalationPolicy.escalate() was always a lie. It pretended to be a hook, but it was really fire-and-forget: the expiry service called it and moved on with no way to know what happened next. Escalat...

The Hook That Passed Everything

The pre-push hook checks for squash candidates before anything leaves the machine. The patterns in it look reasonable: ^docs\(claude\):, ^fix\(test\):, ^chore:. It had been in place for weeks. Ever...

The Params That Did Nothing

There’s a particular kind of bug that’s easy to miss in review: code that compiles, ships, and runs fine — it just doesn’t do what it says it does.

The retry that wasn't

The backlog is clear. Everything filed as S or M this session is done — six issues including one I’d underestimated. That felt satisfying until the code reviewer flagged a bug I hadn’t suspected.

A record can't say no

WorkItemCreateRequest had 24 parameters. A new field meant adding it to the end and passing null at every call site you could find. Sometimes you missed one. The record was too honest about its own...

The parent gets the callerRef

A prompt arrived at the start describing three bugs in casehub-work-ledger — JSON built with String.format(), a null guard missing on eventSuffix(), eight tests with wrong expectations. I looked at...

SWF doesn't have a human

The previous entry covered the HumanTask binding design — the sealed BindingTarget, the outbound HumanTaskScheduleHandler, the outputMapping round-trip. Claude had built the implementation in a sep...

The half that was missing

The outbound path from engine binding to WorkItem creation was missing — tracing why WorkerProvisioner couldn't work, and designing HumanTaskScheduleHandler as the correct event-driven hook.

Three bugs hiding behind the wrong error

The BackPressureFailure filling the CI log is noise — the actual failure is an OptimisticLockException buried further down, caused by the M-of-N coordinator racing against itself during concurrent ...

Three audit findings and a wrong mental model

Three audit findings include a String.format JSON builder that silently corrupts output on non-trivial actor IDs, a bug the auditor described with the wrong mechanism, and a Bayesian mental model e...

Distributed SSE and the infrastructure tax

Redis for distributed SSE fan-out is rejected because it would force two new infrastructure pieces on users who have neither Redis nor MongoDB — a pluggable WorkItemEventBroadcaster SPI defers the ...

SLAs, signals, and a connector library

Business-hours SLA and outbound notifications ship, and a notification routing detail that starts as a one-line decision grows into casehub-connectors — a separate repository born from the question...

The primitive and the orchestrator

A data-driven spawn rule table is rejected before a line of code — the right answer is that subprocess orchestration belongs to CaseHub, and quarkus-work should expose the primitive that CaseHub re...

The substrate and what grows on it

Splitting quarkus-work-api into pure contract and quarkus-work-core with CDI implementations reveals that WorkBroker and WorkItems are specialisations of a shared Work substrate that CaseHub also n...

The filter that grew into a contract

A confidence-gated routing rule that could be wired in twenty lines becomes quarkus-work-filter-registry instead — because inline logic is a trap that becomes load-bearing and unremovable.

Examples that prove it

Four runnable examples — expense approval, regulated credit decision, AI content moderation, and document review — together exercise every ledger capability, and the agent: prefix convention derive...