diff --git a/db/docs/merge_requests_approval_rules_groups.yml b/db/docs/merge_requests_approval_rules_groups.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b8a52e7811f91184d6a280ccaadb6a0e642797d6
--- /dev/null
+++ b/db/docs/merge_requests_approval_rules_groups.yml
@@ -0,0 +1,12 @@
+---
+table_name: merge_requests_approval_rules_groups
+classes:
+  - MergeRequests::ApprovalRulesGroup
+feature_categories:
+  - code_review_workflow
+description: Stores relationship between approval rules v2 and groups
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179861
+milestone: '17.9'
+gitlab_schema: gitlab_main_cell
+sharding_key:
+  group_id: namespaces
diff --git a/db/docs/merge_requests_approval_rules_merge_requests.yml b/db/docs/merge_requests_approval_rules_merge_requests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ec7130a8a9ada913a5e403067c16c7cfcdf8bbfc
--- /dev/null
+++ b/db/docs/merge_requests_approval_rules_merge_requests.yml
@@ -0,0 +1,12 @@
+---
+table_name: merge_requests_approval_rules_merge_requests
+classes:
+  - MergeRequests::ApprovalRulesMergeRequest
+feature_categories:
+  - code_review_workflow
+description: Stores relationship between approval rules v2 and merge requests
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179861
+milestone: '17.9'
+gitlab_schema: gitlab_main_cell
+sharding_key:
+  project_id: projects
diff --git a/db/docs/merge_requests_approval_rules_projects.yml b/db/docs/merge_requests_approval_rules_projects.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bbf00b7348c476036452530b3dbb42caed46d7c9
--- /dev/null
+++ b/db/docs/merge_requests_approval_rules_projects.yml
@@ -0,0 +1,12 @@
+---
+table_name: merge_requests_approval_rules_projects
+classes:
+  - MergeRequests::ApprovalRulesProject
+feature_categories:
+  - code_review_workflow
+description: Stores relationship between approval rules v2 and projects
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/179861
+milestone: '17.9'
+gitlab_schema: gitlab_main_cell
+sharding_key:
+  project_id: projects
diff --git a/db/migrate/20250123151708_create_merge_requests_approval_rules_groups.rb b/db/migrate/20250123151708_create_merge_requests_approval_rules_groups.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e873138551f8eabea95ad7430bfa7a918da54ba2
--- /dev/null
+++ b/db/migrate/20250123151708_create_merge_requests_approval_rules_groups.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestsApprovalRulesGroups < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+
+  def change
+    create_table :merge_requests_approval_rules_groups do |t| # -- Migration/EnsureFactoryForTable false positive
+      t.bigint :approval_rule_id, null: false
+      t.bigint :group_id, null: false
+      t.index :group_id
+      t.timestamps_with_timezone null: false
+    end
+
+    add_index(
+      :merge_requests_approval_rules_groups,
+      %i[approval_rule_id group_id],
+      unique: true,
+      name: 'index_mrs_ars_groups_on_ar_id_and_group_id'
+    )
+  end
+end
diff --git a/db/migrate/20250123151726_create_merge_requests_approval_rules_merge_requests.rb b/db/migrate/20250123151726_create_merge_requests_approval_rules_merge_requests.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a67fe56a775879b3a8d595e2b20a055bb91ec7bd
--- /dev/null
+++ b/db/migrate/20250123151726_create_merge_requests_approval_rules_merge_requests.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestsApprovalRulesMergeRequests < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+
+  def change
+    create_table :merge_requests_approval_rules_merge_requests do |t| # Migration/EnsureFactoryForTable false positive
+      t.bigint :approval_rule_id, null: false
+      t.bigint :merge_request_id, null: false
+      t.bigint :project_id, null: false
+      t.index :merge_request_id, name: 'index_mrs_approval_rules_mrs_on_mr_id'
+      t.index :project_id, name: 'index_mrs_approval_rules_mrs_on_project_id'
+      t.timestamps_with_timezone null: false
+    end
+
+    add_index(
+      :merge_requests_approval_rules_merge_requests,
+      %i[approval_rule_id merge_request_id],
+      unique: true,
+      name: 'index_mrs_ars_mrs_on_ar_id_and_mr_id'
+    )
+  end
+end
diff --git a/db/migrate/20250123151745_create_merge_requests_approval_rules_projects.rb b/db/migrate/20250123151745_create_merge_requests_approval_rules_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b87ec1ace48ae046bce324ed2b3c16789093ed3a
--- /dev/null
+++ b/db/migrate/20250123151745_create_merge_requests_approval_rules_projects.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestsApprovalRulesProjects < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+
+  def change
+    create_table :merge_requests_approval_rules_projects do |t| # -- Migration/EnsureFactoryForTable false positive
+      t.bigint :approval_rule_id, null: false
+      t.bigint :project_id, null: false
+      t.index :project_id, name: 'index_mrs_approval_rules_projects_on_project_id'
+      t.timestamps_with_timezone null: false
+    end
+
+    add_index(
+      :merge_requests_approval_rules_projects,
+      %i[approval_rule_id project_id],
+      unique: true,
+      name: 'index_mrs_ars_projects_on_ar_id_and_project_id'
+    )
+  end
+end
diff --git a/db/migrate/20250206143903_add_merge_requests_approval_rules_groups_approval_rule_fk.rb b/db/migrate/20250206143903_add_merge_requests_approval_rules_groups_approval_rule_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..205b9a21c4596a6ecb3c253f23c916d4b376046a
--- /dev/null
+++ b/db/migrate/20250206143903_add_merge_requests_approval_rules_groups_approval_rule_fk.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesGroupsApprovalRuleFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_groups, :merge_requests_approval_rules,
+      column: :approval_rule_id, on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_groups, column: :approval_rule_id
+    end
+  end
+end
diff --git a/db/migrate/20250206143924_add_merge_requests_approval_rules_groups_group_fk.rb b/db/migrate/20250206143924_add_merge_requests_approval_rules_groups_group_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35db8abf4702f8b82f2bb624819b7038fe8c7598
--- /dev/null
+++ b/db/migrate/20250206143924_add_merge_requests_approval_rules_groups_group_fk.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesGroupsGroupFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_groups, :namespaces, column: :group_id,
+      on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_groups, column: :group_id
+    end
+  end
+end
diff --git a/db/migrate/20250206144004_add_merge_requests_approval_rules_projects_approval_rule_fk.rb b/db/migrate/20250206144004_add_merge_requests_approval_rules_projects_approval_rule_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..740ca025d72897fe7dc8097f3381d1441bada689
--- /dev/null
+++ b/db/migrate/20250206144004_add_merge_requests_approval_rules_projects_approval_rule_fk.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesProjectsApprovalRuleFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_projects, :merge_requests_approval_rules,
+      column: :approval_rule_id, on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_projects, column: :approval_rule_id
+    end
+  end
+end
diff --git a/db/migrate/20250206144027_add_merge_requests_approval_rules_projects_project_fk.rb b/db/migrate/20250206144027_add_merge_requests_approval_rules_projects_project_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..70dea81b3e8c42974f1dff12eef6c5a6b3f8c1ab
--- /dev/null
+++ b/db/migrate/20250206144027_add_merge_requests_approval_rules_projects_project_fk.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesProjectsProjectFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_projects, :projects, column: :project_id,
+      on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_projects, column: :project_id
+    end
+  end
+end
diff --git a/db/migrate/20250206144049_add_merge_requests_approval_rules_mrs_approval_rule_fk.rb b/db/migrate/20250206144049_add_merge_requests_approval_rules_mrs_approval_rule_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb877d49cb85a34471c85be909bb26f069825769
--- /dev/null
+++ b/db/migrate/20250206144049_add_merge_requests_approval_rules_mrs_approval_rule_fk.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesMrsApprovalRuleFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_merge_requests, :merge_requests_approval_rules,
+      column: :approval_rule_id, on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_merge_requests, column: :approval_rule_id
+    end
+  end
+end
diff --git a/db/migrate/20250206144110_add_merge_requests_approval_rules_mrs_mr_fk.rb b/db/migrate/20250206144110_add_merge_requests_approval_rules_mrs_mr_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..222f73bdce3d2960f3bfe23d09c7929fbc7508b0
--- /dev/null
+++ b/db/migrate/20250206144110_add_merge_requests_approval_rules_mrs_mr_fk.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesMrsMrFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_merge_requests, :merge_requests,
+      column: :merge_request_id,
+      on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_merge_requests, column: :merge_request_id
+    end
+  end
+end
diff --git a/db/migrate/20250211141110_add_merge_requests_approval_rules_mrs_project_fk.rb b/db/migrate/20250211141110_add_merge_requests_approval_rules_mrs_project_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..430ff31f2442436513d60fb9410a9061764c319e
--- /dev/null
+++ b/db/migrate/20250211141110_add_merge_requests_approval_rules_mrs_project_fk.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsApprovalRulesMrsProjectFk < Gitlab::Database::Migration[2.2]
+  milestone '17.9'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :merge_requests_approval_rules_merge_requests, :projects,
+      column: :project_id,
+      on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :merge_requests_approval_rules_merge_requests, column: :project_id
+    end
+  end
+end
diff --git a/db/schema_migrations/20250123151708 b/db/schema_migrations/20250123151708
new file mode 100644
index 0000000000000000000000000000000000000000..87a1ad0236b30a48f7ae9437fdd1c5b81800bad7
--- /dev/null
+++ b/db/schema_migrations/20250123151708
@@ -0,0 +1 @@
+620def37e032e7fa463c4f1ecff0aacde33a5b9bd7a56c2f6ead418b3f4ce828
\ No newline at end of file
diff --git a/db/schema_migrations/20250123151726 b/db/schema_migrations/20250123151726
new file mode 100644
index 0000000000000000000000000000000000000000..0d54c5d1bc08d4510c261243d1bf7eb7825ebc72
--- /dev/null
+++ b/db/schema_migrations/20250123151726
@@ -0,0 +1 @@
+e5a26f1a31603cf3ac570824874afbeac3bb970ca079bdaaebbf9cbe13155230
\ No newline at end of file
diff --git a/db/schema_migrations/20250123151745 b/db/schema_migrations/20250123151745
new file mode 100644
index 0000000000000000000000000000000000000000..2ce7ec05eaa1d5fdfedc100a7d31582067a9e7b6
--- /dev/null
+++ b/db/schema_migrations/20250123151745
@@ -0,0 +1 @@
+d6fc925f97583a4509894202b2e529ae64138f2cceef76e0d0c4dcaafe70f45a
\ No newline at end of file
diff --git a/db/schema_migrations/20250206143903 b/db/schema_migrations/20250206143903
new file mode 100644
index 0000000000000000000000000000000000000000..fc7b302235973f82dddf3e57fd27ff1e27fc1a98
--- /dev/null
+++ b/db/schema_migrations/20250206143903
@@ -0,0 +1 @@
+56101662487483f6866c2c1f2cd0ee66d1fa12d08c0da12bd550d389e37f6bf7
\ No newline at end of file
diff --git a/db/schema_migrations/20250206143924 b/db/schema_migrations/20250206143924
new file mode 100644
index 0000000000000000000000000000000000000000..724ba0d42f826bc59ab23e8e2768b555b0f8bc77
--- /dev/null
+++ b/db/schema_migrations/20250206143924
@@ -0,0 +1 @@
+4b740e775e43ed2143b7060b5e5e1730aeb7309cc83e4d76a5316cc84e80893b
\ No newline at end of file
diff --git a/db/schema_migrations/20250206144004 b/db/schema_migrations/20250206144004
new file mode 100644
index 0000000000000000000000000000000000000000..8ccb8a71b94f95903a7ac4228c43438e80a3b729
--- /dev/null
+++ b/db/schema_migrations/20250206144004
@@ -0,0 +1 @@
+31f9cc1659cd680b16a742eddeb361c4eb059388b1af0136d0d5a648a6fb8cca
\ No newline at end of file
diff --git a/db/schema_migrations/20250206144027 b/db/schema_migrations/20250206144027
new file mode 100644
index 0000000000000000000000000000000000000000..99653db12ffd2c6ca3720d127088b29ab21df40b
--- /dev/null
+++ b/db/schema_migrations/20250206144027
@@ -0,0 +1 @@
+08d74afd22511618546821c8cc53473ec3a9f6ec4be3c80a20086e1a2e71da37
\ No newline at end of file
diff --git a/db/schema_migrations/20250206144049 b/db/schema_migrations/20250206144049
new file mode 100644
index 0000000000000000000000000000000000000000..3cbc804941579b641604ec52f8e3456ac3c1f795
--- /dev/null
+++ b/db/schema_migrations/20250206144049
@@ -0,0 +1 @@
+1611b7751ef4fa7476f9ffeab5263f9e0e85f89ab21e3028b2d5245dd38f31db
\ No newline at end of file
diff --git a/db/schema_migrations/20250206144110 b/db/schema_migrations/20250206144110
new file mode 100644
index 0000000000000000000000000000000000000000..49eba2314f5c5a4058752b05933b77a5e4b549ea
--- /dev/null
+++ b/db/schema_migrations/20250206144110
@@ -0,0 +1 @@
+cd304e944ce13f05dc95d6c74a604bca579a04b08d9abf0fd143ae03c00f1390
\ No newline at end of file
diff --git a/db/schema_migrations/20250211141110 b/db/schema_migrations/20250211141110
new file mode 100644
index 0000000000000000000000000000000000000000..c1b01af91f496c4f9f956daac558f79e619d617c
--- /dev/null
+++ b/db/schema_migrations/20250211141110
@@ -0,0 +1 @@
+69fd6b83d6c739006134ada0d7019b9c04ea5d6ae34b3dc4906546a2c3070110
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index a8694caae232ca4c3943a4731b46b92d84c7bec7..fc3c1bee2f851114659eb16c8142a9386774978a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -16330,6 +16330,23 @@ CREATE TABLE merge_requests_approval_rules (
     CONSTRAINT check_c7c36145b7 CHECK ((char_length(name) <= 255))
 );
 
