The trust score snapshot table landed back in #183 as a basic foundation — GLOBAL and CAPABILITY scores captured on each computation run, with two query methods. Enough to prove the concept, not enough for qhorus’s compliance evidence export to build a Trust Score History Report on top of.

Issue #203 listed the gaps: no scoreType discriminator (queries inferred type from null checks on capabilityTag), no dimensionKey column (DIMENSION and CAPABILITY_DIMENSION scores weren’t captured at all), no time-range query, and no retention policy. The compliance report needs all of these — without them, it falls back to current-score-only mode with no trajectory data.

The interesting architectural question was tenancyId. The issue spec listed it as a column, but trust computation is cross-tenant by design — TrustScoreJob pulls from CrossTenantLedgerEntryRepository, and ActorTrustScore itself has no tenancy scope. Adding tenancyId to snapshots without adding it to the score entity would be inconsistent — you’d be tagging a cross-tenant computation result with a tenant ID that has no meaning. The protocol that requires tenancyId on per-subject tables (PP-20260616) doesn’t apply here either; snapshots are keyed by actor, not subject. If per-tenant trust ever becomes a thing, the right sequence is tenancyId on ActorTrustScore first, snapshots follow.

The implementation was clean. scoreType and dimensionKey go into the V1000 migration (no production database — schema convention says rewrite in place). PerActorTrustComputer already had the right structure — the DIMENSION and CAPABILITY_DIMENSION loops just needed snapshot saves wired in, with previous-score lookups mirroring what the CAPABILITY loop already did. A deleteOlderThan method on the repository SPI gives retention, wired into TrustScoreJob with a default 365-day window. Set to 0 to keep everything.

The JPQL for the new queries uses fully-qualified enum literals in @NamedQuery annotations rather than parameter binding — s.scoreType = io.casehub.ledger.api.model.ScoreType.GLOBAL instead of a :scoreType parameter. Slightly unusual, but it means the query is self-describing and doesn’t need an extra setParameter call. Hibernate validates it at boot time either way.

The snapshot table is now a genuine time-series store for trust trajectories — all four score types captured, queryable by time range, with automatic retention trimming. Qhorus can build the full compliance report without falling back to the degraded single-point mode.


<
Previous Post
Giving the Workbench a Public API
>
Next Post
Judgment in the Loop