diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 9fbb089d9732c34fd9f8f7df01ee724452c96c05..afe900f39a692e10fc672c0fcfe942f3e73c1df0 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1001,6 +1001,7 @@
     - <<: *if-merge-request-targeting-stable-branch
       allow_failure: true
     - <<: *if-ruby3-branch
+      allow_failure: true
     - <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-e2e
       changes: *feature-flag-development-config-patterns
       when: manual
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
index 1fd87b9897c2b18d627b5f5b152dee2c6e78e065..264c26294333b7d969cda77d348bd44ad77f8606 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
@@ -19,9 +19,14 @@ export const I18N = {
   ),
   disallowForcePushDescription: s__('BranchRules|Force push is not allowed.'),
   approvalsTitle: s__('BranchRules|Approvals'),
+  manageApprovalsLinkTitle: s__('BranchRules|Manage in Merge Request Approvals'),
+  approvalsDescription: s__(
+    'BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Lean more.%{linkEnd}',
+  ),
   statusChecksTitle: s__('BranchRules|Status checks'),
   allowedToPushHeader: s__('BranchRules|Allowed to push (%{total})'),
   allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'),
+  approvalsHeader: s__('BranchRules|Required approvals (%{total})'),
   noData: s__('BranchRules|No data to display'),
 };
 
@@ -33,3 +38,5 @@ export const WILDCARDS_HELP_PATH =
   'user/project/protected_branches#configure-multiple-protected-branches-by-using-a-wildcard';
 
 export const PROTECTED_BRANCHES_HELP_PATH = 'user/project/protected_branches';
+
+export const APPROVALS_HELP_PATH = 'user/project/merge_requests/approvals/index.md';
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 6534ff883a6a20313847c263e2366adb62e73066..318940478a82d04ef95b4208a5fa337a0fefc7a1 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -11,16 +11,19 @@ import {
   BRANCH_PARAM_NAME,
   WILDCARDS_HELP_PATH,
   PROTECTED_BRANCHES_HELP_PATH,
+  APPROVALS_HELP_PATH,
 } from './constants';
 
 const wildcardsHelpDocLink = helpPagePath(WILDCARDS_HELP_PATH);
 const protectedBranchesHelpDocLink = helpPagePath(PROTECTED_BRANCHES_HELP_PATH);
