Notes | CaseHub - Ledger
The Query That Only Failed at Hour One
The health job’s gap-detection query had been wrong for a while. Not subtly wrong — wrong in a way that Hibernate 6 explicitly refuses to execute. The HAVING clause did arithmetic on MIN() and MAX(...
The Race That Wasn't
The issue title left nothing ambiguous: “ON CONFLICT DO NOTHING masks concurrent data race instead of fixing it.” The analysis was thorough — traced the commit history back to the change, named the...
The Source Code Says So
Four issues landed today on a single branch, and the most interesting thing about three of them is what I didn’t know going in.
The Compromise That Was Already Global
KeyRotationRepository had the same structural gap as the identity binding repository from the previous session: findByActorId returned results from any tenant. The fix looked identical too — add te...
The Repository That Stopped Short
The previous session closed three issues and left two open. Issue #144: JpaActorIdentityBindingRepository.save() never called frontierRepo.replace(). Issue #145: latestBindingFor and bindingHistory...
The Tenant Whose Key Was Always the Same
Three fixes landed on this branch. One of them turned out to be about cryptographic correctness, not health monitoring.
The guard that found its home
The guard that found its home
The Flag That Wasn’t a Gate
The Flag That Wasn’t a Gate
The Tenant That Was Always There
Engine had already solved this problem. When we added tenancyId to casehub-engine a week ago, the first instinct was elegant: inject CurrentPrincipal, read tenancyId() in each repository, filter si...
The Property That Wouldn’t Move
The Property That Wouldn’t Move
Trust scores were always stale — now they don't have to be
Trust scores in casehub-ledger are a materialized view. The nightly batch job computes Bayesian Beta scores from attestation history, writes them to actor_trust_score, and fires CDI events so consu...
CaseHub Ledger — The Observer That Couldn’t Wait
Trust scores that update when attestations arrive, not when the clock ticks. The race condition in the obvious approach, the extraction that made it possible, and the CDI test gotcha that nearly de...
The save() that forgot to count
JpaLedgerEntryRepository.save() never assigned sequenceNumber. Every JPA-persisted entry got 0 — the primitive default. The in-memory implementation had it right from the start: ConcurrentHashMap&l...
The Squash That Needed Surgery
A simple history squash revealed a hidden merge commit, the non-obvious patch-id detection behind 'previously applied', and a documentation audit that should have happened before the write.
Reading the Ledger Backwards
Completing ARC42STORIES.MD with no DESIGN.md meant reading 16 blog entries first — the only honest source for why the code is shaped the way it is.
The log that wouldn't be caught
Six S-scale issues. The kind of work that sounds small until you’re doing it — one
test gap here, one undocumented method there, one missing @BuildStep. Each takes
thirty seconds to describe and th...
The write that commits before returning
QuarkMind needs trust-weighted routing for four game plugins — strategy, economics,
tactics, scouting — at roughly game-loop frequency. What it doesn’t need is Merkle
chain verification, DID/VC ide...
The Infrastructure That Found Its Address
The last few sessions assembled ledger’s identity pipeline — DID binding, SCIM lookup,
key rotation, VC validation. It worked. It just happened to live in entirely the wrong place.
The Cache That Knew When to Forget
The branch before this one proved that ledger entries can be cryptographically bound to W3C DIDs. This one asked the harder operational question: what happens to the caches when a key rotates?
Signing Proves Someone Signed — DID Proves Who
The bilateral signing added in the previous branch proves that someone signed a ledger
entry with the private key for a given actorId. It does not prove that the person holding
that key is actually...
The wrong fix for the right problem
The bug report was straightforward. JDBC-only consumers — casehub-aml, casehub-clinical, casehub-devtown — failed to build after we shipped the reactive key rotation service. Quarkus CDI validation...
How provider-agnostic really works
The KeyRotationService had blocking methods. The platform rule says all ledger service methods must ship both blocking and Uni<T> reactive variants. Adding the reactive variants looked routin...
The architecture of trust evidence
Two design decisions from this week’s ledger work keep rattling around, and I want to write them down while they’re fresh.
Quality at the Intersection
Knowing an agent is trusted for security reviews doesn't tell you whether they're thorough — adding CAPABILITY_DIMENSION as a fourth trust score type and replacing a leaky scope_key encoding with t...
Trust Without Ceremony
Adding a trust read-model and import SPI to casehub-ledger so actors arriving from other deployments can bring their reputation with them — without requiring configuration switches in consumer repos.
The Proxy and the Bean
A static ThreadLocal on an @ApplicationScoped bean sidesteps @RequestScoped's dependency on an active HTTP context, making @ProvenanceCapture work in scheduled jobs and @QuarkusTest without a runti...
Two Models for Trust
Dimension scores are continuous magnitudes, not binary verdicts, so they need a decay-weighted average rather than a Beta accumulator — passing AttestationVerdict.SOUND forces pure time decay and d...
When the Papers Disagree
Three peer-reviewed papers reach three incompatible conclusions on global trust aggregation — the right answer is a pluggable GlobalScoreStrategy SPI rather than picking one framework's position.
The Sentinel, This Time
For capabilityTag, the sentinel "*" is the right answer where NULL was right for scope_key — query patterns use the same operator as any scoped query, and every major policy system already uses "*"...
What the Reviews Missed
Five deferred issues ship in one push, but the session's main discovery is that a sentinel empty-string for global scope was unnecessary — the previous session already solved the uniqueness constra...
The Fix That Would Have Broken Everything
A plausible commit hardcodes @PersistenceUnit("qhorus") into a generic extension library — caught in git log review before it shipped, with the correct fix being a DefaultBean producer pattern in t...
Routing Signals, a Health Check, and the Claude That Went Off-Script
Using distinct CDI event record types — full payload, delta, computed-at timestamp — as a strategy selector lets consumers receive only the trust score signals they need without a configuration enum.
traceId, Entity Listeners, and a Gap I Shouldn't Have Left
Renaming correlationId to traceId fixes a naming collision with established messaging terminology, and a CDI entity listener wires automatic OTel trace ID population at @PrePersist time.
Trust Without Memory
W3C PROV-DM, NIST AI standards, and multi-agent framework research converge on the same answer for LLM agent identity: a stable dereferenceable URI bound to the system configuration, not the session.
When the Paper Is Wrong
Implementing EigenTrust transitivity reveals that the original paper's trust matrix normalisation produces incorrect results for pre-trusted peers — the fix derives from first principles rather tha...
Documentation That Lies
Rating each capability for enterprise applicability forces an honest assessment — EigenTrust transitivity earns two stars in 2026 not because it is wrong but because enterprise AI agent meshes have...
Forgiveness Was a Patch
The ForgivenessParams mechanism gets replaced entirely when Bayesian Beta replaces the coarse-grained scoring model — a principled prior that treats uncertainty correctly makes the forgiveness patc...
No Panache in the Model
Stripping Panache from all extension entities removes a framework dependency consumers didn't choose, and @NamedQuery replaces the static Panache shorthand with startup-validated queries that fail ...
A Clean Entity
Converting LedgerEntry from PanacheEntityBase to a plain @Entity removes the Panache dependency that was blocking Qhorus's reactive migration and unblocks any future consumer choosing a different p...
Catching the Docs Before They Hit a Consumer
The Merkle sprint deleted LedgerHashChain without updating the integration guide, leaving consumers with compile-error-inducing sample code that pointed nowhere — a systematic cleanup removes every...
Teaching the Ledger to Speak W3C
LedgerEntry exports as W3C PROV-JSON-LD per subject, deduplicating agents across entries and emitting both sequential and cross-subject wasDerivedFrom edges so regulators get a complete provenance ...
From O(N) to O(log N)
The Merkle Mountain Range structure from RFC 9162 provides O(log N) frontier storage and tamper-evident inclusion proofs signed with Ed25519, making verification genuinely independent of the operator.
Two Fields in the Wrong Place
A research pass across ten directions produces an ADR grounding the forgiveness mechanism in two parameters — recency and frequency — after finding that severity double-counts a signal already pres...
Extracting a Shared Audit Ledger for the Quarkus AI Ecosystem
Qhorus needing Tarkus's audit patterns prompts extraction into a shared library — LedgerEntry uses JPA JOINED inheritance so each domain adds its own subclass table without touching the base schema.