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:
VendorFile(the whole parser/validator) is byte-for-byte identical to the pre-extraction code except a single line —Rails.root.join(CONFIG_PATH)becameMfe.configuration.vendor_file_path— and the initializer wires that path toRails.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.- 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_urlnuance below.
Mapping of the class methods against the original lib/gitlab/mfe.rb:
enabled?= staticmfe.enabledconfig ANDmfe_enabledfeature flag — unchanged (the flag is now wired in the initializer).config_enabled?returnsfalsewhen 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_urlare extracted with their host dependencies injected as callables, rather than left in the monolith. This is what ADR-004 endorses and matchesgitlab-configs. vendor/mfe.ymlstays at the repo root. It is operational pin data baked by a monolith rake task (gitlab:mfe:bake) reading fromRails.root, not gem source. The gem carries its ownspec/fixtures/mfe.ymlcopy for isolated tests.- Gemfile placement in the
:monorepogroup, next togitlab-configs, withfeature_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.
Mfespecs coverenabled?(the config×flag matrix),registry_url(injected value, callable, and the nil-fallback contract), and no-hooks defaults.VendorFilespecs 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 wiredenabled?/registry_url/VendorFilebehaviour against realstub_config/stub_feature_flags; every assertion from the originalspec/lib/gitlab/mfe_spec.rbis 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 documentedregistry_urlnuance) - 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.