+const approvalsHelpDocLink = helpPagePath(APPROVALS_HELP_PATH);
 
 export default {
   name: 'RuleView',
   i18n: I18N,
   wildcardsHelpDocLink,
   protectedBranchesHelpDocLink,
+  approvalsHelpDocLink,
   components: { Protection, GlSprintf, GlLink, GlLoadingIcon },
   inject: {
     projectPath: {
@@ -29,6 +32,9 @@ export default {
     protectedBranchesPath: {
       default: '',
     },
+    approvalRulesPath: {
+      default: '',
+    },
   },
   apollo: {
     project: {
@@ -48,7 +54,9 @@ export default {
   data() {
     return {
       branch: getParameterByName(BRANCH_PARAM_NAME),
-      branchProtection: {},
+      branchProtection: {
+        approvalRules: {},
+      },
     };
   },
   computed: {
@@ -75,6 +83,15 @@ export default {
         total: this.pushAccessLevels.total,
       });
     },
+    approvalsHeader() {
+      const total = this.approvals.reduce(
+        (sum, { approvalsRequired }) => sum + approvalsRequired,
+        0,
+      );
+      return sprintf(this.$options.i18n.approvalsHeader, {
+        total,
+      });
+    },
     allBranches() {
       return this.branch === ALL_BRANCHES_WILDCARD;
     },
@@ -86,6 +103,9 @@ export default {
         ? this.$options.i18n.targetBranch
         : this.$options.i18n.branchNameOrPattern;
     },
+    approvals() {
+      return this.branchProtection?.approvalRules?.nodes || [];
+    },
   },
   methods: {
     getAccessLevels(accessLevels = {}) {
@@ -164,7 +184,22 @@ export default {
     />
 
     <!-- Approvals -->
-    <!-- Follow-up: add approval section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
+    <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.approvalsTitle }}</h4>
+    <gl-sprintf :message="$options.i18n.approvalsDescription">
+      <template #link="{ content }">
+        <gl-link :href="$options.approvalsHelpDocLink">
+          {{ content }}
+        </gl-link>
+      </template>
+    </gl-sprintf>
+
+    <protection
+      class="gl-mt-3"
+      :header="approvalsHeader"
+      :header-link-title="$options.i18n.manageApprovalsLinkTitle"
+      :header-link-href="approvalRulesPath"
+      :approvals="approvals"
+    />
 
     <!-- Status checks -->
     <!-- Follow-up: add status checks section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
index 8434b7bfce59fa03cafa0e9327c2140f07890904..cfe2df0dbda1facc5d8ef742c165a4428b5e5a7f 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
@@ -41,6 +41,11 @@ export default {
       required: false,
       default: () => [],
     },
+    approvals: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
   },
   computed: {
     showUsersDivider() {
@@ -80,5 +85,15 @@ export default {
       :title="$options.i18n.groupsTitle"
       :access-levels="groups"
     />
+
+    <!-- Approvals -->
+    <protection-row
+      v-for="(approval, index) in approvals"
+      :key="approval.name"
+      :show-divider="index !== 0"
+      :title="approval.name"
+      :users="approval.eligibleApprovers.nodes"
+      :approvals-required="approval.approvalsRequired"
+    />
   </gl-card>
 </template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
index 2509c2538b20b5acaef8f83c334d77b38e244240..28a1c09fa821232fa32bb948206ed5663323e806 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
@@ -36,6 +36,11 @@ export default {
       required: false,
       default: () => [],
     },
+    approvalsRequired: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
   },
   computed: {
     avatarBadgeSrOnlyText() {
@@ -48,6 +53,11 @@ export default {
     commaSeparateList() {
       return this.accessLevels.length > 1;
     },
+    approvalsRequiredTitle() {
+      return this.approvalsRequired
+        ? n__('%d approval required', '%d approvals required', this.approvalsRequired)
+        : null;
+    },
   },
 };
 </script>
@@ -57,34 +67,44 @@ export default {
     class="gl-display-flex gl-align-items-center gl-border-gray-100 gl-mb-4 gl-pt-4"
     :class="{ 'gl-border-t-solid': showDivider }"
   >
-    <div class="gl-mr-7">{{ title }}</div>
+    <div class="gl-display-flex gl-w-half gl-justify-content-space-between">
+      <div class="gl-mr-7 gl-w-quarter">{{ title }}</div>
+
+      <gl-avatars-inline
+        v-if="users.length"
+        class="gl-w-quarter!"
+        :avatars="users"
+        :collapsed="true"
+        :max-visible="$options.MAX_VISIBLE_AVATARS"
+        :avatar-size="$options.AVATAR_SIZE"
+        badge-tooltip-prop="name"
+        :badge-tooltip-max-chars="$options.AVATAR_TOOLTIP_MAX_CHARS"
+        :badge-sr-only-text="avatarBadgeSrOnlyText"
+      >
+        <template #avatar="{ avatar }">
+          <gl-avatar-link
+            :key="avatar.username"
+            v-gl-tooltip
+            target="_blank"
+            :href="avatar.webUrl"
+            :title="avatar.name"
+          >
+            <gl-avatar :src="avatar.avatarUrl" :label="avatar.name" :size="$options.AVATAR_SIZE" />
+          </gl-avatar-link>
+        </template>
+      </gl-avatars-inline>
 
-    <gl-avatars-inline
-      v-if="users.length"
-      :avatars="users"
-      :collapsed="true"
-      :max-visible="$options.MAX_VISIBLE_AVATARS"
-      :avatar-size="$options.AVATAR_SIZE"
-      badge-tooltip-prop="name"
-      :badge-tooltip-max-chars="$options.AVATAR_TOOLTIP_MAX_CHARS"
-      :badge-sr-only-text="avatarBadgeSrOnlyText"
-    >
-      <template #avatar="{ avatar }">
-        <gl-avatar-link
-          :key="avatar.username"
-          v-gl-tooltip
-          target="_blank"
-          :href="avatar.webUrl"
-          :title="avatar.name"
-        >
-          <gl-avatar :src="avatar.avatarUrl" :label="avatar.name" :size="$options.AVATAR_SIZE" />
-        </gl-avatar-link>
-      </template>
-    </gl-avatars-inline>
+      <div
+        v-for="(item, index) in accessLevels"
+        :key="index"
+        data-testid="access-level"
+        class="gl-w-quarter"
+      >
+        <span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span>
+        {{ item.accessLevelDescription }}
+      </div>
 
-    <div v-for="(item, index) in accessLevels" :key="index" data-testid="access-level">
-      <span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span>
-      {{ item.accessLevelDescription }}
+      <div class="gl-ml-7 gl-w-quarter">{{ approvalsRequiredTitle }}</div>
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
index 39164063d05ee97dd79979656e28716cbfe78b3c..07fd0a7080f3b35172ed8ef02d91336a1c2dff44 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
+++ b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
@@ -14,7 +14,7 @@ export default function mountBranchRules(el) {
     defaultClient: createDefaultClient(),
   });
 
-  const { projectPath, protectedBranchesPath } = el.dataset;
+  const { projectPath, protectedBranchesPath, approvalRulesPath } = el.dataset;
 
   return new Vue({
     el,
@@ -22,6 +22,7 @@ export default function mountBranchRules(el) {
     provide: {
       projectPath,
       protectedBranchesPath,
+      approvalRulesPath,
     },
     render(h) {
       return h(View);
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index e34c51bc11f9ced0bb3bbff2deba9b2c2c79e0be..ad061dd2e6b9c4f7232e9a10a31e4a476dc65776 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -85,7 +85,7 @@ export default {
       return this.loading || this.$apollo.queries.issuable.loading;
     },
     canUpdate() {
-      return this.issuable.userPermissions?.updateMergeRequest || false;
+      return this.issuable.userPermissions?.adminMergeRequest || false;
     },
   },
   created() {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql
index 05de680ab059ed6366353dc1908110dde3151acc..f087ca6c9828ef4b0181cc2ba8cf18e6f332fa21 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql
@@ -19,7 +19,7 @@ query mergeRequestReviewers($fullPath: ID!, $iid: String!) {
         }
       }
       userPermissions {
-        updateMergeRequest
+        adminMergeRequest
       }
     }
   }
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 282622c792e3b251c259219ef3bbf4f7852e932d..e05a32ccb5b899327d4444b6b6ed6c04ed93ef62 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -455,8 +455,7 @@ def waiting_for_deployment_approval?
 
     def prevent_rollback_deployment?
       strong_memoize(:prevent_rollback_deployment) do
-        Feature.enabled?(:prevent_outdated_deployment_jobs, project) &&
-          starts_environment? &&
+        starts_environment? &&
           project.ci_forward_deployment_enabled? &&
           deployment&.older_than_last_successful_deployment?
       end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index dafcbc593be0721e76091e0c9b1cd737d79c4e74..4d10499f48d4a72f3a35c2af875f8a6da321a893 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -105,6 +105,7 @@ class Deployment < ApplicationRecord
 
     after_transition any => :running do |deployment|
       next unless deployment.project.ci_forward_deployment_enabled?
+      next if Feature.enabled?(:prevent_outdated_deployment_jobs, deployment.project)
 
       deployment.run_after_commit do
         Deployments::DropOlderDeploymentsWorker.perform_async(id)
diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb
index 72f5ab11060ab12279cbe2dd763b1ef4567314a9..cd7d4fc409a586d3a49d39d0ca538eb2f1f250a7 100644
--- a/app/models/namespace/aggregation_schedule.rb
+++ b/app/models/namespace/aggregation_schedule.rb
@@ -14,9 +14,9 @@ class Namespace::AggregationSchedule < ApplicationRecord
 
   def self.default_lease_timeout
     if Feature.enabled?(:remove_namespace_aggregator_delay)
-      1.hour.to_i
+      30.minutes.to_i
     else
-      1.5.hours.to_i
+      1.hour.to_i
     end
   end
 
diff --git a/app/services/ci/process_build_service.rb b/app/services/ci/process_build_service.rb
index e6ec65fcc913892a0a65835296baa5fd1caeb4fa..22cd267806d20c4fc8ace685d70990fb487baaeb 100644
--- a/app/services/ci/process_build_service.rb
+++ b/app/services/ci/process_build_service.rb
@@ -25,6 +25,8 @@ def process(build)
     end
 
     def enqueue(build)
+      return build.drop!(:failed_outdated_deployment_job) if build.prevent_rollback_deployment?
+
       build.enqueue
     end
 
diff --git a/app/views/projects/settings/branch_rules/index.html.haml b/app/views/projects/settings/branch_rules/index.html.haml
index ab692a23e4468eb185a5483536f06e31d766c173..a7e80101a88407bc1e8e26c3922b27890eecf7b3 100644
--- a/app/views/projects/settings/branch_rules/index.html.haml
+++ b/app/views/projects/settings/branch_rules/index.html.haml
@@ -3,4 +3,4 @@
 
 %h3.gl-mb-5= s_('BranchRules|Branch rules details')
 
-#js-branch-rules{ data: { project_path: @project.full_path, protected_branches_path: project_settings_repository_path(@project, anchor: 'js-protected-branches-settings') } }
+#js-branch-rules{ data: { project_path: @project.full_path, protected_branches_path: project_settings_repository_path(@project, anchor: 'js-protected-branches-settings'), approval_rules_path: project_settings_merge_requests_path(@project, anchor: 'js-merge-request-approval-settings') } }
diff --git a/config/metrics/counts_28d/20220222215951_xmau_plan.yml b/config/metrics/counts_28d/20220222215951_xmau_plan.yml
index 587f34e90c3df09b79fcd51f36429b42c8f2df8c..c254ad942c2cfdbb725e1c1f8ec4fb2c36e00b52 100644
--- a/config/metrics/counts_28d/20220222215951_xmau_plan.yml
+++ b/config/metrics/counts_28d/20220222215951_xmau_plan.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_28d/20220222215952_xmau_project_management.yml b/config/metrics/counts_28d/20220222215952_xmau_project_management.yml
index 542f8a131181c626ef1aecafb329f037d6f17677..0dad4fd0979fc4e8c2157d76ed5136c5d1edf894 100644
--- a/config/metrics/counts_28d/20220222215952_xmau_project_management.yml
+++ b/config/metrics/counts_28d/20220222215952_xmau_project_management.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_28d/20220222215955_users_work_items.yml b/config/metrics/counts_28d/20220222215955_users_work_items.yml
index e7a95c9d3356ac94dc9cbd817ba961feac37a99d..ec07fb25f1182fe644d5b03ee8c508e452aa160a 100644
--- a/config/metrics/counts_28d/20220222215955_users_work_items.yml
+++ b/config/metrics/counts_28d/20220222215955_users_work_items.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_28d/20220922042106_users_updating_work_item_iteration_monthly.yml b/config/metrics/counts_28d/20220922042106_users_updating_work_item_iteration_monthly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4c19e4e32619e47b63d103bdadef04e5a746f297
--- /dev/null
+++ b/config/metrics/counts_28d/20220922042106_users_updating_work_item_iteration_monthly.yml
@@ -0,0 +1,24 @@
+---
+key_path: redis_hll_counters.work_items.users_updating_work_item_iteration_monthly
+description: Unique users updating a work item's iteration
+product_section: team planning
+product_stage: dev
+product_group: plan
+product_category: project_management
+value_type: number
+status: active
+milestone: "15.5"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98539
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+  events:
+    - users_updating_work_item_iteration
+distribution:
+- ce
+- ee
+tier:
+- premium
+- ultimate
diff --git a/config/metrics/counts_7d/20220222215851_xmau_plan.yml b/config/metrics/counts_7d/20220222215851_xmau_plan.yml
index 4fb302d67ac3e985c73480ac4c567ef4f4e741c3..77325a205eeb9713d4f522ab87f5fde61c19d674 100644
--- a/config/metrics/counts_7d/20220222215851_xmau_plan.yml
+++ b/config/metrics/counts_7d/20220222215851_xmau_plan.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_7d/20220222215852_xmau_project_management.yml b/config/metrics/counts_7d/20220222215852_xmau_project_management.yml
index a63adfab8aa9ea87eb0afae8cab28cd9f08eb405..c7e712cf92a55ff87758ea568a35e073bda1e14c 100644
--- a/config/metrics/counts_7d/20220222215852_xmau_project_management.yml
+++ b/config/metrics/counts_7d/20220222215852_xmau_project_management.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_7d/20220222215855_users_work_items.yml b/config/metrics/counts_7d/20220222215855_users_work_items.yml
index cb3120221921c4803951c03ebc27ba96482eeee5..0985f38c83be0253f15c5b72969dce19559feaaa 100644
--- a/config/metrics/counts_7d/20220222215855_users_work_items.yml
+++ b/config/metrics/counts_7d/20220222215855_users_work_items.yml
@@ -21,6 +21,7 @@ options:
     - users_updating_work_item_title
     - users_updating_work_item_dates
     - users_updating_work_item_labels
+    - users_updating_work_item_iteration
 data_category: optional
 distribution:
 - ce
diff --git a/config/metrics/counts_7d/20220922042528_users_updating_work_item_iteration_weekly.yml b/config/metrics/counts_7d/20220922042528_users_updating_work_item_iteration_weekly.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aad949867cfcda41d575450399f6c28c790552c4
--- /dev/null
+++ b/config/metrics/counts_7d/20220922042528_users_updating_work_item_iteration_weekly.yml
@@ -0,0 +1,24 @@
+---
+key_path: redis_hll_counters.work_items.users_updating_work_item_iteration_weekly
+description: Unique users updating a work item's iteration
+product_section: team planning
+product_stage: dev
+product_group: plan
+product_category: project_management
+value_type: number
+status: active
+milestone: "15.5"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98539
+time_frame: 7d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+options:
+  events:
+    - users_updating_work_item_iteration
+distribution:
+- ce
+- ee
+tier:
+- premium
+- ultimate
diff --git a/db/migrate/20221014031033_add_temp_index_to_project_features_where_releases_access_level_gt_repository.rb b/db/migrate/20221014031033_add_temp_index_to_project_features_where_releases_access_level_gt_repository.rb
new file mode 100644
index 0000000000000000000000000000000000000000..14077e3078012389e83d0571d717d6b136793d2c
--- /dev/null
+++ b/db/migrate/20221014031033_add_temp_index_to_project_features_where_releases_access_level_gt_repository.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddTempIndexToProjectFeaturesWhereReleasesAccessLevelGtRepository < Gitlab::Database::Migration[2.0]
+  disable_ddl_transaction!
+
+  INDEX_NAME = 'tmp_idx_project_features_on_releases_al_and_repo_al_partial'
+
+  # Temporary index to be removed in 15.6 https://gitlab.com/gitlab-org/gitlab/-/issues/377915
+  def up
+    add_concurrent_index :project_features,
+                         [:releases_access_level, :repository_access_level],
+                         name: INDEX_NAME,
+                         where: 'releases_access_level > repository_access_level'
+  end
+
+  def down
+    remove_concurrent_index_by_name :project_features, INDEX_NAME
+  end
+end
diff --git a/db/migrate/20221014034338_populate_releases_access_level_from_repository.rb b/db/migrate/20221014034338_populate_releases_access_level_from_repository.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e61d972bf6a022e2f14645ffac11d1fa6ee773b
--- /dev/null
+++ b/db/migrate/20221014034338_populate_releases_access_level_from_repository.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class PopulateReleasesAccessLevelFromRepository < Gitlab::Database::Migration[2.0]
+  restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+  disable_ddl_transaction!
+
+  def up
+    update_column_in_batches(
+      :project_features,
+      :releases_access_level,
+      Arel.sql('repository_access_level')
+    ) do |table, query|
+      query.where(table[:releases_access_level].gt(table[:repository_access_level]))
+    end
+  end
+
+  def down
+    # no-op
+  end
+end
diff --git a/db/schema_migrations/20221014031033 b/db/schema_migrations/20221014031033
new file mode 100644
index 0000000000000000000000000000000000000000..6a24a2027c78cbe2e03d0288b216a84588c85cb7
--- /dev/null
+++ b/db/schema_migrations/20221014031033
@@ -0,0 +1 @@
+bc05939dc672c078161cd9b7dbd7f92601edb6888a77c62adb014964e30c6ae8
\ No newline at end of file
diff --git a/db/schema_migrations/20221014034338 b/db/schema_migrations/20221014034338
new file mode 100644
index 0000000000000000000000000000000000000000..c90dfebb72b99af4ee6fa5a2361b957e31987414
--- /dev/null
+++ b/db/schema_migrations/20221014034338
@@ -0,0 +1 @@
+58ee7f51a0da4ee4ec471d4492106d1fc3124419ba83591913967d6bd38105e5
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e95f93982fab082228a9089fb1e39e1567befaa8..b8779dd9416c188b3133245cf70756e54f1161d3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -30993,6 +30993,8 @@ CREATE UNIQUE INDEX taggings_idx ON taggings USING btree (tag_id, taggable_id, t
 
 CREATE UNIQUE INDEX term_agreements_unique_index ON term_agreements USING btree (user_id, term_id);
 
+CREATE INDEX tmp_idx_project_features_on_releases_al_and_repo_al_partial ON project_features USING btree (releases_access_level, repository_access_level) WHERE (releases_access_level > repository_access_level);
+
 CREATE INDEX tmp_idx_vulnerabilities_on_id_where_report_type_7_99 ON vulnerabilities USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
 
 CREATE INDEX tmp_index_approval_merge_request_rules_on_report_type_equal_one ON approval_merge_request_rules USING btree (id, report_type) WHERE (report_type = 1);
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index bc362b07385772b55d468b9096977cdca2ff2b1a..2f23ebbfd9f4fff79626ee667810c5f0eae84f67 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -95,9 +95,14 @@ after a given period of time.
 
 The following are example projects that demonstrate Review App configuration:
 
-- [NGINX](https://gitlab.com/gitlab-examples/review-apps-nginx).
-- [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift).
-- [HashiCorp Nomad](https://gitlab.com/gitlab-examples/review-apps-nomad).
+| Project                                                                 | Configuration file |
+|-------------------------------------------------------------------------|--------------------|
+| [NGINX](https://gitlab.com/gitlab-examples/review-apps-nginx)           | [`.gitlab-ci.yml`](https://gitlab.com/gitlab-examples/review-apps-nginx/-/blob/b9c1f6a8a7a0dfd9c8784cbf233c0a7b6a28ff27/.gitlab-ci.yml#L20) |
+| [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift)   | [`.gitlab-ci.yml`](https://gitlab.com/gitlab-examples/review-apps-openshift/-/blob/82ebd572334793deef2d5ddc379f38942f3488be/.gitlab-ci.yml#L42) |
+| [HashiCorp Nomad](https://gitlab.com/gitlab-examples/review-apps-nomad) | [`.gitlab-ci.yml`](https://gitlab.com/gitlab-examples/review-apps-nomad/-/blob/ca372c778be7aaed5e82d3be24e98c3f10a465af/.gitlab-ci.yml#L110) |
+| [GitLab Documentation](https://gitlab.com/gitlab-org/gitlab-docs/)      | [`build-and-deploy.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/a715625496303cbd90ff89f3d3658ea8d36ce0f3/.gitlab/ci/build-and-deploy.gitlab-ci.yml#L59) |
+| [`https://about.gitlab.com/`](https://gitlab.com/gitlab-com/www-gitlab-com/)       | [`.gitlab-ci.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/6ffcdc3cb9af2abed490cbe5b7417df3e83cd76c/.gitlab-ci.yml#L332) |
+| [GitLab Insights](https://gitlab.com/gitlab-org/gitlab-insights/)       | [`.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab-insights/-/blob/9e63f44ac2a5a4defc965d0d61d411a768e20546/.gitlab-ci.yml#L234) |
 
 Other examples of Review Apps:
 
diff --git a/ee/app/services/ee/ci/process_build_service.rb b/ee/app/services/ee/ci/process_build_service.rb
index a586057e0eec84bbf83a7fb0f8f9ceec5e74ce2e..d73c2579706cbcefbff68566357afef31c5e0d66 100644
--- a/ee/app/services/ee/ci/process_build_service.rb
+++ b/ee/app/services/ee/ci/process_build_service.rb
@@ -23,8 +23,6 @@ def enqueue(build)
           return build.drop!(:protected_environment_failure)
         end
 
-        return build.drop!(:failed_outdated_deployment_job) if build.prevent_rollback_deployment?
-
         super
       end
     end
diff --git a/ee/app/services/resource_events/change_iteration_service.rb b/ee/app/services/resource_events/change_iteration_service.rb
index 53def1785a53b9d7fcd812cd067b0923e1f9c21f..86fc53720ce87a61d550fd7c3f379d6daf658d3e 100644
--- a/ee/app/services/resource_events/change_iteration_service.rb
+++ b/ee/app/services/resource_events/change_iteration_service.rb
@@ -7,10 +7,20 @@ class ChangeIterationService < ::ResourceEvents::BaseChangeTimeboxService
     def initialize(resource, user, old_iteration_id:)
       super(resource, user)
 
+      @resource = resource
+      @user = user
       @iteration = resource&.iteration
       @old_iteration_id = old_iteration_id
     end
 
+    def execute
+      super
+
+      return unless resource.is_a?(WorkItem)
+
+      Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter.track_work_item_iteration_changed_action(author: user)
+    end
+
     private
 
     def create_event
diff --git a/ee/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter.rb b/ee/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
index aedd0e3a828215167104744c3dbce970fcf84c13..46872defa9265aa0f35cb606b4844a6b38b22db9 100644
--- a/ee/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
+++ b/ee/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
@@ -7,11 +7,16 @@ module WorkItemActivityUniqueCounter
         extend ActiveSupport::Concern
 
         WORK_ITEM_WEIGHT_CHANGED = 'users_updating_weight_estimate'
+        WORK_ITEM_ITERATION_CHANGED = 'users_updating_work_item_iteration'
 
         class_methods do
           def track_work_item_weight_changed_action(author:)
             track_unique_action(WORK_ITEM_WEIGHT_CHANGED, author)
           end
+
+          def track_work_item_iteration_changed_action(author:)
+            track_unique_action(WORK_ITEM_ITERATION_CHANGED, author)
+          end
         end
       end
     end
diff --git a/ee/spec/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb b/ee/spec/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
index affc5ce5581285303f4109af0850651f16401e14..fab3057a9783b79890c6c8b1f6315dcc8a3b34b1 100644
--- a/ee/spec/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
+++ b/ee/spec/lib/ee/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
@@ -12,4 +12,12 @@
 
     it_behaves_like 'work item unique counter'
   end
+
+  describe '.track_work_item_iteration_changed_action' do
+    subject(:track_event) { described_class.track_work_item_iteration_changed_action(author: user) }
+
+    let(:event_name) { described_class::WORK_ITEM_ITERATION_CHANGED }
+
+    it_behaves_like 'work item unique counter'
+  end
 end
diff --git a/ee/spec/services/ee/resource_events/change_iteration_service_spec.rb b/ee/spec/services/ee/resource_events/change_iteration_service_spec.rb
index cec3cdabb31b7ee2cba1a73aa6a9beb8f937cdd5..f10db46944bea207e696654d8590a3b4fe45e78a 100644
--- a/ee/spec/services/ee/resource_events/change_iteration_service_spec.rb
+++ b/ee/spec/services/ee/resource_events/change_iteration_service_spec.rb
@@ -14,4 +14,33 @@
       let_it_be(:resource) { create(issuable) } # rubocop:disable Rails/SaveBang
     end
   end
+
+  describe 'events tracking' do
+    let_it_be(:user) { create(:user) }
+
+    subject(:changed_service_instance) { described_class.new(resource, user, old_iteration_id: nil) }
+
+    context 'when the resource is a work item' do
+      let(:resource) { create(:work_item, iteration: timebox) }
+
+      it 'tracks work item usage data counters' do
+        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter)
+          .to receive(:track_work_item_iteration_changed_action)
+          .with(author: user)
+
+        changed_service_instance.execute
+      end
+    end
+
+    context 'when the resource is not a work item' do
+      let(:resource) { create(:issue, iteration: timebox) }
+
+      it 'does not track work item usage data counters' do
+        expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter)
+          .not_to receive(:track_work_item_iteration_changed_action)
+
+        changed_service_instance.execute
+      end
+    end
+  end
 end
diff --git a/lib/gitlab/usage_data_counters/known_events/work_items.yml b/lib/gitlab/usage_data_counters/known_events/work_items.yml
index 94b7a37e67e34b74de2af4a65f1d9b41fedd7773..ee828fc0f72413bf1f0816f619776973598820da 100644
--- a/lib/gitlab/usage_data_counters/known_events/work_items.yml
+++ b/lib/gitlab/usage_data_counters/known_events/work_items.yml
@@ -19,3 +19,11 @@
   redis_slot: users
   aggregation: weekly
   feature_flag: track_work_items_activity
+- name: users_updating_work_item_iteration
+  # The event tracks an EE feature.
+  # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics.
+  # It will report 0 for CE instances and should not be used with 'AND' aggregators.
+  category: work_items
+  redis_slot: users
+  aggregation: weekly
+  feature_flag: track_work_items_activity
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cf1a3becf8efd5ce259ace241c5b4dc5780d9340..34cdc9c4553615f2382f94f9b497d94fdafb3077 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -143,6 +143,11 @@ msgid_plural "%d additional users"
 msgstr[0] ""
 msgstr[1] ""
 
+msgid "%d approval required"
+msgid_plural "%d approvals required"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "%d approver"
 msgid_plural "%d approvers"
 msgstr[0] ""
@@ -6826,6 +6831,9 @@ msgstr ""
 msgid "BranchRules|Approvals"
 msgstr ""
 
+msgid "BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Lean more.%{linkEnd}"
+msgstr ""
+
 msgid "BranchRules|Branch"
 msgstr ""
 
@@ -6853,6 +6861,9 @@ msgstr ""
 msgid "BranchRules|Keep stable branches secure and force developers to use merge requests. %{linkStart}What are protected branches?%{linkEnd}"
 msgstr ""
 
+msgid "BranchRules|Manage in Merge Request Approvals"
+msgstr ""
+
 msgid "BranchRules|Manage in Protected Branches"
 msgstr ""
 
@@ -6874,6 +6885,9 @@ msgstr ""
 msgid "BranchRules|Require approval from code owners."
 msgstr ""
 
+msgid "BranchRules|Required approvals (%{total})"
+msgstr ""
+
 msgid "BranchRules|Roles"
 msgstr ""
 
diff --git a/qa/Gemfile b/qa/Gemfile
index 5817dcd2f901731a71307b7426cf8413bbaa389a..ab48865af5e9edef57b98e1a5265c12d86e9d1c9 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -18,7 +18,7 @@ gem 'faker', '~> 2.19', '>= 2.19.0'
 gem 'knapsack', '~> 4.0'
 gem 'parallel_tests', '~> 2.29'
 gem 'rotp', '~> 3.1.0'
-gem 'timecop', '~> 0.9.1'
+gem 'timecop', '~> 0.9.5'
 gem 'parallel', '~> 1.19'
 gem 'rainbow', '~> 3.0.0'
 gem 'rspec-parameterized', '~> 0.4.2'
@@ -45,5 +45,5 @@ gem 'deprecation_toolkit', '~> 1.5.1', require: false
 
 group :development do
   gem 'pry-byebug', '~> 3.5.1', platform: :mri
-  gem "ruby-debug-ide", "~> 0.7.0"
+  gem "ruby-debug-ide", "~> 0.7.3"
 end
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 9917df25a65ca1c8436eb7c959f51482e70c6279..232099e0f6e33403bf7efeb0308634e7cd32869e 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -255,7 +255,7 @@ GEM
     rspec-support (3.10.2)
     rspec_junit_formatter (0.4.1)
       rspec-core (>= 2, < 4, != 2.12.0)
-    ruby-debug-ide (0.7.2)
+    ruby-debug-ide (0.7.3)
       rake (>= 0.8.1)
     ruby2_keywords (0.0.5)
     rubyzip (2.3.2)
@@ -277,7 +277,7 @@ GEM
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
     thread_safe (0.3.6)
-    timecop (0.9.1)
+    timecop (0.9.5)
     trailblazer-option (0.1.2)
     tzinfo (2.0.5)
       concurrent-ruby (~> 1.0)
@@ -342,11 +342,11 @@ DEPENDENCIES
   rspec-parameterized (~> 0.4.2)
   rspec-retry (~> 0.6.1)
   rspec_junit_formatter (~> 0.4.1)
-  ruby-debug-ide (~> 0.7.0)
+  ruby-debug-ide (~> 0.7.3)
   selenium-webdriver (~> 4.0)
   slack-notifier (~> 2.4)
   terminal-table (~> 3.0.0)
-  timecop (~> 0.9.1)
+  timecop (~> 0.9.5)
   warning (~> 1.3)
   webdrivers (~> 5.2)
   zeitwerk (~> 2.4)
diff --git a/scripts/api/pipeline_failed_jobs.rb b/scripts/api/pipeline_failed_jobs.rb
index 3c29e8842d3ea123a15fc1fdb85b538761c28924..c25567af69844b4d6ee6dde11e622c3e1640cb9e 100644
--- a/scripts/api/pipeline_failed_jobs.rb
+++ b/scripts/api/pipeline_failed_jobs.rb
@@ -31,6 +31,13 @@ def execute
       failed_jobs << job
     end
 
+    client.pipeline_bridges(project, pipeline_id, scope: 'failed', per_page: 100).auto_paginate do |job|
+      next if exclude_allowed_to_fail_jobs && job.allow_failure
+
+      job.web_url = job.downstream_pipeline.web_url # job.web_url is linking to an invalid page
+      failed_jobs << job
+    end
+
     failed_jobs
   end
 
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
index 3176b28d5474a356eaf89baa11a2f85bdc2c6ab1..bf4026b65db59ed350f6408ed1c81b9c39a28b5f 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
@@ -33,6 +33,7 @@ describe('View branch rules', () => {
   let fakeApollo;
   const projectPath = 'test/testing';
   const protectedBranchesPath = 'protected/branches';
+  const approvalRulesPath = 'approval/rules';
   const branchProtectionsMockRequestHandler = jest
     .fn()
     .mockResolvedValue(branchProtectionsMockResponse);
@@ -42,7 +43,7 @@ describe('View branch rules', () => {
 
     wrapper = shallowMountExtended(RuleView, {
       apolloProvider: fakeApollo,
-      provide: { projectPath, protectedBranchesPath },
+      provide: { projectPath, protectedBranchesPath, approvalRulesPath },
     });
 
     await waitForPromises();
@@ -57,6 +58,7 @@ describe('View branch rules', () => {
   const findBranchProtectionTitle = () => wrapper.findByText(I18N.protectBranchTitle);
   const findBranchProtections = () => wrapper.findAllComponents(Protection);
   const findForcePushTitle = () => wrapper.findByText(I18N.allowForcePushDescription);
+  const findApprovalsTitle = () => wrapper.findByText(I18N.approvalsTitle);
 
   it('gets the branch param from url and renders it in the view', () => {
     expect(util.getParameterByName).toHaveBeenCalledWith('branch');
@@ -98,4 +100,14 @@ describe('View branch rules', () => {
       ...protectionMockProps,
     });
   });
+
+  it('renders a branch protection component for approvals', () => {
+    expect(findApprovalsTitle().exists()).toBe(true);
+
+    expect(findBranchProtections().at(2).props()).toMatchObject({
+      header: sprintf(I18N.approvalsHeader, { total: 0 }),
+      headerLinkHref: approvalRulesPath,
+      headerLinkTitle: I18N.manageApprovalsLinkTitle,
+    });
+  });
 });
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
index c5774977205f6276d7944ee4e04ed6674bff79cf..c3f573061da7c2e71c5ff52ed9c6be899ba206bf 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
@@ -36,6 +36,8 @@ const accessLevelsMock = [
   { accessLevelDescription: 'Maintainer' },
 ];
 
+const approvalsRequired = 3;
+
 const groupsMock = [{ name: 'test_group_1' }, { name: 'test_group_2' }];
 
 export const protectionPropsMock = {
@@ -45,12 +47,20 @@ export const protectionPropsMock = {
   roles: accessLevelsMock,
   users: usersMock,
   groups: groupsMock,
+  approvals: [
+    {
+      name: 'test',
+      eligibleApprovers: { nodes: usersMock },
+      approvalsRequired,
+    },
+  ],
 };
 
 export const protectionRowPropsMock = {
   title: 'Test title',
   users: usersMock,
   accessLevels: accessLevelsMock,
+  approvalsRequired,
 };
 
 export const accessLevelsMockResponse = [
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
index 7770e1fb2aa7a597ad71482d51a3177644b57d83..b0a69bedd3e0a691f87891d7ed8a01ba5a37ca85 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
@@ -25,6 +25,8 @@ describe('Branch rule protection row', () => {
   const findAvatarLinks = () => wrapper.findAllComponents(GlAvatarLink);
   const findAvatars = () => wrapper.findAllComponents(GlAvatar);
   const findAccessLevels = () => wrapper.findAllByTestId('access-level');
+  const findApprovalsRequired = () =>
+    wrapper.findByText(`${protectionRowPropsMock.approvalsRequired} approvals required`);
 
   it('renders a title', () => {
     expect(findTitle().exists()).toBe(true);
@@ -62,4 +64,8 @@ describe('Branch rule protection row', () => {
       protectionRowPropsMock.accessLevels[1].accessLevelDescription,
     );
   });
+
+  it('renders the number of approvals required', () => {
+    expect(findApprovalsRequired().exists()).toBe(true);
+  });
 });
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
index 91d16fd86a6e1ee2d34d3cefc675b6b0dcd7379e..e2fbb4f5bbb05d00364936a5f8b2813e59fafc45 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
@@ -56,4 +56,13 @@ describe('Branch rule protection', () => {
       title: i18n.groupsTitle,
     });
   });
+
+  it('renders a protection row for approvals', () => {
+    const approval = protectionPropsMock.approvals[0];
+    expect(findProtectionRows().at(3).props()).toMatchObject({
+      title: approval.name,
+      users: approval.eligibleApprovers.nodes,
+      approvalsRequired: approval.approvalsRequired,
+    });
+  });
 });
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb
index c13a5ba4d7236cddf88e046ea7979751955a9be2..3e315692d0a2b69e49b267f8d556765b7cfbeb4c 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb
@@ -6,7 +6,7 @@
   let(:metric_definition) do
     {
       data_source: 'redis_hll',
-      time_frame: '7d',
+      time_frame: time_frame,
       options: {
         aggregate: {
           operator: 'OR'
@@ -15,6 +15,7 @@
           users_creating_work_items
           users_updating_work_item_title
           users_updating_work_item_dates
+          users_updating_work_item_iteration
         ]
       }
     }
@@ -24,31 +25,36 @@
     freeze_time { example.run }
   end
 
-  describe '#available?' do
-    it 'returns false without track_work_items_activity feature' do
-      stub_feature_flags(track_work_items_activity: false)
+  where(:time_frame) { [['28d'], ['7d']] }
 
-      expect(described_class.new(metric_definition).available?).to eq(false)
-    end
+  with_them do
+    describe '#available?' do
+      it 'returns false without track_work_items_activity feature' do
+        stub_feature_flags(track_work_items_activity: false)
+
+        expect(described_class.new(metric_definition).available?).to eq(false)
+      end
 
-    it 'returns true with track_work_items_activity feature' do
-      stub_feature_flags(track_work_items_activity: true)
+      it 'returns true with track_work_items_activity feature' do
+        stub_feature_flags(track_work_items_activity: true)
 
-      expect(described_class.new(metric_definition).available?).to eq(true)
+        expect(described_class.new(metric_definition).available?).to eq(true)
+      end
     end
-  end
 
-  describe '#value', :clean_gitlab_redis_shared_state do
-    let(:counter) { Gitlab::UsageDataCounters::HLLRedisCounter }
+    describe '#value', :clean_gitlab_redis_shared_state do
+      let(:counter) { Gitlab::UsageDataCounters::HLLRedisCounter }
 
-    before do
-      counter.track_event(:users_creating_work_items, values: 1, time: 1.week.ago)
-      counter.track_event(:users_updating_work_item_title, values: 1, time: 1.week.ago)
-      counter.track_event(:users_updating_work_item_dates, values: 2, time: 1.week.ago)
-    end
+      before do
+        counter.track_event(:users_creating_work_items, values: 1, time: 1.week.ago)
+        counter.track_event(:users_updating_work_item_title, values: 1, time: 1.week.ago)
+        counter.track_event(:users_updating_work_item_dates, values: 2, time: 1.week.ago)
+        counter.track_event(:users_updating_work_item_iteration, values: 2, time: 1.week.ago)
+      end
 
-    it 'has correct value' do
-      expect(described_class.new(metric_definition).value).to eq 2
+      it 'has correct value' do
+        expect(described_class.new(metric_definition).value).to eq 2
+      end
     end
   end
 end
diff --git a/spec/migrations/populate_releases_access_level_from_repository_spec.rb b/spec/migrations/populate_releases_access_level_from_repository_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2bb97662923c09994b34f90e5b499e1dbd146654
--- /dev/null
+++ b/spec/migrations/populate_releases_access_level_from_repository_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe PopulateReleasesAccessLevelFromRepository, :migration do
+  let(:projects) { table(:projects) }
+  let(:groups) { table(:namespaces) }
+  let(:project_features) { table(:project_features) }
+
+  let(:group) { groups.create!(name: 'test-group', path: 'test-group') }
+  let(:project) { projects.create!(namespace_id: group.id, project_namespace_id: group.id) }
+  let(:project_feature) do
+    project_features.create!(project_id: project.id, pages_access_level: 20, **project_feature_attributes)
+  end
+
+  # repository_access_level and releases_access_level default to ENABLED
+  describe '#up' do
+    context 'when releases_access_level is greater than repository_access_level' do
+      let(:project_feature_attributes) { { repository_access_level: ProjectFeature::PRIVATE } }
+
+      it 'reduces releases_access_level to match repository_access_level' do
+        expect { migrate! }.to change { project_feature.reload.releases_access_level }
+                           .from(ProjectFeature::ENABLED)
+                           .to(ProjectFeature::PRIVATE)
+      end
+    end
+
+    context 'when releases_access_level is less than repository_access_level' do
+      let(:project_feature_attributes) { { releases_access_level: ProjectFeature::DISABLED } }
+
+      it 'does not change releases_access_level' do
+        expect { migrate! }.not_to change { project_feature.reload.releases_access_level }
+                           .from(ProjectFeature::DISABLED)
+      end
+    end
+  end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index ec0fe456d990fe2fe67ba2326df8de0768ebed32..9713734e97ab2ebc817e8f9aa093956d1baaceab 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -632,15 +632,6 @@
       it { expect(subject).to be_falsey }
     end
 
-    context 'when prevent_outdated_deployment_jobs FF is disabled' do
-      before do
-        stub_feature_flags(prevent_outdated_deployment_jobs: false)
-        expect(build.deployment).not_to receive(:rollback?)
-      end
-
-      it { expect(subject).to be_falsey }
-    end
-
     context 'when build can prevent rollback deployment' do
       before do
         expect(build.deployment).to receive(:older_than_last_successful_deployment?).and_return(true)
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 87fa5289795b0b9be9ea97ecf57f478291ff4259..bf1cf9856a0856c43ef90a50cff8a995f3e2224e 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -171,11 +171,22 @@
       end
 
       it 'executes Deployments::DropOlderDeploymentsWorker asynchronously' do
+        stub_feature_flags(prevent_outdated_deployment_jobs: false)
+
         expect(Deployments::DropOlderDeploymentsWorker)
             .to receive(:perform_async).once.with(deployment.id)
 
         deployment.run!
       end
+
+      it 'does not execute Deployments::DropOlderDeploymentsWorker when FF enabled' do
+        stub_feature_flags(prevent_outdated_deployment_jobs: true)
+
+        expect(Deployments::DropOlderDeploymentsWorker)
+          .not_to receive(:perform_async).with(deployment.id)
+
+        deployment.run!
+      end
     end
 
     context 'when deployment succeeded' do
diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb
index 3f6a890654a397f9ce9d7eba806415de420e627c..45b66fa12dd5fe89352c237343aa4be417a5e4a9 100644
--- a/spec/models/namespace/aggregation_schedule_spec.rb
+++ b/spec/models/namespace/aggregation_schedule_spec.rb
@@ -12,14 +12,14 @@
   describe "#default_lease_timeout" do
     subject(:default_lease_timeout) { default_timeout }
 
-    it { is_expected.to eq 1.hour.to_i }
+    it { is_expected.to eq 30.minutes.to_i }
 
     context 'when remove_namespace_aggregator_delay FF is disabled' do
       before do
         stub_feature_flags(remove_namespace_aggregator_delay: false)
       end
 
-      it { is_expected.to eq 1.5.hours.to_i }
+      it { is_expected.to eq 1.hour.to_i }
     end
   end
 
diff --git a/workhorse/go.mod b/workhorse/go.mod
index d2d5e4b44f9477ee31529c28fc45f1ef2333dc14..0843fe481305d8dee3749e6ca8d085e7b2486a68 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -34,7 +34,7 @@ require (
 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
 	golang.org/x/net v0.0.0-20220722155237-a158d28d115b
 	golang.org/x/tools v0.1.12
-	google.golang.org/grpc v1.50.0
+	google.golang.org/grpc v1.50.1
 	google.golang.org/protobuf v1.28.1
 	honnef.co/go/tools v0.3.3
 )
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 3795e8aae7a9bbecce4703a39610df1a92701fdd..9515f7d0384ac2d498e58f6f338466adfac99307 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -1550,8 +1550,8 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
-google.golang.org/grpc v1.50.0 h1:fPVVDxY9w++VjTZsYvXWqEf9Rqar/e+9zYfxKK+W+YU=
-google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=