Extract Gitlab::Mfe into gems/gitlab-mfe

What does this MR do and why?

Extracts the ActiveRecord-free Gitlab::Mfe platform code (the micro-frontend delivery gate, registry URL, and the vendor/mfe.yml pin-file reader) out of lib/gitlab/ into a new in-repo gem at gems/gitlab-mfe, per ADR-004: Library extraction and the library-extraction epic &22390.

The gem stays free of Feature, Gitlab.config and Rails; the monolith injects those runtime hooks via Gitlab::Mfe.configure in config/initializers/mfe.rb, following the gitlab-configs injectable-callback precedent.

Closes #616912 (closed).

Behaviour parity (verified, not just asserted)

This is a pure extraction: the goal is zero behavioural change. Two things establish that with high confidence:

  1. VendorFile (the whole parser/validator) is byte-for-byte identical to the pre-extraction code except a single line — Rails.root.join(CONFIG_PATH) became Mfe.configuration.vendor_file_path — and the initializer wires that path to Rails.root.join(Gitlab::Mfe::VendorFile::CONFIG_PATH), i.e. the exact same value. So the entire (highest-risk) parsing/validation surface is provably unchanged; only the source of the path moved behind an injection seam whose default is identical.
  2. A golden-master characterization harness drove the pinned original implementation and the extracted gem through an exhaustive matrix (7 config-section shapes × flag on/off), asserting identical return values and identical raised-error classes for every case. Result: identical everywhere except the one documented registry_url nuance below.

Mapping of the class methods against the original lib/gitlab/mfe.rb:

  • enabled? = static mfe.enabled config AND mfe_enabled feature flag — unchanged (the flag is now wired in the initializer).
  • config_enabled? returns false when the section is missing — unchanged.

The one caller of the constant at load time (config/initializers/1_settings.rb, which sets Settings.mfe['registry_url'] ||= Gitlab::Mfe::DEFAULT_REGISTRY_URL) keeps working: require 'gitlab/mfe' is added at the top of 1_settings.rb (mirroring the existing require 'gitlab/configs') so the constant is available before the settings run, while the hook-dependent methods are only invoked at runtime, well after config/initializers/mfe.rb has wired them.

The one intentional behavioural nuance: registry_url nil-fallback

The original returned the configured registry_url as-is and only fell back to DEFAULT_REGISTRY_URL when the whole mfe config section was absent (Gitlab::Configs::MissingConfig). So if the section existed but the registry_url was nil — an explicit nil, an absent key, or an empty section — the original returned nil.

In the extracted version the initializer lambda returns nil in the MissingConfig case and the gem applies || DEFAULT_REGISTRY_URL, so any nil registry_url now falls back to the default rather than being returned as nil. In practice this is unreachable today (1_settings.rb sets Settings.mfe['registry_url'] ||= DEFAULT_REGISTRY_URL, so it is never nil), and a nil registry was never a useful value — so this is a strict improvement. It is now locked by a dedicated contract spec in the gem (when the injected registry url resolves to nil) so it can't silently regress. Calling it out explicitly rather than claiming byte-for-byte identical behaviour.

Design decisions (resolved during self-review)

  • Full extraction (not partial). enabled?/registry_url are extracted with their host dependencies injected as callables, rather than left in the monolith. This is what ADR-004 endorses and matches gitlab-configs.
  • vendor/mfe.yml stays at the repo root. It is operational pin data baked by a monolith rake task (gitlab:mfe:bake) reading from Rails.root, not gem source. The gem carries its own spec/fixtures/mfe.yml copy for isolated tests.
  • Gemfile placement in the :monorepo group, next to gitlab-configs, with feature_category: :compliance_management (owner: group::compliance, from the originating MR !249067 (merged)).

Gem name reservation (resolved)

gitlab-mfe is now reserved on RubyGems (0.0.0 stub owned by gitlab_rubygems, confirmed by @rymai), so the publish-readiness validation runs for real and the earlier skip_gem_validation workaround has been removed.

Test coverage

  • Gem: 39 examples, 0 failures. Mfe specs cover enabled? (the config×flag matrix), registry_url (injected value, callable, and the nil-fallback contract), and no-hooks defaults. VendorFile specs cover valid/empty/missing/malformed pins, YAML aliases, duplicate names, and invalid name/version/sha (table-driven), plus frozen-entry and memoization guarantees, and a no-stubs parse of the committed fixture.
  • Monolith: spec/initializers/mfe_spec.rb (8 examples, 0 failures) verifies the wired enabled?/registry_url/VendorFile behaviour against real stub_config/stub_feature_flags; every assertion from the original spec/lib/gitlab/mfe_spec.rb is preserved (one strengthened).
  • Gem RuboCop: 0 offenses.

Note: VendorFile.entries is class-level memoized; on first access two threads could both parse the file, which is benign (idempotent parse, frozen result) and matches the original behaviour.

Author checklist

  • Follows ADR-004 (AR-free gem, host coupling injected)
  • Behaviour parity verified (byte-diff on VendorFile + golden-master characterization on the module methods; one documented registry_url nuance)
  • Tests moved with the code; original assertions preserved; gem suite + monolith initializer spec green
  • RuboCop clean

Part of &22390 (Backend Phase 1 — Extracting cross-cutting libraries into gems), under the GitLab Rails Decomposition effort &22372.

Edited by Gregory Havenga

Merge request reports

Loading
Loading