Derive Secrets Manager trial state on read

What

Cache CDot's raw GET /trials body instead of the parsed SecretsManagerTrialResponse, and parse on read.

Why

state is derived by comparing the trial's expires_at to Time.current. Caching the parsed object freezes that comparison for the cache TTL, so a cached :trial keeps reporting :trial after the trial has actually ended. Entitlement::Resolver#blocked_reason_from then labels the resulting denial :credits_exhausted instead of :trial_expired.

Access is unaffected — permits_access? covers both :trial and :paid. What is wrong is the reported state: GraphQL entitlement_state and denial telemetry.

How

  • RawTrialPayload (raw body + cache_ttl) is what goes into Rails.cache.
  • SecretsManagerTrialResponse is built inside the SafeRequestStore block, so it is parsed once per request and re-derived on the next one.
  • derive_secrets_manager_trial_state is unchanged and remains the only place the clock is consulted.

Re-parsing costs ~14 µs more per read than deserializing the pre-derived object, against a Redis round-trip of hundreds of µs.

Note on merge order

No cache-key version bump is needed because CDot currently sends Cache-Control: max-age=0, so nothing is written to Rails.cache today. That stops being true once customers-gitlab-com!17034 ships a real max-age — this MR should merge first, otherwise the cache fills with the old shape and the new reader breaks for up to one TTL.

Merge request reports

Loading
Loading