+CREATE TABLE merge_requests_approval_rules_groups (
+    id bigint NOT NULL,
+    approval_rule_id bigint NOT NULL,
+    group_id bigint NOT NULL,
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE merge_requests_approval_rules_groups_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE merge_requests_approval_rules_groups_id_seq OWNED BY merge_requests_approval_rules_groups.id;
+
 CREATE SEQUENCE merge_requests_approval_rules_id_seq
     START WITH 1
     INCREMENT BY 1
@@ -16339,6 +16356,41 @@ CREATE SEQUENCE merge_requests_approval_rules_id_seq
 
 ALTER SEQUENCE merge_requests_approval_rules_id_seq OWNED BY merge_requests_approval_rules.id;
 
+CREATE TABLE merge_requests_approval_rules_merge_requests (
+    id bigint NOT NULL,
+    approval_rule_id bigint NOT NULL,
+    merge_request_id bigint NOT NULL,
+    project_id bigint NOT NULL,
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE merge_requests_approval_rules_merge_requests_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE merge_requests_approval_rules_merge_requests_id_seq OWNED BY merge_requests_approval_rules_merge_requests.id;
+
+CREATE TABLE merge_requests_approval_rules_projects (
+    id bigint NOT NULL,
+    approval_rule_id bigint NOT NULL,
+    project_id bigint NOT NULL,
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE merge_requests_approval_rules_projects_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE merge_requests_approval_rules_projects_id_seq OWNED BY merge_requests_approval_rules_projects.id;
+
 CREATE TABLE merge_requests_closing_issues (
     id bigint NOT NULL,
     merge_request_id bigint NOT NULL,
@@ -25589,6 +25641,12 @@ ALTER TABLE ONLY merge_requests ALTER COLUMN id SET DEFAULT nextval('merge_reque
 
 ALTER TABLE ONLY merge_requests_approval_rules ALTER COLUMN id SET DEFAULT nextval('merge_requests_approval_rules_id_seq'::regclass);
 
+ALTER TABLE ONLY merge_requests_approval_rules_groups ALTER COLUMN id SET DEFAULT nextval('merge_requests_approval_rules_groups_id_seq'::regclass);
+
+ALTER TABLE ONLY merge_requests_approval_rules_merge_requests ALTER COLUMN id SET DEFAULT nextval('merge_requests_approval_rules_merge_requests_id_seq'::regclass);
+
+ALTER TABLE ONLY merge_requests_approval_rules_projects ALTER COLUMN id SET DEFAULT nextval('merge_requests_approval_rules_projects_id_seq'::regclass);
+
 ALTER TABLE ONLY merge_requests_closing_issues ALTER COLUMN id SET DEFAULT nextval('merge_requests_closing_issues_id_seq'::regclass);
 
 ALTER TABLE ONLY merge_requests_compliance_violations ALTER COLUMN id SET DEFAULT nextval('merge_requests_compliance_violations_id_seq'::regclass);
@@ -28150,9 +28208,18 @@ ALTER TABLE ONLY merge_request_reviewers
 ALTER TABLE ONLY merge_request_user_mentions
     ADD CONSTRAINT merge_request_user_mentions_pkey PRIMARY KEY (id);
 
+ALTER TABLE ONLY merge_requests_approval_rules_groups
+    ADD CONSTRAINT merge_requests_approval_rules_groups_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY merge_requests_approval_rules_merge_requests
+    ADD CONSTRAINT merge_requests_approval_rules_merge_requests_pkey PRIMARY KEY (id);
+
 ALTER TABLE ONLY merge_requests_approval_rules
     ADD CONSTRAINT merge_requests_approval_rules_pkey PRIMARY KEY (id);
 
+ALTER TABLE ONLY merge_requests_approval_rules_projects
+    ADD CONSTRAINT merge_requests_approval_rules_projects_pkey PRIMARY KEY (id);
+
 ALTER TABLE ONLY merge_requests_closing_issues
     ADD CONSTRAINT merge_requests_closing_issues_pkey PRIMARY KEY (id);
 
@@ -33556,6 +33623,8 @@ CREATE INDEX index_merge_request_reviewers_on_user_id ON merge_request_reviewers
 
 CREATE UNIQUE INDEX index_merge_request_user_mentions_on_note_id ON merge_request_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL);
 
+CREATE INDEX index_merge_requests_approval_rules_groups_on_group_id ON merge_requests_approval_rules_groups USING btree (group_id);
+
 CREATE INDEX index_merge_requests_approval_rules_on_group_id ON merge_requests_approval_rules USING btree (group_id);
 
 CREATE INDEX index_merge_requests_approval_rules_on_project_id ON merge_requests_approval_rules USING btree (project_id);
@@ -33734,6 +33803,18 @@ CREATE INDEX index_mr_metrics_on_target_project_id_merged_at_nulls_last ON merge
 
 CREATE INDEX index_mr_metrics_on_target_project_id_merged_at_time_to_merge ON merge_request_metrics USING btree (target_project_id, merged_at, created_at) WHERE (merged_at > created_at);
 
+CREATE INDEX index_mrs_approval_rules_mrs_on_mr_id ON merge_requests_approval_rules_merge_requests USING btree (merge_request_id);
+
+CREATE INDEX index_mrs_approval_rules_mrs_on_project_id ON merge_requests_approval_rules_merge_requests USING btree (project_id);
+
+CREATE INDEX index_mrs_approval_rules_projects_on_project_id ON merge_requests_approval_rules_projects USING btree (project_id);
+
+CREATE UNIQUE INDEX index_mrs_ars_groups_on_ar_id_and_group_id ON merge_requests_approval_rules_groups USING btree (approval_rule_id, group_id);
+
+CREATE UNIQUE INDEX index_mrs_ars_mrs_on_ar_id_and_mr_id ON merge_requests_approval_rules_merge_requests USING btree (approval_rule_id, merge_request_id);
+
+CREATE UNIQUE INDEX index_mrs_ars_projects_on_ar_id_and_project_id ON merge_requests_approval_rules_projects USING btree (approval_rule_id, project_id);
+
 CREATE INDEX index_namespace_admin_notes_on_namespace_id ON namespace_admin_notes USING btree (namespace_id);
 
 CREATE UNIQUE INDEX index_namespace_aggregation_schedules_on_namespace_id ON namespace_aggregation_schedules USING btree (namespace_id);
@@ -38603,6 +38684,9 @@ ALTER TABLE ONLY observability_traces_issues_connections
 ALTER TABLE ONLY merge_request_assignment_events
     ADD CONSTRAINT fk_08f7602bfd FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_groups
+    ADD CONSTRAINT fk_094b4086a3 FOREIGN KEY (approval_rule_id) REFERENCES merge_requests_approval_rules(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY catalog_resource_component_last_usages
     ADD CONSTRAINT fk_094c686785 FOREIGN KEY (component_project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
@@ -38762,6 +38846,9 @@ ALTER TABLE ONLY agent_project_authorizations
 ALTER TABLE ONLY ai_agent_version_attachments
     ADD CONSTRAINT fk_1d4253673b FOREIGN KEY (ai_vectorizable_file_id) REFERENCES ai_vectorizable_files(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_merge_requests
+    ADD CONSTRAINT fk_1d49645a27 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY design_management_versions
     ADD CONSTRAINT fk_1dccb304f8 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
 
@@ -39083,6 +39170,9 @@ ALTER TABLE ONLY incident_management_timeline_events
 ALTER TABLE ONLY todos
     ADD CONSTRAINT fk_45054f9c45 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_projects
+    ADD CONSTRAINT fk_451a9dfe93 FOREIGN KEY (approval_rule_id) REFERENCES merge_requests_approval_rules(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY security_policy_requirements
     ADD CONSTRAINT fk_458f7f5ad5 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
 
@@ -39200,6 +39290,9 @@ ALTER TABLE ONLY approval_merge_request_rules
 ALTER TABLE ONLY deploy_keys_projects
     ADD CONSTRAINT fk_58a901ca7e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_groups
+    ADD CONSTRAINT fk_59068f09e5 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY oauth_access_grants
     ADD CONSTRAINT fk_59cdb2323c FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
 
@@ -39233,6 +39326,9 @@ ALTER TABLE ONLY dast_scanner_profiles_builds
 ALTER TABLE ONLY protected_environment_deploy_access_levels
     ADD CONSTRAINT fk_5d9b05a7e9 FOREIGN KEY (protected_environment_project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_merge_requests
+    ADD CONSTRAINT fk_5ddc4a2f7b FOREIGN KEY (approval_rule_id) REFERENCES merge_requests_approval_rules(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY issue_assignees
     ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
 
@@ -39374,6 +39470,9 @@ ALTER TABLE ONLY index_statuses
 ALTER TABLE ONLY abuse_report_notes
     ADD CONSTRAINT fk_74e1990397 FOREIGN KEY (abuse_report_id) REFERENCES abuse_reports(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY merge_requests_approval_rules_merge_requests
+    ADD CONSTRAINT fk_74e3466397 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY software_license_policies
     ADD CONSTRAINT fk_74f6d8328a FOREIGN KEY (custom_software_license_id) REFERENCES custom_software_licenses(id) ON DELETE CASCADE;
 
@@ -39818,6 +39917,9 @@ ALTER TABLE ONLY ml_experiments
 ALTER TABLE ONLY merge_request_metrics
     ADD CONSTRAINT fk_ae440388cc FOREIGN KEY (latest_closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
 
+ALTER TABLE ONLY merge_requests_approval_rules_projects
+    ADD CONSTRAINT fk_af4078336f FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY analytics_cycle_analytics_group_stages
     ADD CONSTRAINT fk_analytics_cycle_analytics_group_stages_group_value_stream_id FOREIGN KEY (group_value_stream_id) REFERENCES analytics_cycle_analytics_group_value_streams(id) ON DELETE CASCADE;
 
diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb
index 16024c44236e4e9975ce6f5159a85d7b8642c13a..82faa88634223f607759b3665040bb648818cfa3 100644
--- a/ee/app/models/ee/group.rb
+++ b/ee/app/models/ee/group.rb
@@ -99,6 +99,10 @@ module Group
 
       has_many :security_exclusions, class_name: 'Security::GroupSecurityExclusion'
 
+      # WIP v2 approval rules as part of https://gitlab.com/groups/gitlab-org/-/epics/12955
+      has_many :v2_approval_rules_groups, class_name: 'MergeRequests::ApprovalRulesGroup', inverse_of: :group
+      has_many :v2_approval_rules, through: :v2_approval_rules_groups, class_name: 'MergeRequests::ApprovalRule', source: :approval_rule
+
       delegate :deleting_user, :marked_for_deletion_on, to: :deletion_schedule, allow_nil: true
 
       delegate :repository_read_only,
diff --git a/ee/app/models/ee/merge_request.rb b/ee/app/models/ee/merge_request.rb
index fcbf51d524998fcbd25c83e9a8a2a2a9f94c3d3b..83dcc54275cddcafab32fce1a3594caa34167928 100644
--- a/ee/app/models/ee/merge_request.rb
+++ b/ee/app/models/ee/merge_request.rb
@@ -101,6 +101,12 @@ def set_applicable_when_copying_rules(applicable_ids)
 
       has_many :merge_request_stage_events, class_name: 'Analytics::CycleAnalytics::MergeRequestStageEvent'
 
+      # WIP v2 approval rules as part of https://gitlab.com/groups/gitlab-org/-/epics/12955
+      has_many :v2_approval_rules_merge_requests, class_name: 'MergeRequests::ApprovalRulesMergeRequest',
+        inverse_of: :merge_request
+      has_many :v2_approval_rules, through: :v2_approval_rules_merge_requests,
+        class_name: 'MergeRequests::ApprovalRule', source: :approval_rule
+
       delegate :sha, to: :head_pipeline, prefix: :head_pipeline, allow_nil: true
       delegate :sha, to: :base_pipeline, prefix: :base_pipeline, allow_nil: true
       delegate :wrapped_approval_rules, :invalid_approvers_rules, to: :approval_state
diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb
index 8a392295d9e97f6716e384d0990f9f52484f4409..7785ce7520238e0aa077f2760fdae33503b3198e 100644
--- a/ee/app/models/ee/project.rb
+++ b/ee/app/models/ee/project.rb
@@ -217,6 +217,10 @@ def lock_for_confirmation!(id)
       has_many :project_control_compliance_statuses,
         class_name: 'ComplianceManagement::ComplianceFramework::ProjectControlComplianceStatus'
 
+      # WIP v2 approval rules as part of https://gitlab.com/groups/gitlab-org/-/epics/12955
+      has_many :v2_approval_rules_projects, class_name: 'MergeRequests::ApprovalRulesProject', inverse_of: :project
+      has_many :v2_approval_rules, through: :v2_approval_rules_projects, class_name: 'MergeRequests::ApprovalRule', source: :approval_rule
+
       elastic_index_dependant_association :issues, on_change: :visibility_level
       elastic_index_dependant_association :issues, on_change: :archived
       elastic_index_dependant_association :work_items, on_change: :visibility_level
diff --git a/ee/app/models/merge_requests/approval_rule.rb b/ee/app/models/merge_requests/approval_rule.rb
index 46538828aea773d3fd9b8e9d0a87d563089eb6e0..8f002d21655a8e084036c2a4b5a9b99308d79e5f 100644
--- a/ee/app/models/merge_requests/approval_rule.rb
+++ b/ee/app/models/merge_requests/approval_rule.rb
@@ -4,6 +4,30 @@ module MergeRequests
   class ApprovalRule < ApplicationRecord
     self.table_name = 'merge_requests_approval_rules'
 
+    # If we allow overriding in subgroups there can be multiple groups
+    has_many :approval_rules_groups
+    has_many :groups, through: :approval_rules_groups
+
+    # When this originated from group there is only one group
+    has_one :approval_rules_group, inverse_of: :approval_rule
+    has_one :group, through: :approval_rules_group
+
+    # When this originated from group there are multiple projects
+    has_many :approval_rules_projects
+    has_many :projects, through: :approval_rules_projects
+
+    # When this originated from project there is only one project
+    has_one :approval_rules_project
+    has_one :project, through: :approval_rules_project
+
+    # When this originated from group or project there are multiple merge_requests
+    has_many :approval_rules_merge_requests
+    has_many :merge_requests, through: :approval_rules_merge_requests
+
+    # When this originated from merge_request there is only one merge_request
+    has_one :approval_rules_merge_request, inverse_of: :approval_rule
+    has_one :merge_request, through: :approval_rules_merge_request
+
     validate :ensure_single_sharding_key
 
     with_options validate: true do
diff --git a/ee/app/models/merge_requests/approval_rules_group.rb b/ee/app/models/merge_requests/approval_rules_group.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f5b08c306cdb5b9fd8c7d0c327cf7836e776977
--- /dev/null
+++ b/ee/app/models/merge_requests/approval_rules_group.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module MergeRequests
+  class ApprovalRulesGroup < ApplicationRecord
+    self.table_name = 'merge_requests_approval_rules_groups'
+
+    belongs_to :approval_rule, class_name: 'MergeRequests::ApprovalRule'
+    belongs_to :group
+
+    validates :group_id, uniqueness: { scope: :approval_rule_id }
+  end
+end
diff --git a/ee/app/models/merge_requests/approval_rules_merge_request.rb b/ee/app/models/merge_requests/approval_rules_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..add783e2d3d6cfb031ce6d3a2c24b915cad952ef
--- /dev/null
+++ b/ee/app/models/merge_requests/approval_rules_merge_request.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module MergeRequests
+  class ApprovalRulesMergeRequest < ApplicationRecord
+    self.table_name = 'merge_requests_approval_rules_merge_requests'
+
+    belongs_to :approval_rule, class_name: 'MergeRequests::ApprovalRule'
+    belongs_to :merge_request, class_name: 'MergeRequest'
+
+    validates :merge_request_id, uniqueness: { scope: :approval_rule_id }
+    before_create :set_project_id
+
+    private
+
+    def set_project_id
+      self.project_id = merge_request.source_project.id
+    end
+  end
+end
diff --git a/ee/app/models/merge_requests/approval_rules_project.rb b/ee/app/models/merge_requests/approval_rules_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dbe99c628f2daf6b7595fa0a23a2057467f86134
--- /dev/null
+++ b/ee/app/models/merge_requests/approval_rules_project.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module MergeRequests
+  class ApprovalRulesProject < ApplicationRecord
+    self.table_name = 'merge_requests_approval_rules_projects'
+
+    belongs_to :approval_rule, class_name: 'MergeRequests::ApprovalRule'
+    belongs_to :project
+
+    validates :project_id, uniqueness: { scope: :approval_rule_id }
+  end
+end
diff --git a/ee/spec/factories/merge_requests/approval_rules_groups.rb b/ee/spec/factories/merge_requests/approval_rules_groups.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb8212dd5ea6cce4a3284bf88a2db073cdfb04eb
--- /dev/null
+++ b/ee/spec/factories/merge_requests/approval_rules_groups.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :merge_requests_approval_rules_group, class: 'MergeRequests::ApprovalRulesGroup' do
+    association :approval_rule, factory: :merge_requests_approval_rule
+    association :group, factory: :group
+  end
+end
diff --git a/ee/spec/factories/merge_requests/approval_rules_merge_requests.rb b/ee/spec/factories/merge_requests/approval_rules_merge_requests.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b221902638aaa6cacc2f30682e4d69005cadf3b6
--- /dev/null
+++ b/ee/spec/factories/merge_requests/approval_rules_merge_requests.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :merge_requests_approval_rules_merge_request, class: 'MergeRequests::ApprovalRulesMergeRequest' do
+    association :approval_rule, factory: :merge_requests_approval_rule
+    association :merge_request, factory: :merge_request
+  end
+end
diff --git a/ee/spec/factories/merge_requests/approval_rules_projects.rb b/ee/spec/factories/merge_requests/approval_rules_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..de9d478f357fffcd592a0c795ae95a6c29d63eff
--- /dev/null
+++ b/ee/spec/factories/merge_requests/approval_rules_projects.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :merge_requests_approval_rules_project, class: 'MergeRequests::ApprovalRulesProject' do
+    association :approval_rule, factory: :merge_requests_approval_rule
+    association :project, factory: :project
+  end
+end
diff --git a/ee/spec/models/merge_requests/approval_rules_group_spec.rb b/ee/spec/models/merge_requests/approval_rules_group_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..870caa6b9799d7292185ed167d66a8e7de8ca039
--- /dev/null
+++ b/ee/spec/models/merge_requests/approval_rules_group_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalRulesGroup, type: :model, feature_category: :code_review_workflow do
+  describe 'associations' do
+    it { is_expected.to belong_to(:approval_rule) }
+    it { is_expected.to belong_to(:group) }
+  end
+
+  describe 'validations' do
+    context 'when adding the same group to an approval rule' do
+      let(:group) { create(:group) }
+      let(:approval_rule) { create(:merge_requests_approval_rule, group_id: group.id) }
+
+      before do
+        create(:merge_requests_approval_rules_group, approval_rule: approval_rule, group: group)
+      end
+
+      it 'is not valid' do
+        duplicate_approval_rules_group = build(:merge_requests_approval_rules_group, approval_rule: approval_rule,
+          group_id: group.id)
+        expect(duplicate_approval_rules_group).not_to be_valid
+        expect(duplicate_approval_rules_group.errors[:group_id]).to include('has already been taken')
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/merge_requests/approval_rules_merge_request_spec.rb b/ee/spec/models/merge_requests/approval_rules_merge_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2d0ebf27bc8a0224d93ce9f479e955e9c868627
--- /dev/null
+++ b/ee/spec/models/merge_requests/approval_rules_merge_request_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalRulesMergeRequest, type: :model, feature_category: :code_review_workflow do
+  describe 'associations' do
+    it { is_expected.to belong_to(:approval_rule) }
+    it { is_expected.to belong_to(:merge_request) }
+  end
+
+  describe 'validations' do
+    context 'when adding the same merge request to an approval rule' do
+      let(:merge_request) { create(:merge_request) }
+      let(:group) { create(:group) }
+      let(:approval_rule) { create(:merge_requests_approval_rule, group_id: group.id) }
+
+      before do
+        create(:merge_requests_approval_rules_merge_request, approval_rule: approval_rule,
+          merge_request: merge_request, project_id: merge_request.project_id)
+      end
+
+      it 'is not valid' do
+        duplicate_approval_rules_merge_request = build(:merge_requests_approval_rules_merge_request,
+          approval_rule: approval_rule, merge_request_id: merge_request.id, project_id: merge_request.project_id)
+        expect(duplicate_approval_rules_merge_request).not_to be_valid
+        expect(duplicate_approval_rules_merge_request.errors[:merge_request_id]).to include('has already been taken')
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/merge_requests/approval_rules_project_spec.rb b/ee/spec/models/merge_requests/approval_rules_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eeb9ee379d6da7e542d5623632c85c68fb39a88d
--- /dev/null
+++ b/ee/spec/models/merge_requests/approval_rules_project_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalRulesProject, type: :model, feature_category: :code_review_workflow do
+  describe 'associations' do
+    it { is_expected.to belong_to(:approval_rule) }
+    it { is_expected.to belong_to(:project) }
+  end
+
+  describe 'validations' do
+    context 'when adding the same project to an approval rule' do
+      let_it_be(:project) { create(:project) }
+      let(:approval_rule) { create(:merge_requests_approval_rule, project_id: project.id) }
+
+      before do
+        create(:merge_requests_approval_rules_project, approval_rule: approval_rule, project: project)
+      end
+
+      it 'is not valid' do
+        duplicate_approval_rules_project = build(:merge_requests_approval_rules_project, approval_rule: approval_rule,
+          project_id: project.id)
+        expect(duplicate_approval_rules_project).not_to be_valid
+        expect(duplicate_approval_rules_project.errors[:project_id]).to include('has already been taken')
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index dcd711a26ca37aa8b59361c25f329711c56e35ec..e9da7b7abfb56126dc954ae389b365c3eb5b9a00 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -256,6 +256,9 @@ merge_requests:
 - approver_users
 - approver_groups
 - approved_by_users
+- v2_approval_rules
+- v2_approval_rules_projects
+- v2_approval_rules_merge_requests
 - draft_notes
 - merge_train_car
 - blocks_as_blocker
@@ -770,6 +773,9 @@ project:
 - approval_merge_request_rules
 - approval_merge_request_rule_sources
 - approval_project_rules
+- v2_approval_rules
+- v2_approval_rules_projects
+- v2_approval_rules_merge_requests
 - approvers
 - approver_users
 - audit_events