When caching derived state, you must define (and encode) a validity contract: what exact changes make the cached data incorrect, and how that change is detected (keying and/or TTL/refresh).

Practical rules: 1) Key caches on the right change marker

Code sketch (keying on a change marker):

-- Example pattern: include modifiedIndex in the cache key
local tracer, err = core.lrucache.plugin_ctx(
    lrucache,
    api_ctx,
    metadata.modifiedIndex, -- invalidates/rebuilds on metadata change
    create_tracer_obj,
    conf,
    plugin_info
)

Code sketch (don’t skip deletes/empty updates in incremental reconciliation):

local function filter(val)
  if not val.value then
    pending_delete = true
    has_pending = true
    -- record delete so reconciliation removes old entries
    return
  end
  -- ... even if val.value.plugins is empty, still record the update for reconciliation
end

Adhering to these rules prevents security/auth drift, stale behavior that only clears on TTL, and “rebuild-time” bugs caused by inconsistent IDs or non-idempotent refresh logic.