Skip secrets_stored billing events for opted-out namespaces
What does this MR do and why?
When a root namespace opts out of the GitLab Secrets Manager, secret writes are blocked, so its stored-secret counts stay above zero indefinitely. The daily billing job still processes every root namespace with a non-zero count, which meant opted-out namespaces kept emitting a daily secrets_stored billing event forever and kept being billed.
This MR adds a guard to the billing event emitter so it returns early when the root namespace has opted out. All emissions go through this emitter, so the guard covers every path, and it runs before the secret-count query, so opted-out namespaces skip that query as well. Two new specs verify that an opted-out namespace emits no billing event and that a namespace with an active enrollment still emits, so the guard only applies to the disabled state.
References
- Resolves https://gitlab.com/gitlab-org/gitlab/-/work_items/622364 (confidential issue)
Screenshots or screen recordings
Not applicable — backend-only change with no UI.
How to set up and validate locally
In a rails console:
-
Enable the feature flag:
Feature.enable(:secrets_manager_emit_secret_stored_events) -
Pick a root group and seed a secret count row:
group = Group.find_by_full_path('<your-root-group>') SecretsManagement::NamespaceSecretCount.upsert_all([{ namespace_id: group.id, root_namespace_id: group.id, count: 5 }], unique_by: :namespace_id) -
Opt the group out:
SecretsManagement::NamespaceEnrollment.create!(namespace: group, disabled_at: Time.current) -
Run the emitter and confirm no billing event is tracked:
SecretsManagement::BillableEvents::SecretsStoredEmitter.new(group).emit!It returns early and
Gitlab::BillingEvents::Client.track_billing_eventis not called. Before this fix, it emitted asecrets_storedevent with quantity 5. -
Remove
disabled_atand re-run to confirm the event is emitted again:SecretsManagement::NamespaceEnrollment.find_by(namespace_id: group.id).update!(disabled_at: nil) SecretsManagement::BillableEvents::SecretsStoredEmitter.new(group).emit!
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.