Add entry-point guards for Secrets Manager entitlement enforcement (D2)

What does this MR do and why?

D2 of the Secrets Manager entitlement enforcement epic: wires every Secrets Manager write GraphQL mutation and the internal OpenBao audit-log API to D1's entitlement policy, so a blocked/ineligible namespace gets a structured denial instead of the write silently succeeding.

  • Adds SecretsManagement::Entitlement#write_denial_reason and .root_namespace_for (shared "what's the entitlement-relevant namespace" resolution).
  • Adds a SecretsManagement::EnforcesWriteEntitlement mutation concern. It hooks GraphQL-Ruby's #ready? (the framework's pre-resolve filter) to short-circuit denied writes with a structured payload, and declares the new reason field — so each of the 16 write mutations only needs one line: enforces_write_entitlement_for :payload_key. resolve methods are untouched.
  • Adds Types::SecretsManagement::WriteDenialReasonEnum (SecretsManagerWriteDenialReason), exposed as a new, additive, nullable reason field next to the existing errors: [String] on every affected mutation payload — no breaking change to errors.
  • Adds the same entitlement check to ee/lib/api/internal/secrets_manager.rb's audit-log endpoint (returns 403 instead of logging/billing a blocked namespace).
  • Adds test coverage confirming read paths are already hard-denied by D1's policy (EE::GroupPolicy/EE::ProjectPolicy), with no resolver code changes needed.
  • All new behavior is gated behind the existing secrets_manager_paid_experience feature flag; with it off, behavior is unchanged from before this MR.

See the implementation update note on the issue for where this diverges from the original proposal (error shape, feature flag reuse, secretsManagerStartTrial exclusion) and why.

References

How to set up and validate locally

  1. Enable the flag for a top-level group:
    Feature.enable(:secrets_manager_paid_experience, Group.find_by_full_path('my-group'))
  2. Stub or set up an entitlement state that denies writes (e.g. :ineligible, or :blocked with blocked_reason: :trial_expired) for that group.
  3. Call any Secrets Manager write mutation (e.g. projectSecretCreate) against a project/group under that namespace.
  4. Confirm the response has errors: ["Secrets Manager access is restricted for this namespace."] and reason: "INELIGIBLE" / "TRIAL_EXPIRED" / etc., with the payload field null, and that no underlying service call happened.
  5. With the flag disabled, confirm the mutation behaves exactly as before this MR (existing role/permission checks only).

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Merge request reports

Loading
Loading