Claude added @PersistenceUnit("qhorus") to six runtime beans in casehub-ledger without being asked. The commit message was plausible: “Qualifies EntityManager injections so beans resolve correctly when only the qhorus datasource is configured.”

I caught it reviewing git log the next session. The commit was gone within minutes.

Why it was wrong

casehub-ledger is a generic extension. Hardcoding "qhorus" in JpaLedgerEntryRepository, LedgerErasureService, TrustScoreJob, and the rest means every consumer that isn’t Qhorus fails with the mirror error — "Unsatisfied dependency for type EntityManager and qualifiers [@PersistenceUnit("qhorus")]". CaseHub would break. casehub-work would break. Any future consumer with a different datasource name would break on startup.

The error Claude was fixing was real: Claudony configures only a named qhorus datasource with no default, and @Default EntityManager is absent from the CDI context when that’s the case. The symptom pointed at a missing annotation. The wrong response was to add the consumer’s annotation to the extension.

The right fix

The extension doesn’t know — and shouldn’t know — what datasource its consumers configure. The right design is a config key that lets each consumer tell the extension which persistence unit to use:

# Only needed when the app has no default datasource
quarkus.ledger.datasource=qhorus

We built this with a @LedgerPersistenceUnit CDI qualifier and a producer that reads the config and selects the right EntityManager via Instance<EntityManager>.select() at startup:

@Produces @LedgerPersistenceUnit
public EntityManager produce(@Any Instance<EntityManager> instance) {
    String pu = config.datasource().orElse("").trim();
    if (pu.isBlank()) {
        return instance.select(Default.Literal.INSTANCE).get();
    }
    return instance.select(new PersistenceUnitLiteral(pu)).get();
}

All eight injection points in the runtime module now use @LedgerPersistenceUnit. The fix is IntelliJ-stable — it’s a real CDI annotation the IDE understands, not a string-valued annotation that import optimisers strip.

Moving to casehubio

The repo transferred to the casehubio GitHub organisation. I’m now a shared owner there alongside the casehub-engine and the rest of the ecosystem. The artifact coordinates stay the same — only the GitHub URL changed.

The shared BOM

The transfer also pushed the BOM question into focus. As more repos move to casehubio, they need a shared way to manage dependency versions. The pattern from Quarkiverse, SmallRye, and Apache is a <org-name>-parent pom that serves as both parent and BOM. That repo is now at casehubio/parent.

I chose BOM-import rather than inheritance for the initial wire-up. The projects in the ecosystem have heterogeneous parents and a shared inheritance chain would force awkward multi-level structures. Importing the BOM in <dependencyManagement> avoids that without giving up centralised version management.


<
Previous Post
WorkBroker: wiring the shared SPI into casehub-engine
>
Next Post
The config mapping trap