diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh
index d84bf77ed54cf..2787d6f5b68a0 100644
--- a/.buildkite/scripts/steps/functional/performance_playwright.sh
+++ b/.buildkite/scripts/steps/functional/performance_playwright.sh
@@ -39,8 +39,13 @@ if [ "$BUILDKITE_PIPELINE_SLUG" == "kibana-performance-data-set-extraction" ]; t
   node scripts/run_performance.js --kibana-install-dir "$KIBANA_BUILD_LOCATION" --skip-warmup
 else
   # pipeline should use bare metal static worker
-  echo "--- Running performance tests"
-  node scripts/run_performance.js --kibana-install-dir "$KIBANA_BUILD_LOCATION"
+  if [[ -z "${JOURNEYS_GROUP+x}" ]]; then
+    echo "--- Running performance tests"
+    node scripts/run_performance.js --kibana-install-dir "$KIBANA_BUILD_LOCATION"
+  else
+    echo "--- Running performance tests: '$JOURNEYS_GROUP' group"
+    node scripts/run_performance.js --kibana-install-dir "$KIBANA_BUILD_LOCATION" --group "$JOURNEYS_GROUP"
+  fi
 fi
 
 echo "--- Upload journey step screenshots"
diff --git a/.devcontainer/.env.template b/.devcontainer/.env.template
index 3ca02c49bfa9c..b812dc9659a02 100644
--- a/.devcontainer/.env.template
+++ b/.devcontainer/.env.template
@@ -1,4 +1,5 @@
 # /bin/bash or /bin/zsh (oh-my-zsh is installed by default as well)
 SHELL=/bin/bash
-# Switch to 1 to enable FIPS environment, any other value to disable
+# Switch to 1 to enable FIPS environment, any other value to disable,
+# then close and reopen a new terminal to setup the environment
 FIPS=0
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 539e23a4a3a31..142e5ce933777 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -49,6 +49,9 @@ WORKDIR ${KBN_DIR}
 
 # Node and NVM setup
 COPY .node-version /tmp/
+
+USER vscode
+
 RUN mkdir -p $NVM_DIR && \
   curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh | bash && \
   . "$NVM_DIR/nvm.sh" && \
@@ -61,6 +64,8 @@ RUN mkdir -p $NVM_DIR && \
   echo "source $NVM_DIR/nvm.sh" >> ${HOME}/.zshrc  && \
   chown -R 1000:1000 "${HOME}/.npm"
 
+USER root
+
 # Reload the env everytime a new shell is opened incase the .env file changed.
 RUN echo "source $KBN_DIR/.devcontainer/scripts/env.sh" >> ${HOME}/.bashrc && \
   echo "source $KBN_DIR/.devcontainer/scripts/env.sh" >> ${HOME}/.zshrc
diff --git a/dev_docs/tutorials/performance/adding_performance_journey.mdx b/dev_docs/tutorials/performance/adding_performance_journey.mdx
index 2f7f37452c285..a9c4fe7ef6bc5 100644
--- a/dev_docs/tutorials/performance/adding_performance_journey.mdx
+++ b/dev_docs/tutorials/performance/adding_performance_journey.mdx
@@ -89,6 +89,27 @@ simulate real life internet connection. This means that all requests have a fixe
 In order to keep track on performance metrics stability, journeys are run on main branch with a scheduled interval.
 Bare metal machine is used to produce results as stable and reproducible as possible.
 
+#### Running subset of journeys for the PR
+
+Some code changes might affect the Kibana performance and it might be benefitial to run relevant journeys against the PR
+and compare performance metrics vs. the ones on main branch.
+
+In oder to trigger the build for Kibana PR, you can follow these steps:
+
+- Create a new kibana-single-user-performance [build](https://buildkite.com/elastic/kibana-single-user-performance#new)
+- Provide the following arguments:
+  - Branch: `refs/pull/<PR_number>/head`
+  - Under Options, set the environment variable: `JOURNEYS_GROUP=<group_name>`
+
+Currently supported journey groups:
+
+- kibanaStartAndLoad
+- crud
+- dashboard
+- discover
+- maps
+- ml
+
 #### Machine specifications
 
 All benchmarks are run on bare-metal machines with the [following specifications](https://www.hetzner.com/dedicated-rootserver/ex100):
diff --git a/package.json b/package.json
index a152b84fb6592..49b45c263c020 100644
--- a/package.json
+++ b/package.json
@@ -1801,7 +1801,7 @@
     "tape": "^5.0.1",
     "terser": "^5.32.0",
     "terser-webpack-plugin": "^4.2.3",
-    "tough-cookie": "^4.1.4",
+    "tough-cookie": "^5.0.0",
     "tree-kill": "^1.2.2",
     "ts-morph": "^15.1.0",
     "tsd": "^0.31.1",
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
index 9ef4f740167fe..dc1ad36f01c5e 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
@@ -11,7 +11,7 @@ import { registerAnalyticsContextProviderMock } from './chrome_service.test.mock
 import { shallow, mount } from 'enzyme';
 import React from 'react';
 import * as Rx from 'rxjs';
-import { toArray } from 'rxjs';
+import { toArray, firstValueFrom } from 'rxjs';
 import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks';
 import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks';
 import { httpServiceMock } from '@kbn/core-http-browser-mocks';
@@ -556,6 +556,39 @@ describe('start', () => {
                   `);
     });
   });
+
+  describe('side nav', () => {
+    describe('isCollapsed$', () => {
+      it('should return false by default', async () => {
+        const { chrome, service } = await start();
+        const isCollapsed = await firstValueFrom(chrome.sideNav.getIsCollapsed$());
+        service.stop();
+        expect(isCollapsed).toBe(false);
+      });
+
+      it('should read the localStorage value', async () => {
+        store.set('core.chrome.isSideNavCollapsed', 'true');
+        const { chrome, service } = await start();
+        const isCollapsed = await firstValueFrom(chrome.sideNav.getIsCollapsed$());
+        service.stop();
+        expect(isCollapsed).toBe(true);
+      });
+    });
+
+    describe('setIsCollapsed', () => {
+      it('should update the isCollapsed$ observable', async () => {
+        const { chrome, service } = await start();
+        const isCollapsed$ = chrome.sideNav.getIsCollapsed$();
+        const isCollapsed = await firstValueFrom(isCollapsed$);
+
+        chrome.sideNav.setIsCollapsed(!isCollapsed);
+
+        const updatedIsCollapsed = await firstValueFrom(isCollapsed$);
+        service.stop();
+        expect(updatedIsCollapsed).toBe(!isCollapsed);
+      });
+    });
+  });
 });
 
 describe('stop', () => {
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
index 3eb846cc15dc8..4605dd02fd229 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
@@ -54,6 +54,7 @@ import type { InternalChromeStart } from './types';
 import { HeaderTopBanner } from './ui/header/header_top_banner';
 
 const IS_LOCKED_KEY = 'core.chrome.isLocked';
+const IS_SIDENAV_COLLAPSED_KEY = 'core.chrome.isSideNavCollapsed';
 const SNAPSHOT_REGEX = /-snapshot/i;
 
 interface ConstructorParams {
@@ -86,7 +87,9 @@ export class ChromeService {
   private readonly docTitle = new DocTitleService();
   private readonly projectNavigation: ProjectNavigationService;
   private mutationObserver: MutationObserver | undefined;
-  private readonly isSideNavCollapsed$ = new BehaviorSubject<boolean>(true);
+  private readonly isSideNavCollapsed$ = new BehaviorSubject(
+    localStorage.getItem(IS_SIDENAV_COLLAPSED_KEY) === 'true'
+  );
   private logger: Logger;
   private isServerless = false;
 
@@ -360,6 +363,11 @@ export class ChromeService {
       projectNavigation.setProjectName(projectName);
     };
 
+    const setIsSideNavCollapsed = (isCollapsed: boolean) => {
+      localStorage.setItem(IS_SIDENAV_COLLAPSED_KEY, JSON.stringify(isCollapsed));
+      this.isSideNavCollapsed$.next(isCollapsed);
+    };
+
     if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) {
       notifications.toasts.addWarning({
         title: mountReactNode(
@@ -431,9 +439,8 @@ export class ChromeService {
                 docLinks={docLinks}
                 kibanaVersion={injectedMetadata.getKibanaVersion()}
                 prependBasePath={http.basePath.prepend}
-                toggleSideNav={(isCollapsed) => {
-                  this.isSideNavCollapsed$.next(isCollapsed);
-                }}
+                isSideNavCollapsed$={this.isSideNavCollapsed$}
+                toggleSideNav={setIsSideNavCollapsed}
               >
                 <SideNavComponent activeNodes={activeNodes} />
               </ProjectHeader>
@@ -556,7 +563,10 @@ export class ChromeService {
       getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)),
       setChromeStyle,
       getChromeStyle$: () => chromeStyle$,
-      getIsSideNavCollapsed$: () => this.isSideNavCollapsed$.asObservable(),
+      sideNav: {
+        getIsCollapsed$: () => this.isSideNavCollapsed$.asObservable(),
+        setIsCollapsed: setIsSideNavCollapsed,
+      },
       getActiveSolutionNavId$: () => projectNavigation.getActiveSolutionNavId$(),
       project: {
         setHome: setProjectHome,
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
index 3e4bc7a8a1fe5..743cd1726e03e 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
@@ -35,6 +35,7 @@ describe('Header', () => {
     navControlsCenter$: Rx.of([]),
     navControlsRight$: Rx.of([]),
     customBranding$: Rx.of({}),
+    isSideNavCollapsed$: Rx.of(false),
     prependBasePath: (str) => `hello/world/${str}`,
     toggleSideNav: jest.fn(),
   };
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
index f3d92ff31638d..bf8b103709260 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
@@ -130,6 +130,7 @@ export interface Props {
   navControlsCenter$: Observable<ChromeNavControl[]>;
   navControlsRight$: Observable<ChromeNavControl[]>;
   prependBasePath: (url: string) => string;
+  isSideNavCollapsed$: Observable<boolean>;
   toggleSideNav: (isCollapsed: boolean) => void;
 }
 
@@ -248,7 +249,12 @@ export const ProjectHeader = ({
           <EuiHeader position="fixed" className="header__firstBar">
             <EuiHeaderSection grow={false} css={headerCss.leftHeaderSection}>
               <Router history={application.history}>
-                <ProjectNavigation toggleSideNav={toggleSideNav}>{children}</ProjectNavigation>
+                <ProjectNavigation
+                  isSideNavCollapsed$={observables.isSideNavCollapsed$}
+                  toggleSideNav={toggleSideNav}
+                >
+                  {children}
+                </ProjectNavigation>
               </Router>
 
               <EuiHeaderSectionItem>
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx
index 9daf99bbbfc23..a607d69fb0633 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/navigation.tsx
@@ -7,34 +7,28 @@
  * License v3.0 only", or the "Server Side Public License, v 1".
  */
 
-import React, { useEffect, useRef, FC, PropsWithChildren } from 'react';
+import React, { FC, PropsWithChildren } from 'react';
 import { EuiCollapsibleNavBeta } from '@elastic/eui';
-import useLocalStorage from 'react-use/lib/useLocalStorage';
+import useObservable from 'react-use/lib/useObservable';
+import type { Observable } from 'rxjs';
 
-const LOCAL_STORAGE_IS_COLLAPSED_KEY = 'PROJECT_NAVIGATION_COLLAPSED' as const;
+interface Props {
+  toggleSideNav: (isVisible: boolean) => void;
+  isSideNavCollapsed$: Observable<boolean>;
+}
 
-export const ProjectNavigation: FC<
-  PropsWithChildren<{ toggleSideNav: (isVisible: boolean) => void }>
-> = ({ children, toggleSideNav }) => {
-  const isMounted = useRef(false);
-  const [isCollapsed, setIsCollapsed] = useLocalStorage(LOCAL_STORAGE_IS_COLLAPSED_KEY, false);
-  const onCollapseToggle = (nextIsCollapsed: boolean) => {
-    setIsCollapsed(nextIsCollapsed);
-    toggleSideNav(nextIsCollapsed);
-  };
-
-  useEffect(() => {
-    if (!isMounted.current && isCollapsed !== undefined) {
-      toggleSideNav(isCollapsed);
-    }
-    isMounted.current = true;
-  }, [isCollapsed, toggleSideNav]);
+export const ProjectNavigation: FC<PropsWithChildren<Props>> = ({
+  children,
+  isSideNavCollapsed$,
+  toggleSideNav,
+}) => {
+  const isCollapsed = useObservable(isSideNavCollapsed$, false);
 
   return (
     <EuiCollapsibleNavBeta
       data-test-subj="projectLayoutSideNav"
-      initialIsCollapsed={isCollapsed}
-      onCollapseToggle={onCollapseToggle}
+      isCollapsed={isCollapsed}
+      onCollapseToggle={toggleSideNav}
       css={
         isCollapsed
           ? undefined
diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
index e408c3a7c3c51..1380214e4311e 100644
--- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
+++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
@@ -51,7 +51,10 @@ const createStartContractMock = () => {
     setBadge: jest.fn(),
     getBreadcrumbs$: jest.fn(),
     setBreadcrumbs: jest.fn(),
-    getIsSideNavCollapsed$: jest.fn(),
+    sideNav: {
+      getIsCollapsed$: jest.fn(),
+      setIsCollapsed: jest.fn(),
+    },
     getBreadcrumbsAppendExtension$: jest.fn(),
     setBreadcrumbsAppendExtension: jest.fn(),
     getGlobalHelpExtensionMenuLinks$: jest.fn(),
@@ -94,7 +97,7 @@ const createStartContractMock = () => {
   startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false));
   startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([]));
   startContract.hasHeaderBanner$.mockReturnValue(new BehaviorSubject(false));
-  startContract.getIsSideNavCollapsed$.mockReturnValue(new BehaviorSubject(false));
+  startContract.sideNav.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
   return startContract;
 };
 
diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts
index c326e7107aa2a..1e9ea66bc0920 100644
--- a/packages/core/chrome/core-chrome-browser/src/contracts.ts
+++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts
@@ -173,10 +173,18 @@ export interface ChromeStart {
    */
   getChromeStyle$(): Observable<ChromeStyle>;
 
-  /**
-   * Get an observable of the current collapsed state of the side nav.
-   */
-  getIsSideNavCollapsed$(): Observable<boolean>;
+  sideNav: {
+    /**
+     * Get an observable of the current collapsed state of the side nav.
+     */
+    getIsCollapsed$(): Observable<boolean>;
+
+    /**
+     * Set the collapsed state of the side nav.
+     * @param isCollapsed The collapsed state of the side nav.
+     */
+    setIsCollapsed(isCollapsed: boolean): void;
+  };
 
   /**
    * Get the id of the currently active project navigation or `null` otherwise.
diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx
index c6f301bbdf69f..658bf96dc76c9 100644
--- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx
+++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.tsx
@@ -14,6 +14,7 @@ import { UiCounterMetricType } from '@kbn/analytics';
 import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
 import { Draggable } from '@kbn/dom-drag-drop';
 import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
+import { Filter } from '@kbn/es-query';
 import type { SearchMode } from '../../types';
 import { FieldItemButton, type FieldItemButtonProps } from '../../components/field_item_button';
 import {
@@ -200,6 +201,10 @@ export interface UnifiedFieldListItemProps {
    * Item size
    */
   size: FieldItemButtonProps<DataViewField>['size'];
+  /**
+   * Custom filters to apply for the field list, ex: namespace custom filter
+   */
+  additionalFilters?: Filter[];
 }
 
 function UnifiedFieldListItemComponent({
@@ -223,6 +228,7 @@ function UnifiedFieldListItemComponent({
   groupIndex,
   itemIndex,
   size,
+  additionalFilters,
 }: UnifiedFieldListItemProps) {
   const [infoIsOpen, setOpen] = useState(false);
 
@@ -288,6 +294,7 @@ function UnifiedFieldListItemComponent({
           multiFields={multiFields}
           dataView={dataView}
           onAddFilter={addFilterAndClosePopover}
+          additionalFilters={additionalFilters}
         />
 
         {searchMode === 'documents' && multiFields && (
diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item_stats.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item_stats.tsx
index c83a0694c7b67..223a5e15ca6e7 100644
--- a/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item_stats.tsx
+++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item_stats.tsx
@@ -27,10 +27,11 @@ export interface UnifiedFieldListItemStatsProps {
   dataView: DataView;
   multiFields?: Array<{ field: DataViewField; isSelected: boolean }>;
   onAddFilter: FieldStatsProps['onAddFilter'];
+  additionalFilters?: FieldStatsProps['filters'];
 }
 
 export const UnifiedFieldListItemStats: React.FC<UnifiedFieldListItemStatsProps> = React.memo(
-  ({ stateService, services, field, dataView, multiFields, onAddFilter }) => {
+  ({ stateService, services, field, dataView, multiFields, onAddFilter, additionalFilters }) => {
     const querySubscriberResult = useQuerySubscriber({
       data: services.data,
       timeRangeUpdatesType: stateService.creationOptions.timeRangeUpdatesType,
@@ -55,6 +56,11 @@ export const UnifiedFieldListItemStats: React.FC<UnifiedFieldListItemStatsProps>
       [services]
     );
 
+    const filters = useMemo(
+      () => [...(querySubscriberResult.filters ?? []), ...(additionalFilters ?? [])],
+      [querySubscriberResult.filters, additionalFilters]
+    );
+
     if (!hasQuerySubscriberData(querySubscriberResult)) {
       return null;
     }
@@ -63,7 +69,7 @@ export const UnifiedFieldListItemStats: React.FC<UnifiedFieldListItemStatsProps>
       <FieldStats
         services={statsServices}
         query={querySubscriberResult.query}
-        filters={querySubscriberResult.filters}
+        filters={filters}
         fromDate={querySubscriberResult.fromDate}
         toDate={querySubscriberResult.toDate}
         dataViewOrDataViewId={dataView}
diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
index 238022fb150f4..a7d8fb4616cb0 100644
--- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
+++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar.tsx
@@ -50,6 +50,7 @@ export type UnifiedFieldListSidebarCustomizableProps = Pick<
   | 'onAddFilter'
   | 'onAddFieldToWorkspace'
   | 'onRemoveFieldFromWorkspace'
+  | 'additionalFilters'
 > & {
   /**
    * All fields: fields from data view and unmapped fields or columns from text-based search
@@ -168,6 +169,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
   onDeleteField,
   onToggleSidebar,
   additionalFieldGroups,
+  additionalFilters,
 }) => {
   const { dataViews, core } = services;
   const useNewFieldsApi = useMemo(
@@ -285,6 +287,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
             groupName === FieldsGroupNames.SelectedFields ||
             Boolean(selectedFieldsState.selectedFieldsMap[field.name])
           }
+          additionalFilters={additionalFilters}
         />
       </li>
     ),
@@ -304,6 +307,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
       onDeleteField,
       workspaceSelectedFieldNames,
       selectedFieldsState.selectedFieldsMap,
+      additionalFilters,
     ]
   );
 
diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx
index 6bbf075546775..f43c87ca430ae 100644
--- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx
+++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx
@@ -117,6 +117,7 @@ const UnifiedFieldListSidebarContainer = memo(
         prependInFlyout,
         variant = 'responsive',
         onFieldEdited,
+        additionalFilters,
       } = props;
       const [stateService] = useState<UnifiedFieldListSidebarContainerStateService>(
         createStateService({ options: getCreationOptions() })
@@ -151,11 +152,16 @@ const UnifiedFieldListSidebarContainer = memo(
       const searchMode: SearchMode | undefined = querySubscriberResult.searchMode;
       const isAffectedByGlobalFilter = Boolean(querySubscriberResult.filters?.length);
 
+      const filters = useMemo(
+        () => [...(querySubscriberResult.filters ?? []), ...(additionalFilters ?? [])],
+        [querySubscriberResult.filters, additionalFilters]
+      );
+
       const { isProcessing, refetchFieldsExistenceInfo } = useExistingFieldsFetcher({
         disableAutoFetching: stateService.creationOptions.disableFieldsExistenceAutoFetching,
         dataViews: searchMode === 'documents' && dataView ? [dataView] : [],
         query: querySubscriberResult.query,
-        filters: querySubscriberResult.filters,
+        filters,
         fromDate: querySubscriberResult.fromDate,
         toDate: querySubscriberResult.toDate,
         services,
diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx
index fec11110f8b6a..1b0102533e53e 100644
--- a/packages/shared-ux/chrome/navigation/src/services.tsx
+++ b/packages/shared-ux/chrome/navigation/src/services.tsx
@@ -36,7 +36,7 @@ export const NavigationKibanaProvider: FC<PropsWithChildren<NavigationKibanaDepe
   const { chrome, http, analytics } = core;
   const { basePath } = http;
   const { navigateToUrl } = core.application;
-  const isSideNavCollapsed = useObservable(chrome.getIsSideNavCollapsed$(), true);
+  const isSideNavCollapsed = useObservable(chrome.sideNav.getIsCollapsed$(), true);
 
   const value: NavigationServices = useMemo(
     () => ({
diff --git a/packages/shared-ux/chrome/navigation/src/types.ts b/packages/shared-ux/chrome/navigation/src/types.ts
index c7f882c3580a6..9db808e37799a 100644
--- a/packages/shared-ux/chrome/navigation/src/types.ts
+++ b/packages/shared-ux/chrome/navigation/src/types.ts
@@ -53,7 +53,9 @@ export interface NavigationKibanaDependencies {
       navLinks: {
         getNavLinks$: () => Observable<Readonly<ChromeNavLink[]>>;
       };
-      getIsSideNavCollapsed$: () => Observable<boolean>;
+      sideNav: {
+        getIsCollapsed$: () => Observable<boolean>;
+      };
     };
     http: {
       basePath: BasePathService;
diff --git a/renovate.json b/renovate.json
index 6f3b61c6e1b12..d013a49ea37bd 100644
--- a/renovate.json
+++ b/renovate.json
@@ -493,6 +493,15 @@
       "labels": ["Team: Sec Eng Productivity", "release_note:skip", "backport:all-open"],
       "minimumReleaseAge": "7 days",
       "enabled": true
+    },
+    {
+      "groupName": "@mswjs/http-middleware",
+      "matchPackageNames": ["@mswjs/http-middleware"],
+      "reviewers": ["team:kibana-cloud-security-posture"],
+      "matchBaseBranches": ["main"],
+      "labels": ["Team:Cloud Security", "release_note:skip", "backport:skip"],
+      "minimumReleaseAge": "7 days",
+      "enabled": true
     }
   ],
   "customManagers": [
diff --git a/src/dev/performance/run_performance_cli.ts b/src/dev/performance/run_performance_cli.ts
index bb4192779207a..72f2bc46495a2 100644
--- a/src/dev/performance/run_performance_cli.ts
+++ b/src/dev/performance/run_performance_cli.ts
@@ -35,6 +35,19 @@ interface TestRunProps extends EsRunProps {
   kibanaInstallDir: string | undefined;
 }
 
+interface JourneyTargetGroups {
+  [key: string]: string[];
+}
+
+const journeyTargetGroups: JourneyTargetGroups = {
+  kibanaStartAndLoad: ['login'],
+  crud: ['tags_listing_page', 'dashboard_listing_page'],
+  dashboard: ['ecommerce_dashboard', 'data_stress_test_lens', 'flight_dashboard'],
+  discover: ['many_fields_discover', 'many_fields_discover_esql'],
+  maps: ['ecommerce_dashboard_map_only'],
+  ml: ['aiops_log_rate_analysis', 'many_fields_transform', 'tsdb_logs_data_visualizer'],
+};
+
 const readFilesRecursively = (dir: string, callback: Function) => {
   const files = fs.readdirSync(dir);
   files.forEach((file) => {
@@ -48,6 +61,44 @@ const readFilesRecursively = (dir: string, callback: Function) => {
   });
 };
 
+const getAllJourneys = (dir: string) => {
+  const journeys: Journey[] = [];
+
+  readFilesRecursively(dir, (filePath: string) =>
+    journeys.push({
+      name: path.parse(filePath).name,
+      path: path.resolve(dir, filePath),
+    })
+  );
+
+  return journeys;
+};
+
+const getJourneysToRun = ({ journeyPath, group }: { journeyPath?: string; group?: string }) => {
+  if (group && typeof group === 'string') {
+    if (!(group in journeyTargetGroups)) {
+      throw createFlagError(`Group '${group}' is not defined, try again`);
+    }
+
+    const fileNames = journeyTargetGroups[group];
+    const dir = path.resolve(REPO_ROOT, JOURNEY_BASE_PATH);
+
+    return getAllJourneys(dir).filter((journey) => fileNames.includes(journey.name));
+  }
+
+  if (journeyPath && !fs.existsSync(journeyPath)) {
+    throw createFlagError('--journey-path must be an existing path');
+  }
+
+  if (journeyPath && fs.statSync(journeyPath).isFile()) {
+    return [{ name: path.parse(journeyPath).name, path: journeyPath }];
+  } else {
+    // default dir is x-pack/performance/journeys_e2e
+    const dir = journeyPath ?? path.resolve(REPO_ROOT, JOURNEY_BASE_PATH);
+    return getAllJourneys(dir);
+  }
+};
+
 async function startEs(props: EsRunProps) {
   const { procRunner, log, logsDir } = props;
   await procRunner.run('es', {
@@ -115,29 +166,17 @@ run(
     const skipWarmup = flagsReader.boolean('skip-warmup');
     const kibanaInstallDir = flagsReader.path('kibana-install-dir');
     const journeyPath = flagsReader.path('journey-path');
+    const group = flagsReader.string('group');
 
-    if (kibanaInstallDir && !fs.existsSync(kibanaInstallDir)) {
-      throw createFlagError('--kibana-install-dir must be an existing directory');
+    if (group && journeyPath) {
+      throw createFlagError('--group and --journeyPath cannot be used simultaneously');
     }
 
-    if (journeyPath && !fs.existsSync(journeyPath)) {
-      throw createFlagError('--journey-path must be an existing path');
+    if (kibanaInstallDir && !fs.existsSync(kibanaInstallDir)) {
+      throw createFlagError('--kibana-install-dir must be an existing directory');
     }
 
-    const journeys: Journey[] = [];
-
-    if (journeyPath && fs.statSync(journeyPath).isFile()) {
-      journeys.push({ name: path.parse(journeyPath).name, path: journeyPath });
-    } else {
-      // default dir is x-pack/performance/journeys_e2e
-      const dir = journeyPath ?? path.resolve(REPO_ROOT, JOURNEY_BASE_PATH);
-      readFilesRecursively(dir, (filePath: string) =>
-        journeys.push({
-          name: path.parse(filePath).name,
-          path: path.resolve(dir, filePath),
-        })
-      );
-    }
+    const journeys = getJourneysToRun({ journeyPath, group });
 
     if (journeys.length === 0) {
       throw new Error('No journeys found');
@@ -191,13 +230,14 @@ run(
   },
   {
     flags: {
-      string: ['kibana-install-dir', 'journey-path'],
+      string: ['kibana-install-dir', 'journey-path', 'group'],
       boolean: ['skip-warmup'],
       help: `
       --kibana-install-dir=dir      Run Kibana from existing install directory instead of from source
       --journey-path=path           Define path to performance journey or directory with multiple journeys
                                     that should be executed. '${JOURNEY_BASE_PATH}' is run by default
       --skip-warmup                 Journey will be executed without warmup (TEST phase only)
+      --group                       Run subset of journeys, defined in the specified group
     `,
     },
   }
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json
index f109f849be68c..4180fc878a0ba 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/esql.query.json
@@ -5,7 +5,16 @@
       "filter_path": [],
       "human": "__flag__",
       "pretty": "__flag__",
-      "format": "",
+      "format": [
+        "csv",
+        "json",
+        "tsv",
+        "txt",
+        "yaml",
+        "cbor",
+        "smile",
+        "arrow"
+      ],
       "delimiter": ""
     },
     "methods": [
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.resolve_index.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.resolve_index.json
index 77a3fc9cc18a2..790e5ecd171f1 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.resolve_index.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.resolve_index.json
@@ -11,7 +11,9 @@
         "closed",
         "hidden",
         "none"
-      ]
+      ],
+      "ignore_unavailable": "__flag__",
+      "allow_no_indices": "__flag__"
     },
     "methods": [
       "GET"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.delete_geoip_database.json b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.delete_geoip_database.json
new file mode 100644
index 0000000000000..109dfce014012
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.delete_geoip_database.json
@@ -0,0 +1,31 @@
+{
+  "ingest.delete_geoip_database": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "master_timeout": [
+        "30s",
+        "-1",
+        "0"
+      ],
+      "timeout": [
+        "30s",
+        "-1",
+        "0"
+      ]
+    },
+    "methods": [
+      "DELETE"
+    ],
+    "patterns": [
+      "_ingest/geoip/database/{id}"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/delete-geoip-database-api.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.get_geoip_database.json b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.get_geoip_database.json
new file mode 100644
index 0000000000000..e57b36f09cbf7
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.get_geoip_database.json
@@ -0,0 +1,27 @@
+{
+  "ingest.get_geoip_database": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "master_timeout": [
+        "30s",
+        "-1",
+        "0"
+      ]
+    },
+    "methods": [
+      "GET"
+    ],
+    "patterns": [
+      "_ingest/geoip/database",
+      "_ingest/geoip/database/{id}"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/get-geoip-database-api.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.put_geoip_database.json b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.put_geoip_database.json
new file mode 100644
index 0000000000000..675c01239af38
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/ingest.put_geoip_database.json
@@ -0,0 +1,31 @@
+{
+  "ingest.put_geoip_database": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "master_timeout": [
+        "30s",
+        "-1",
+        "0"
+      ],
+      "timeout": [
+        "30s",
+        "-1",
+        "0"
+      ]
+    },
+    "methods": [
+      "PUT"
+    ],
+    "patterns": [
+      "_ingest/geoip/database/{id}"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/put-geoip-database-api.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/msearch.json b/src/plugins/console/server/lib/spec_definitions/json/generated/msearch.json
index 771c603c87924..58308e10f4fbe 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/msearch.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/msearch.json
@@ -16,6 +16,7 @@
       ],
       "ignore_throttled": "__flag__",
       "ignore_unavailable": "__flag__",
+      "include_named_queries_score": "__flag__",
       "max_concurrent_searches": "",
       "max_concurrent_shard_requests": [
         "5"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.delete.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_rule.json
similarity index 93%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.delete.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_rule.json
index eea5d085b03bc..4255ae1672064 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.delete.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_rule.json
@@ -1,5 +1,5 @@
 {
-  "query_rule.delete": {
+  "query_rules.delete_rule": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.delete.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_ruleset.json
similarity index 92%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.delete.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_ruleset.json
index c0e564ec5c212..d4f34e7d76716 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.delete.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.delete_ruleset.json
@@ -1,5 +1,5 @@
 {
-  "query_ruleset.delete": {
+  "query_rules.delete_ruleset": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.get.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_rule.json
similarity index 94%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.get.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_rule.json
index 27564d1fe7b16..fdd424c2a2d1b 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.get.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_rule.json
@@ -1,5 +1,5 @@
 {
-  "query_rule.get": {
+  "query_rules.get_rule": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.get.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_ruleset.json
similarity index 93%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.get.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_ruleset.json
index 424b749bc3a19..23cff910d815e 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.get.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.get_ruleset.json
@@ -1,5 +1,5 @@
 {
-  "query_ruleset.get": {
+  "query_rules.get_ruleset": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.list.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.list_rulesets.json
similarity index 93%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.list.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.list_rulesets.json
index 3213cdbe9d6d6..c20f8050c5c91 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.list.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.list_rulesets.json
@@ -1,5 +1,5 @@
 {
-  "query_ruleset.list": {
+  "query_rules.list_rulesets": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.put.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_rule.json
similarity index 94%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.put.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_rule.json
index 346eeba09e6ed..2653a15dab650 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_rule.put.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_rule.json
@@ -1,5 +1,5 @@
 {
-  "query_rule.put": {
+  "query_rules.put_rule": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.put.json b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_ruleset.json
similarity index 93%
rename from src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.put.json
rename to src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_ruleset.json
index 924e449ab29d3..a4c0ad3c72d94 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/query_ruleset.put.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/query_rules.put_ruleset.json
@@ -1,5 +1,5 @@
 {
-  "query_ruleset.put": {
+  "query_rules.put_ruleset": {
     "url_params": {
       "error_trace": "__flag__",
       "filter_path": [],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/search.json b/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
index dd41962508677..ffa3709b9ae3f 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
@@ -29,6 +29,7 @@
       "explain": "__flag__",
       "ignore_throttled": "__flag__",
       "ignore_unavailable": "__flag__",
+      "include_named_queries_score": "__flag__",
       "lenient": "__flag__",
       "max_concurrent_shard_requests": [
         "5"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_delete_role.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_delete_role.json
new file mode 100644
index 0000000000000..cbc71a4354533
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_delete_role.json
@@ -0,0 +1,26 @@
+{
+  "security.bulk_delete_role": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "refresh": [
+        "true",
+        "false",
+        "wait_for"
+      ]
+    },
+    "methods": [
+      "DELETE"
+    ],
+    "patterns": [
+      "_security/role"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-bulk-delete-role.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_put_role.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_put_role.json
new file mode 100644
index 0000000000000..a31328b512882
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.bulk_put_role.json
@@ -0,0 +1,26 @@
+{
+  "security.bulk_put_role": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "refresh": [
+        "true",
+        "false",
+        "wait_for"
+      ]
+    },
+    "methods": [
+      "POST"
+    ],
+    "patterns": [
+      "_security/role"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-bulk-put-role.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_role.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_role.json
new file mode 100644
index 0000000000000..7648662f973c0
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_role.json
@@ -0,0 +1,22 @@
+{
+  "security.query_role": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__"
+    },
+    "methods": [
+      "GET",
+      "POST"
+    ],
+    "patterns": [
+      "_security/_query/role"
+    ],
+    "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-query-role.html",
+    "availability": {
+      "stack": true,
+      "serverless": false
+    }
+  }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_user.json b/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_user.json
index 4b8f22799b4bc..811d5c907173b 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_user.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/security.query_user.json
@@ -1,5 +1,12 @@
 {
   "security.query_user": {
+    "url_params": {
+      "error_trace": "__flag__",
+      "filter_path": [],
+      "human": "__flag__",
+      "pretty": "__flag__",
+      "with_profile_uid": "__flag__"
+    },
     "methods": [
       "GET",
       "POST"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
index 787d36fdb2ec6..2d382decb9c2e 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
@@ -30,6 +30,7 @@
       "max_docs": "",
       "pipeline": "",
       "preference": "",
+      "q": "",
       "refresh": "__flag__",
       "request_cache": "__flag__",
       "requests_per_second": [
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
index 949b897b29ff4..1c432db8e00e8 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
@@ -1,38 +1,10 @@
 {
   "cluster.health": {
     "url_params": {
-      "expand_wildcards": [
-        "open",
-        "closed",
-        "hidden",
-        "none",
-        "all"
-      ],
-      "level": [
-        "cluster",
-        "indices",
-        "shards"
-      ],
-      "local": "__flag__",
       "master_timeout": "",
       "timeout": "",
       "wait_for_active_shards": "",
-      "wait_for_nodes": "",
-      "wait_for_events": [
-        "immediate",
-        "urgent",
-        "high",
-        "normal",
-        "low",
-        "languid"
-      ],
-      "wait_for_no_relocating_shards": "__flag__",
-      "wait_for_no_initializing_shards": "__flag__",
-      "wait_for_status": [
-        "green",
-        "yellow",
-        "red"
-      ]
+      "wait_for_nodes": ""
     }
   }
 }
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/esql.query.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/esql.query.json
index 2dd1f9b50ee3b..0947840158818 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/esql.query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/esql.query.json
@@ -5,21 +5,6 @@
       "locale": "",
       "params": [],
       "query": ""
-    },
-    "url_params": {
-      "format": [
-        "cbor",
-        "csv",
-        "json",
-        "smile",
-        "txt",
-        "tsv",
-        "yaml"
-      ],
-      "drop_null_columns": [
-        "false",
-        "true"
-      ]
     }
   }
 }
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/query_rules.put_ruleset.json
similarity index 93%
rename from src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json
rename to src/plugins/console/server/lib/spec_definitions/json/overrides/query_rules.put_ruleset.json
index 9b50197db9689..21d598b0cdf7b 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/query_ruleset.put.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/query_rules.put_ruleset.json
@@ -1,5 +1,5 @@
 {
-  "query_ruleset.put": {
+  "query_rules.put_ruleset": {
     "data_autocomplete_rules": {
       "rules": [{
         "rule_id": "",
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json
deleted file mode 100644
index 1028422b303f2..0000000000000
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "search": {
-    "url_params": {
-      "error_trace": true
-    }
-  }
-}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/xpack.info.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/xpack.info.json
index fff8fda8328f9..be604c799cf26 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/xpack.info.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/xpack.info.json
@@ -5,8 +5,7 @@
         "build",
         "license",
         "features"
-      ],
-      "human": "__flag__"
+      ]
     }
   }
 }
diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx
index 97e1a34f85caf..82403ad38c710 100644
--- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx
+++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx
@@ -103,6 +103,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
     state.dataView!,
     state.isDataViewLoading,
   ]);
+  const customFilters = useInternalStateSelector((state) => state.customFilters);
+
   const dataState: DataMainMsg = useDataState(main$);
   const savedSearch = useSavedSearchInitial();
 
@@ -401,6 +403,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
                 onFieldEdited={onFieldEdited}
                 onDataViewCreated={stateContainer.actions.onDataViewCreated}
                 sidebarToggleState$={sidebarToggleState$}
+                additionalFilters={customFilters}
               />
             }
             mainPanel={
diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
index 59b008d99e494..80a3b9d412c76 100644
--- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
+++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
@@ -23,6 +23,7 @@ import {
   FieldsGroupNames,
 } from '@kbn/unified-field-list';
 import { calcFieldCounts } from '@kbn/discover-utils/src/utils/calc_field_counts';
+import { Filter } from '@kbn/es-query';
 import { PLUGIN_ID } from '../../../../../common';
 import { useDiscoverServices } from '../../../../hooks/use_discover_services';
 import { DataDocuments$ } from '../../state_management/discover_data_state_container';
@@ -127,6 +128,10 @@ export interface DiscoverSidebarResponsiveProps {
   fieldListVariant?: UnifiedFieldListSidebarContainerProps['variant'];
 
   sidebarToggleState$: BehaviorSubject<SidebarToggleState>;
+  /**
+   * Custom filters to apply for the field list, ex: namespace custom filter
+   */
+  additionalFilters?: Filter[];
 }
 
 /**
@@ -153,6 +158,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
     onAddField,
     onRemoveField,
     sidebarToggleState$,
+    additionalFilters,
   } = props;
   const [sidebarState, dispatchSidebarStateAction] = useReducer(
     discoverSidebarReducer,
@@ -383,6 +389,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
             onFieldEdited={onFieldEdited}
             prependInFlyout={prependDataViewPickerForMobile}
             additionalFieldGroups={additionalFieldGroups}
+            additionalFilters={additionalFilters}
           />
         ) : null}
       </EuiFlexItem>
diff --git a/src/plugins/navigation/public/plugin.test.ts b/src/plugins/navigation/public/plugin.test.ts
index 02f23d16de209..a88b51abba665 100644
--- a/src/plugins/navigation/public/plugin.test.ts
+++ b/src/plugins/navigation/public/plugin.test.ts
@@ -97,11 +97,11 @@ describe('Navigation Plugin', () => {
 
   describe('addSolutionNavigation()', () => {
     it('should update the solution navigation definitions', async () => {
-      const { plugin, coreStart, unifiedSearch, cloud } = setup();
+      const { plugin, coreStart, unifiedSearch, spaces } = setup();
 
       const { addSolutionNavigation } = plugin.start(coreStart, {
         unifiedSearch,
-        cloud,
+        spaces,
       });
       await new Promise((resolve) => setTimeout(resolve));
 
@@ -180,13 +180,29 @@ describe('Navigation Plugin', () => {
   });
 
   describe('isSolutionNavEnabled$', () => {
-    // This test will need to be changed when we remove the feature flag
-    it('should be off by default', async () => {
-      const { plugin, coreStart, unifiedSearch, cloud } = setup();
+    it('should be off if spaces plugin not available', async () => {
+      const { plugin, coreStart, unifiedSearch } = setup();
 
       const { isSolutionNavEnabled$ } = plugin.start(coreStart, {
         unifiedSearch,
-        cloud,
+      });
+      await new Promise((resolve) => setTimeout(resolve));
+
+      const isEnabled = await firstValueFrom(isSolutionNavEnabled$);
+      expect(isEnabled).toBe(false);
+    });
+
+    it('should be off if spaces plugin `isSolutionViewEnabled` = false', async () => {
+      const { plugin, coreStart, unifiedSearch, spaces } = setup();
+      spaces.getActiveSpace$ = jest
+        .fn()
+        .mockReturnValue(of({ solution: 'es' } as Pick<Space, 'solution'>));
+
+      spaces.isSolutionViewEnabled = false;
+
+      const { isSolutionNavEnabled$ } = plugin.start(coreStart, {
+        unifiedSearch,
+        spaces,
       });
       await new Promise((resolve) => setTimeout(resolve));
 
diff --git a/src/plugins/navigation/public/plugin.tsx b/src/plugins/navigation/public/plugin.tsx
index d2aa1f4c7f557..f382b80221642 100644
--- a/src/plugins/navigation/public/plugin.tsx
+++ b/src/plugins/navigation/public/plugin.tsx
@@ -72,10 +72,8 @@ export class NavigationPublicPlugin
     const extensions = this.topNavMenuExtensionsRegistry.getAll();
     const chrome = core.chrome as InternalChromeStart;
     const activeSpace$: Observable<Space | undefined> = spaces?.getActiveSpace$() ?? of(undefined);
-    const onCloud = cloud !== undefined; // The new side nav will initially only be available to cloud users
     const isServerless = this.initializerContext.env.packageInfo.buildFlavor === 'serverless';
-
-    this.isSolutionNavEnabled = onCloud && !isServerless;
+    this.isSolutionNavEnabled = spaces?.isSolutionViewEnabled ?? false;
 
     /*
      *
diff --git a/test/functional/apps/console/_variables.ts b/test/functional/apps/console/_variables.ts
index 298011c8e347d..a5eef8cccd15d 100644
--- a/test/functional/apps/console/_variables.ts
+++ b/test/functional/apps/console/_variables.ts
@@ -15,7 +15,8 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
   const log = getService('log');
   const PageObjects = getPageObjects(['common', 'console', 'header']);
 
-  describe('Console variables', function testConsoleVariables() {
+  // Failing: See https://github.com/elastic/kibana/issues/157776
+  describe.skip('Console variables', function testConsoleVariables() {
     this.tags('includeFirefox');
 
     before(async () => {
@@ -65,7 +66,10 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
       });
     });
 
-    describe('with variables in request body', () => {
+    // Flaky: https://github.com/elastic/kibana/issues/157776
+    // Beware that this test will pass locally and in flaky test runner, but it
+    // will fail after merged.
+    describe.skip('with variables in request body', () => {
       it('should send a successful request', async () => {
         await PageObjects.console.openConfig();
         await PageObjects.console.addNewVariable({ name: 'query1', value: '{"match_all": {}}' });
diff --git a/test/functional/apps/dashboard/group4/dashboard_empty.ts b/test/functional/apps/dashboard/group4/dashboard_empty.ts
index 1d38b777cd691..574181b614d01 100644
--- a/test/functional/apps/dashboard/group4/dashboard_empty.ts
+++ b/test/functional/apps/dashboard/group4/dashboard_empty.ts
@@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
   const dataViews = getService('dataViews');
   const { common, dashboard, header } = getPageObjects(['common', 'dashboard', 'header']);
 
-  describe('dashboard empty state', () => {
+  // Failing: See https://github.com/elastic/kibana/issues/165745
+  describe.skip('dashboard empty state', () => {
     const kbnDirectory = 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana';
 
     before(async function () {
diff --git a/test/plugin_functional/test_suites/data_plugin/session.ts b/test/plugin_functional/test_suites/data_plugin/session.ts
index d69a7529a19ad..c44e606aa2d04 100644
--- a/test/plugin_functional/test_suites/data_plugin/session.ts
+++ b/test/plugin_functional/test_suites/data_plugin/session.ts
@@ -32,6 +32,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
     return sessionIds.split(',');
   };
 
+  // Failing: See https://github.com/elastic/kibana/issues/192510
   // Failing: See https://github.com/elastic/kibana/issues/192510
   describe.skip('Session management', function describeSessionManagementTests() {
     describe('Discover', () => {
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/containers_from_ecs_data.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/containers_from_ecs_data.ts
new file mode 100644
index 0000000000000..b9119143ec37e
--- /dev/null
+++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/containers_from_ecs_data.ts
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EntityDefinition, entityDefinitionSchema } from '@kbn/entities-schema';
+import { BUILT_IN_ID_PREFIX } from './constants';
+
+export const builtInContainersFromEcsEntityDefinition: EntityDefinition =
+  entityDefinitionSchema.parse({
+    id: `${BUILT_IN_ID_PREFIX}containers_from_ecs_data`,
+    managed: true,
+    version: '1.0.0',
+    name: 'Containers from ECS data',
+    description:
+      'This definition extracts container entities from common data streams by looking for the ECS field container.id',
+    type: 'container',
+    indexPatterns: ['filebeat-*', 'logs-*', 'metrics-*', 'metricbeat-*'],
+    identityFields: ['container.id'],
+    displayNameTemplate: '{{container.id}}',
+    history: {
+      timestampField: '@timestamp',
+      interval: '5m',
+      settings: {
+        frequency: '5m',
+      },
+    },
+    metadata: [
+      {
+        source: '_index',
+        destination: 'source_index',
+      },
+      {
+        source: 'data_stream.type',
+        destination: 'source_data_stream.type',
+      },
+      {
+        source: 'data_stream.dataset',
+        destination: 'source_data_stream.dataset',
+      },
+      'container.name',
+      'container.image.name',
+      'container.image.tag',
+      'container.runtime',
+      'host.name',
+      'host.ip',
+      'host.mac',
+      'host.architecture',
+      'host.os.family',
+      'host.os.kernel',
+      'host.os.name',
+      'host.os.platform',
+      'host.os.type',
+      'host.os.version',
+      'cloud.provider',
+      'cloud.region',
+      'cloud.availability_zone',
+      'cloud.instance.id',
+      'cloud.instance.name',
+      'cloud.machine.type',
+      'cloud.service.name',
+      'agent.name',
+      'agent.type',
+      'agent.ephemeral_id',
+    ],
+    metrics: [
+      {
+        name: 'log_rate',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'doc_count',
+            filter: 'log.level: * OR error.log.level: *',
+          },
+        ],
+      },
+      {
+        name: 'error_log_rate',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'doc_count',
+            filter: '(log.level: "error" OR "ERROR") OR (error.log.level: "error" OR "ERROR")',
+          },
+        ],
+      },
+      {
+        name: 'cpu_usage_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.cpu.total.pct',
+          },
+        ],
+      },
+      {
+        name: 'memory_usage_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.memory.usage.pct',
+          },
+        ],
+      },
+      {
+        name: 'network_in_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.network.in.bytes',
+          },
+        ],
+      },
+      {
+        name: 'network_out_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.network.out.bytes',
+          },
+        ],
+      },
+      {
+        name: 'disk_read_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.diskio.read.ops',
+          },
+        ],
+      },
+      {
+        name: 'disk_write_avg',
+        equation: 'A',
+        metrics: [
+          {
+            name: 'A',
+            aggregation: 'avg',
+            field: 'docker.diskio.write.ops',
+          },
+        ],
+      },
+    ],
+  });
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/hosts_from_ecs_data.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/hosts_from_ecs_data.ts
new file mode 100644
index 0000000000000..5fead32f5c0e8
--- /dev/null
+++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/hosts_from_ecs_data.ts
@@ -0,0 +1,179 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EntityDefinition, entityDefinitionSchema } from '@kbn/entities-schema';
+import { BUILT_IN_ID_PREFIX } from './constants';
+
+export const builtInHostsFromEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({
+  id: `${BUILT_IN_ID_PREFIX}hosts_from_ecs_data`,
+  managed: true,
+  version: '1.0.0',
+  name: 'Hosts from ECS data',
+  description:
+    'This definition extracts host entities from common data streams by looking for the ECS field host.name',
+  type: 'host',
+  indexPatterns: ['filebeat-*', 'logs-*', 'metrics-*', 'metricbeat-*'],
+  identityFields: ['host.name'],
+  displayNameTemplate: '{{host.name}}',
+  history: {
+    timestampField: '@timestamp',
+    interval: '5m',
+    settings: {
+      frequency: '5m',
+    },
+  },
+  metadata: [
+    {
+      source: '_index',
+      destination: 'source_index',
+    },
+    {
+      source: 'data_stream.type',
+      destination: 'source_data_stream.type',
+    },
+    {
+      source: 'data_stream.dataset',
+      destination: 'source_data_stream.dataset',
+    },
+    'host.hostname',
+    'host.ip',
+    'host.mac',
+    'host.architecture',
+    'host.containerized',
+    'host.os.platform',
+    'host.os.name',
+    'host.os.type',
+    'host.os.codename',
+    'host.os.family',
+    'host.os.kernel',
+    'host.os.version',
+    'cloud.provider',
+    'cloud.region',
+    'cloud.availability_zone',
+    'cloud.instance.id',
+    'cloud.instance.name',
+    'cloud.service.name',
+    'cloud.machine.type',
+    'cloud.account.id',
+    'cloud.project.id',
+    'agent.id',
+    'agent.name',
+    'agent.type',
+    'agent.version',
+  ],
+  metrics: [
+    {
+      name: 'log_rate',
+      equation: 'A',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'doc_count',
+          filter: 'log.level: * OR error.log.level: *',
+        },
+      ],
+    },
+    {
+      name: 'error_log_rate',
+      equation: 'A',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'doc_count',
+          filter: '(log.level: "error" OR "ERROR") OR (error.log.level: "error" OR "ERROR")',
+        },
+      ],
+    },
+    {
+      name: 'cpu_usage_avg',
+      equation: 'A',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'avg',
+          field: 'system.cpu.total.norm.pct',
+        },
+      ],
+    },
+    {
+      name: 'normalized_load_avg',
+      equation: 'A / B',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'avg',
+          field: 'system.load.1',
+        },
+        {
+          name: 'B',
+          aggregation: 'max',
+          field: 'system.load.cores',
+        },
+      ],
+    },
+    {
+      name: 'memory_usage_avg',
+      equation: 'A',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'avg',
+          field: 'system.memory.actual.used.pct',
+        },
+      ],
+    },
+    {
+      name: 'memory_free_avg',
+      equation: 'A - B',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'max',
+          field: 'system.memory.total',
+        },
+        {
+          name: 'B',
+          aggregation: 'avg',
+          field: 'system.memory.actual.used.bytes',
+        },
+      ],
+    },
+    {
+      name: 'disk_usage_max',
+      equation: 'A',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'max',
+          field: 'system.filesystem.used.pct',
+        },
+      ],
+    },
+    {
+      name: 'rx_avg',
+      equation: 'A * 8',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'sum',
+          field: 'host.network.ingress.bytes',
+        },
+      ],
+    },
+    {
+      name: 'tx_avg',
+      equation: 'A * 8',
+      metrics: [
+        {
+          name: 'A',
+          aggregation: 'sum',
+          field: 'host.network.egress.bytes',
+        },
+      ],
+    },
+  ],
+});
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts
index d091e21f446d2..6c0d4c5995c63 100644
--- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts
+++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/index.ts
@@ -6,8 +6,14 @@
  */
 
 import { EntityDefinition } from '@kbn/entities-schema';
-import { builtInServicesFromLogsEntityDefinition } from './services';
+import { builtInServicesFromEcsEntityDefinition } from './services_from_ecs_data';
+import { builtInHostsFromEcsEntityDefinition } from './hosts_from_ecs_data';
+import { builtInContainersFromEcsEntityDefinition } from './containers_from_ecs_data';
 
 export { BUILT_IN_ID_PREFIX } from './constants';
 
-export const builtInDefinitions: EntityDefinition[] = [builtInServicesFromLogsEntityDefinition];
+export const builtInDefinitions: EntityDefinition[] = [
+  builtInServicesFromEcsEntityDefinition,
+  builtInHostsFromEcsEntityDefinition,
+  builtInContainersFromEcsEntityDefinition,
+];
diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/services.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts
similarity index 98%
rename from x-pack/plugins/entity_manager/server/lib/entities/built_in/services.ts
rename to x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts
index aa1d86ee25adf..96667fb4d0af4 100644
--- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/services.ts
+++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts
@@ -18,7 +18,7 @@ const serviceTransactionFilter = (additionalFilters: string[] = []) => {
   return [...baseFilters, ...additionalFilters].join(' AND ');
 };
 
-export const builtInServicesFromLogsEntityDefinition: EntityDefinition =
+export const builtInServicesFromEcsEntityDefinition: EntityDefinition =
   entityDefinitionSchema.parse({
     version: '1.0.3',
     id: `${BUILT_IN_ID_PREFIX}services_from_ecs_data`,
diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts
index e639b364343d9..5fa4085c5106f 100644
--- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts
+++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts
@@ -71,7 +71,7 @@ export interface FullAgentPolicyInputStream {
   id: string;
   data_stream: {
     dataset: string;
-    type: string;
+    type?: string;
   };
   [key: string]: any;
 }
diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts
index 1e456f3e4d21d..354834d2571dc 100644
--- a/x-pack/plugins/fleet/common/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts
@@ -90,6 +90,7 @@ export interface NewPackagePolicy {
     privileges?: {
       cluster?: string[];
     };
+    [key: string]: any;
   };
   overrides?: { inputs?: { [key: string]: any } } | null;
 }
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx
index 1b0e791fbfd8c..0b9a6f6e85830 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx
@@ -35,7 +35,8 @@ export const InstallElasticAgentStandalonePageStep: React.FC<InstallAgentPagePro
   const [commandCopied, setCommandCopied] = useState(false);
   const [policyCopied, setPolicyCopied] = useState(false);
 
-  const { yaml, onCreateApiKey, apiKey, downloadYaml } = useFetchFullPolicy(agentPolicy);
+  const { yaml, onCreateApiKey, isCreatingApiKey, apiKey, downloadYaml } =
+    useFetchFullPolicy(agentPolicy);
 
   if (!agentPolicy) {
     return (
@@ -60,6 +61,7 @@ export const InstallElasticAgentStandalonePageStep: React.FC<InstallAgentPagePro
       downloadYaml,
       apiKey,
       onCreateApiKey,
+      isCreatingApiKey,
       isComplete: policyCopied,
       onCopy: () => setPolicyCopied(true),
     }),
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
index 50de1560b82b8..e6ecfbdfbd944 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
@@ -210,12 +210,15 @@ export function useGetCreateApiKey() {
   const core = useStartServices();
 
   const [apiKey, setApiKey] = useState<string | undefined>(undefined);
+  const [isLoading, setIsLoading] = useState(false);
   const onCreateApiKey = useCallback(async () => {
     try {
+      setIsLoading(true);
       const res = await sendCreateStandaloneAgentAPIKey({
         name: crypto.randomBytes(16).toString('hex'),
       });
-      const newApiKey = `${res.data?.item.id}:${res.data?.item.api_key}`;
+
+      const newApiKey = `${res.item.id}:${res.item.api_key}`;
       setApiKey(newApiKey);
     } catch (err) {
       core.notifications.toasts.addError(err, {
@@ -224,9 +227,11 @@ export function useGetCreateApiKey() {
         }),
       });
     }
+    setIsLoading(false);
   }, [core.notifications.toasts]);
   return {
     apiKey,
+    isLoading,
     onCreateApiKey,
   };
 }
@@ -235,7 +240,7 @@ export function useFetchFullPolicy(agentPolicy: AgentPolicy | undefined, isK8s?:
   const core = useStartServices();
   const [yaml, setYaml] = useState<any | undefined>('');
   const [fullAgentPolicy, setFullAgentPolicy] = useState<FullAgentPolicy | undefined>();
-  const { apiKey, onCreateApiKey } = useGetCreateApiKey();
+  const { apiKey, isLoading: isCreatingApiKey, onCreateApiKey } = useGetCreateApiKey();
 
   useEffect(() => {
     async function fetchFullPolicy() {
@@ -302,6 +307,7 @@ export function useFetchFullPolicy(agentPolicy: AgentPolicy | undefined, isK8s?:
     yaml,
     onCreateApiKey,
     fullAgentPolicy,
+    isCreatingApiKey,
     apiKey,
     downloadYaml,
   };
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
index bc4e7755044e7..ea31b163fb368 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
@@ -54,7 +54,10 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
   isK8s,
   cloudSecurityIntegration,
 }) => {
-  const { yaml, onCreateApiKey, apiKey, downloadYaml } = useFetchFullPolicy(selectedPolicy, isK8s);
+  const { yaml, onCreateApiKey, isCreatingApiKey, apiKey, downloadYaml } = useFetchFullPolicy(
+    selectedPolicy,
+    isK8s
+  );
 
   const agentVersion = useAgentVersion();
 
@@ -88,6 +91,7 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
         downloadYaml,
         apiKey,
         onCreateApiKey,
+        isCreatingApiKey,
       })
     );
 
@@ -116,6 +120,7 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
     downloadYaml,
     apiKey,
     onCreateApiKey,
+    isCreatingApiKey,
     cloudSecurityIntegration,
     mode,
     setMode,
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx
index 36d6eddd8eecc..9fc74b3562ab0 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/configure_standalone_agent_step.tsx
@@ -34,6 +34,7 @@ export const ConfigureStandaloneAgentStep = ({
   downloadYaml,
   apiKey,
   onCreateApiKey,
+  isCreatingApiKey,
   isComplete,
   onCopy,
 }: {
@@ -43,6 +44,7 @@ export const ConfigureStandaloneAgentStep = ({
   downloadYaml: () => void;
   apiKey: string | undefined;
   onCreateApiKey: () => void;
+  isCreatingApiKey: boolean;
   isComplete?: boolean;
   onCopy?: () => void;
 }): EuiContainedStepProps => {
@@ -167,7 +169,7 @@ export const ConfigureStandaloneAgentStep = ({
             <EuiSpacer size="s" />
             <EuiFlexGroup gutterSize="m">
               <EuiFlexItem grow={false}>
-                <EuiButton onClick={onCreateApiKey}>
+                <EuiButton onClick={onCreateApiKey} isLoading={isCreatingApiKey}>
                   <FormattedMessage
                     id="xpack.fleet.agentEnrollment.createApiKeyButton"
                     defaultMessage="Create API key"
diff --git a/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts b/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts
index 3df53fd4f35f1..34a0fbe5e5ad0 100644
--- a/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts
+++ b/x-pack/plugins/fleet/public/hooks/use_request/standalone_agent_api_key.ts
@@ -12,10 +12,10 @@ import type {
 
 import { API_VERSIONS, CREATE_STANDALONE_AGENT_API_KEY_ROUTE } from '../../../common/constants';
 
-import { sendRequest } from './use_request';
+import { sendRequestForRq } from './use_request';
 
 export function sendCreateStandaloneAgentAPIKey(body: PostStandaloneAgentAPIKeyRequest['body']) {
-  return sendRequest<PostStandaloneAgentAPIKeyResponse>({
+  return sendRequestForRq<PostStandaloneAgentAPIKeyResponse>({
     method: 'post',
     path: CREATE_STANDALONE_AGENT_API_KEY_ROUTE,
     version: API_VERSIONS.internal.v1,
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
index fbcf1ee80f206..05876c2bbdf26 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
@@ -9,9 +9,20 @@ import { httpServerMock, httpServiceMock } from '@kbn/core/server/mocks';
 import type { KibanaRequest } from '@kbn/core/server';
 import type { RouteConfig } from '@kbn/core/server';
 
+import type {
+  ListResult,
+  PostDeletePackagePoliciesResponse,
+  UpgradePackagePolicyResponse,
+} from '../../../common';
+
 import type { FleetAuthzRouter } from '../../services/security';
 
 import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants';
+import type {
+  DryRunPackagePolicy,
+  UpgradePackagePolicyDryRunResponse,
+  UpgradePackagePolicyDryRunResponseItem,
+} from '../../../common/types';
 import {
   agentPolicyService,
   appContextService,
@@ -21,10 +32,33 @@ import {
 import { createAppContextStartContractMock, xpackMocks } from '../../mocks';
 import type { PackagePolicyClient, FleetRequestHandlerContext } from '../..';
 import type { UpdatePackagePolicyRequestSchema } from '../../types/rest_spec';
-import type { AgentPolicy, FleetRequestHandler } from '../../types';
+import {
+  PackagePolicyResponseSchema,
+  type AgentPolicy,
+  type FleetRequestHandler,
+  BulkGetPackagePoliciesResponseBodySchema,
+  DeletePackagePoliciesResponseBodySchema,
+  DeleteOnePackagePolicyResponseSchema,
+  UpgradePackagePoliciesResponseBodySchema,
+  DryRunPackagePoliciesResponseBodySchema,
+  OrphanedPackagePoliciesResponseSchema,
+  CreatePackagePolicyResponseSchema,
+} from '../../types';
 import type { PackagePolicy } from '../../types';
 
-import { getPackagePoliciesHandler } from './handlers';
+import { ListResponseSchema } from '../schema/utils';
+
+import {
+  bulkGetPackagePoliciesHandler,
+  createPackagePolicyHandler,
+  deleteOnePackagePolicyHandler,
+  deletePackagePolicyHandler,
+  dryRunUpgradePackagePolicyHandler,
+  getOnePackagePolicyHandler,
+  getOrphanedPackagePolicies,
+  getPackagePoliciesHandler,
+  upgradePackagePolicyHandler,
+} from './handlers';
 import { registerRoutes } from '.';
 
 const packagePolicyServiceMock = packagePolicyService as jest.Mocked<PackagePolicyClient>;
@@ -79,35 +113,7 @@ jest.mock(
         delete: jest.fn(),
         get: jest.fn(),
         getByIDs: jest.fn(),
-        list: jest.fn(async (_, __) => {
-          return {
-            total: 1,
-            perPage: 10,
-            page: 1,
-            items: [
-              {
-                id: `123`,
-                name: `Package Policy 123`,
-                description: '',
-                created_at: '2022-12-19T20:43:45.879Z',
-                created_by: 'elastic',
-                updated_at: '2022-12-19T20:43:45.879Z',
-                updated_by: 'elastic',
-                policy_id: `agent-policy-id-a`,
-                policy_ids: [`agent-policy-id-a`],
-                enabled: true,
-                inputs: [],
-                namespace: 'default',
-                package: {
-                  name: 'a-package',
-                  title: 'package A',
-                  version: '1.0.0',
-                },
-                revision: 1,
-              },
-            ],
-          };
-        }),
+        list: jest.fn(),
         listIds: jest.fn(),
         update: jest.fn(),
         // @ts-ignore
@@ -131,6 +137,7 @@ jest.mock('../../services/agent_policy', () => {
     agentPolicyService: {
       get: jest.fn(),
       update: jest.fn(),
+      list: jest.fn(),
     },
   };
 });
@@ -140,9 +147,18 @@ jest.mock('../../services/epm/packages', () => {
     ensureInstalledPackage: jest.fn(() => Promise.resolve()),
     getPackageInfo: jest.fn(() => Promise.resolve()),
     getInstallation: jest.fn(),
+    getInstallations: jest.fn().mockResolvedValue({
+      saved_objects: [
+        {
+          attributes: { name: 'a-package', version: '1.0.0' },
+        },
+      ],
+    }),
   };
 });
 
+let testPackagePolicy: PackagePolicy;
+
 describe('When calling package policy', () => {
   let routerMock: jest.Mocked<FleetAuthzRouter>;
   let routeHandler: FleetRequestHandler<any, any, any>;
@@ -160,6 +176,65 @@ describe('When calling package policy', () => {
     context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
     (await context.fleet).packagePolicyService.asCurrentUser as jest.Mocked<PackagePolicyClient>;
     response = httpServerMock.createResponseFactory();
+    testPackagePolicy = {
+      agents: 100,
+      created_at: '2022-12-19T20:43:45.879Z',
+      created_by: 'elastic',
+      description: '',
+      enabled: true,
+      id: '123',
+      inputs: [
+        {
+          streams: [
+            {
+              id: '1',
+              compiled_stream: {},
+              enabled: true,
+              keep_enabled: false,
+              release: 'beta',
+              vars: { var: { type: 'text', value: 'value', frozen: false } },
+              config: { config: { type: 'text', value: 'value', frozen: false } },
+              data_stream: { dataset: 'apache.access', type: 'logs', elasticsearch: {} },
+            },
+          ],
+          compiled_input: '',
+          id: '1',
+          enabled: true,
+          type: 'logs',
+          policy_template: '',
+          keep_enabled: false,
+          vars: { var: { type: 'text', value: 'value', frozen: false } },
+          config: { config: { type: 'text', value: 'value', frozen: false } },
+        },
+      ],
+      vars: { var: { type: 'text', value: 'value', frozen: false } },
+      name: 'Package Policy 123',
+      namespace: 'default',
+      package: {
+        name: 'a-package',
+        title: 'package A',
+        version: '1.0.0',
+        experimental_data_stream_features: [{ data_stream: 'logs', features: { tsdb: true } }],
+        requires_root: false,
+      },
+      policy_id: 'agent-policy-id-a',
+      policy_ids: ['agent-policy-id-a'],
+      revision: 1,
+      updated_at: '2022-12-19T20:43:45.879Z',
+      updated_by: 'elastic',
+      version: '1.0.0',
+      secret_references: [
+        {
+          id: 'ref1',
+        },
+      ],
+      spaceIds: ['space1'],
+      elasticsearch: {
+        'index_template.mappings': {
+          dynamic_templates: [],
+        },
+      },
+    };
   });
 
   afterEach(() => {
@@ -187,7 +262,14 @@ describe('When calling package policy', () => {
       });
     };
 
-    const existingPolicy = {
+    const existingPolicy: PackagePolicy = {
+      id: '1',
+      revision: 1,
+      created_at: '',
+      created_by: '',
+      updated_at: '',
+      updated_by: '',
+      policy_ids: ['2'],
       name: 'endpoint-1',
       description: 'desc',
       policy_id: '2',
@@ -231,17 +313,10 @@ describe('When calling package policy', () => {
     beforeEach(() => {
       jest.spyOn(licenseService, 'hasAtLeast').mockClear();
       packagePolicyServiceMock.update.mockImplementation((soClient, esClient, policyId, newData) =>
-        Promise.resolve(newData as PackagePolicy)
+        Promise.resolve({ ...existingPolicy, ...newData } as PackagePolicy)
       );
       packagePolicyServiceMock.get.mockResolvedValue({
-        id: '1',
-        revision: 1,
-        created_at: '',
-        created_by: '',
-        updated_at: '',
-        updated_by: '',
         ...existingPolicy,
-        policy_ids: [existingPolicy.policy_id],
         inputs: [
           {
             ...existingPolicy.inputs[0],
@@ -264,6 +339,8 @@ describe('When calling package policy', () => {
       expect(response.ok).toHaveBeenCalledWith({
         body: { item: existingPolicy },
       });
+      const validationResp = PackagePolicyResponseSchema.validate(existingPolicy);
+      expect(validationResp).toEqual(existingPolicy);
     });
 
     it('should use request package policy props if provided by request', async () => {
@@ -300,9 +377,13 @@ describe('When calling package policy', () => {
       };
       const request = getUpdateKibanaRequest(newData as any);
       await routeHandler(context, request, response);
+      const responseItem = { ...existingPolicy, ...newData };
       expect(response.ok).toHaveBeenCalledWith({
-        body: { item: newData },
+        body: { item: responseItem },
       });
+
+      const validationResp = PackagePolicyResponseSchema.validate(responseItem);
+      expect(validationResp).toEqual(responseItem);
     });
 
     it('should override props provided by request only', async () => {
@@ -435,43 +516,43 @@ describe('When calling package policy', () => {
         inputs,
       } as any);
       await routeHandler(context, request, response);
-      expect(response.ok).toHaveBeenCalledWith({
-        body: {
-          item: {
-            description: 'desc',
-            enabled: true,
-            inputs: [
+      const responseItem = {
+        ...existingPolicy,
+        inputs: [
+          {
+            type: 'input-logs',
+            enabled: false,
+            streams: [
               {
-                type: 'input-logs',
                 enabled: false,
-                streams: [
-                  {
-                    enabled: false,
-                    data_stream: {
-                      type: 'logs',
-                      dataset: 'test.some_logs',
-                    },
-                  },
-                ],
+                data_stream: {
+                  type: 'logs',
+                  dataset: 'test.some_logs',
+                },
               },
             ],
-            name: 'endpoint-1',
-            namespace: 'default',
-            package: {
-              name: 'endpoint',
-              title: 'Elastic Endpoint',
-              version: '0.5.0',
-            },
-            vars: expect.any(Object),
-            policy_id: '2',
           },
+        ],
+      };
+      expect(response.ok).toHaveBeenCalledWith({
+        body: {
+          item: responseItem,
         },
       });
+
+      const validationResp = PackagePolicyResponseSchema.validate(responseItem);
+      expect(validationResp).toEqual(responseItem);
     });
   });
 
   describe('list api handler', () => {
     it('should return agent count when `withAgentCount` query param is used', async () => {
+      packagePolicyServiceMock.list.mockResolvedValue({
+        total: 1,
+        perPage: 10,
+        page: 1,
+        items: [testPackagePolicy],
+      });
       const request = httpServerMock.createKibanaRequest({
         query: {
           withAgentCount: true,
@@ -510,37 +591,334 @@ describe('When calling package policy', () => {
       });
 
       await getPackagePoliciesHandler(context, request, response);
+      const responseBody: ListResult<PackagePolicy> = {
+        page: 1,
+        perPage: 10,
+        total: 1,
+        items: [testPackagePolicy],
+      };
+      expect(response.ok).toHaveBeenCalledWith({
+        body: responseBody,
+      });
+
+      const validationResp = ListResponseSchema(PackagePolicyResponseSchema).validate(responseBody);
+      expect(validationResp).toEqual(responseBody);
+    });
+  });
+
+  describe('bulk api handler', () => {
+    it('should return valid response', async () => {
+      const items: PackagePolicy[] = [testPackagePolicy];
+      packagePolicyServiceMock.getByIDs.mockResolvedValue(items);
+      const request = httpServerMock.createKibanaRequest({
+        query: {},
+        body: { ids: ['1'] },
+      });
+      await bulkGetPackagePoliciesHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: { items },
+      });
+      const validationResp = BulkGetPackagePoliciesResponseBodySchema.validate({ items });
+      expect(validationResp).toEqual({ items });
+    });
+  });
+
+  describe('orphaned package policies api handler', () => {
+    it('should return valid response', async () => {
+      const items: PackagePolicy[] = [testPackagePolicy];
+      const expectedResponse = {
+        items,
+        total: 1,
+      };
+      packagePolicyServiceMock.list.mockResolvedValue({
+        items: [testPackagePolicy],
+        total: 1,
+        page: 1,
+        perPage: 20,
+      });
+      mockedAgentPolicyService.list.mockResolvedValue({
+        items: [],
+        total: 0,
+        page: 1,
+        perPage: 20,
+      });
+      await getOrphanedPackagePolicies(context, {} as any, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: expectedResponse,
+      });
+      const validationResp = OrphanedPackagePoliciesResponseSchema.validate(expectedResponse);
+      expect(validationResp).toEqual(expectedResponse);
+    });
+  });
+
+  describe('get api handler', () => {
+    it('should return valid response', async () => {
+      packagePolicyServiceMock.get.mockResolvedValue(testPackagePolicy);
+      const request = httpServerMock.createKibanaRequest({
+        params: {
+          packagePolicyId: '1',
+        },
+      });
+      await getOnePackagePolicyHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: { item: testPackagePolicy },
+      });
+      const validationResp = PackagePolicyResponseSchema.validate(testPackagePolicy);
+      expect(validationResp).toEqual(testPackagePolicy);
+    });
+
+    it('should return valid response simplified format', async () => {
+      packagePolicyServiceMock.get.mockResolvedValue(testPackagePolicy);
+      const request = httpServerMock.createKibanaRequest({
+        params: {
+          packagePolicyId: '1',
+        },
+        query: {
+          format: 'simplified',
+        },
+      });
+      await getOnePackagePolicyHandler(context, request, response);
+      const simplifiedPackagePolicy = {
+        ...testPackagePolicy,
+        inputs: {
+          logs: {
+            enabled: true,
+            streams: {
+              'apache.access': {
+                enabled: true,
+                vars: {
+                  var: 'value',
+                },
+              },
+            },
+            vars: {
+              var: 'value',
+            },
+          },
+        },
+        vars: {
+          var: 'value',
+        },
+      };
+      expect(response.ok).toHaveBeenCalledWith({
+        body: { item: simplifiedPackagePolicy },
+      });
+      const validationResp = PackagePolicyResponseSchema.validate(simplifiedPackagePolicy);
+      expect(validationResp).toEqual(simplifiedPackagePolicy);
+    });
+  });
 
+  describe('create api handler', () => {
+    it('should return valid response', async () => {
+      packagePolicyServiceMock.get.mockResolvedValue(testPackagePolicy);
+      (
+        (await context.fleet).packagePolicyService.asCurrentUser as jest.Mocked<PackagePolicyClient>
+      ).create.mockResolvedValue(testPackagePolicy);
+      const request = httpServerMock.createKibanaRequest({
+        body: testPackagePolicy,
+      });
+      const expectedResponse = { item: testPackagePolicy };
+      await createPackagePolicyHandler(context, request, response);
       expect(response.ok).toHaveBeenCalledWith({
+        body: expectedResponse,
+      });
+      const validationResp = CreatePackagePolicyResponseSchema.validate(expectedResponse);
+      expect(validationResp).toEqual(expectedResponse);
+    });
+  });
+
+  describe('bulk delete api handler', () => {
+    it('should return valid response', async () => {
+      const responseBody: PostDeletePackagePoliciesResponse = [
+        {
+          id: '1',
+          name: 'policy',
+          success: true,
+          policy_ids: ['1'],
+          output_id: '1',
+          package: {
+            name: 'package',
+            version: '1.0.0',
+            title: 'Package',
+          },
+          statusCode: 409,
+          body: {
+            message: 'conflict',
+          },
+        },
+      ];
+      packagePolicyServiceMock.delete.mockResolvedValue(responseBody);
+      const request = httpServerMock.createKibanaRequest({
         body: {
-          page: 1,
-          perPage: 10,
-          total: 1,
-          items: [
-            {
-              agents: 100,
-              created_at: '2022-12-19T20:43:45.879Z',
-              created_by: 'elastic',
-              description: '',
-              enabled: true,
-              id: '123',
-              inputs: [],
-              name: 'Package Policy 123',
-              namespace: 'default',
-              package: {
-                name: 'a-package',
-                title: 'package A',
-                version: '1.0.0',
+          packagePolicyIds: ['1'],
+        },
+      });
+      await deletePackagePolicyHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: responseBody,
+      });
+      const validationResp = DeletePackagePoliciesResponseBodySchema.validate(responseBody);
+      expect(validationResp).toEqual(responseBody);
+    });
+  });
+
+  describe('delete api handler', () => {
+    it('should return valid response', async () => {
+      const responseBody = {
+        id: '1',
+      };
+      packagePolicyServiceMock.delete.mockResolvedValue([
+        {
+          id: '1',
+          name: 'policy',
+          success: true,
+          policy_ids: ['1'],
+          output_id: '1',
+          package: {
+            name: 'package',
+            version: '1.0.0',
+            title: 'Package',
+          },
+          statusCode: 409,
+          body: {
+            message: 'conflict',
+          },
+        },
+      ]);
+      const request = httpServerMock.createKibanaRequest({
+        body: {
+          force: false,
+        },
+        params: {
+          packagePolicyId: '1',
+        },
+      });
+      await deleteOnePackagePolicyHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: responseBody,
+      });
+      const validationResp = DeleteOnePackagePolicyResponseSchema.validate(responseBody);
+      expect(validationResp).toEqual(responseBody);
+    });
+  });
+
+  describe('upgrade api handler', () => {
+    it('should return valid response', async () => {
+      const responseBody: UpgradePackagePolicyResponse = [
+        {
+          id: '1',
+          name: 'policy',
+          success: true,
+          statusCode: 200,
+          body: {
+            message: 'success',
+          },
+        },
+      ];
+      packagePolicyServiceMock.upgrade.mockResolvedValue(responseBody);
+      const request = httpServerMock.createKibanaRequest({
+        body: {
+          packagePolicyIds: ['1'],
+        },
+      });
+      await upgradePackagePolicyHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: responseBody,
+      });
+      const validationResp = UpgradePackagePoliciesResponseBodySchema.validate(responseBody);
+      expect(validationResp).toEqual(responseBody);
+    });
+  });
+
+  describe('dry run upgrade api handler', () => {
+    it('should return valid response', async () => {
+      const dryRunPackagePolicy: DryRunPackagePolicy = {
+        description: '',
+        enabled: true,
+        id: '123',
+        inputs: [
+          {
+            streams: [
+              {
+                id: '1',
+                enabled: true,
+                keep_enabled: false,
+                release: 'beta',
+                vars: { var: { type: 'text', value: 'value', frozen: false } },
+                config: { config: { type: 'text', value: 'value', frozen: false } },
+                data_stream: { dataset: 'apache.access', type: 'logs', elasticsearch: {} },
               },
-              policy_id: 'agent-policy-id-a',
-              policy_ids: ['agent-policy-id-a'],
+            ],
+            id: '1',
+            enabled: true,
+            type: 'logs',
+            policy_template: '',
+            keep_enabled: false,
+            vars: { var: { type: 'text', value: 'value', frozen: false } },
+            config: { config: { type: 'text', value: 'value', frozen: false } },
+          },
+        ],
+        vars: { var: { type: 'text', value: 'value', frozen: false } },
+        name: 'Package Policy 123',
+        namespace: 'default',
+        package: {
+          name: 'a-package',
+          title: 'package A',
+          version: '1.0.0',
+          experimental_data_stream_features: [{ data_stream: 'logs', features: { tsdb: true } }],
+          requires_root: false,
+        },
+        policy_id: 'agent-policy-id-a',
+        policy_ids: ['agent-policy-id-a'],
+        errors: [{ key: 'error', message: 'error' }],
+        missingVars: ['var'],
+      };
+      const responseItem: UpgradePackagePolicyDryRunResponseItem = {
+        hasErrors: false,
+        name: 'policy',
+        statusCode: 200,
+        body: {
+          message: 'success',
+        },
+        diff: [testPackagePolicy, dryRunPackagePolicy],
+        agent_diff: [
+          [
+            {
+              id: '1',
+              name: 'input',
               revision: 1,
-              updated_at: '2022-12-19T20:43:45.879Z',
-              updated_by: 'elastic',
+              type: 'logs',
+              data_stream: { namespace: 'default' },
+              use_output: 'default',
+              package_policy_id: '1',
+              streams: [
+                {
+                  id: 'logfile-log.logs-d46700b2-47f8-4b1a-9153-14a717dc5edf',
+                  data_stream: {
+                    dataset: 'generic',
+                  },
+                  paths: ['/var/tmp'],
+                  ignore_older: '72h',
+                },
+              ],
             },
           ],
+        ],
+      };
+      const responseBody: UpgradePackagePolicyDryRunResponse = [responseItem, responseItem];
+      packagePolicyServiceMock.getUpgradeDryRunDiff.mockResolvedValueOnce(responseBody[0]);
+      packagePolicyServiceMock.getUpgradeDryRunDiff.mockResolvedValueOnce(responseBody[1]);
+      const request = httpServerMock.createKibanaRequest({
+        body: {
+          packagePolicyIds: ['1', '2'],
         },
       });
+      await dryRunUpgradePackagePolicyHandler(context, request, response);
+      expect(response.ok).toHaveBeenCalledWith({
+        body: responseBody,
+      });
+      const validationResp = DryRunPackagePoliciesResponseBodySchema.validate(responseBody);
+      expect(validationResp).toEqual(responseBody);
     });
   });
 });
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/index.ts b/x-pack/plugins/fleet/server/routes/package_policy/index.ts
index 893eb37a9b1bc..86ac38e658ee3 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/index.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/index.ts
@@ -4,6 +4,7 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+import { schema } from '@kbn/config-schema';
 
 import { getRouteRequiredAuthz } from '../../services/security';
 
@@ -22,9 +23,21 @@ import {
   DryRunPackagePoliciesRequestSchema,
   DeleteOnePackagePolicyRequestSchema,
   BulkGetPackagePoliciesRequestSchema,
+  PackagePolicyResponseSchema,
+  BulkGetPackagePoliciesResponseBodySchema,
+  DeletePackagePoliciesResponseBodySchema,
+  DeleteOnePackagePolicyResponseSchema,
+  UpgradePackagePoliciesResponseBodySchema,
+  DryRunPackagePoliciesResponseBodySchema,
+  OrphanedPackagePoliciesResponseSchema,
+  CreatePackagePolicyResponseSchema,
 } from '../../types';
 import { calculateRouteAuthz } from '../../services/security/security';
 
+import { genericErrorResponse, notFoundResponse } from '../schema/errors';
+
+import { ListResponseSchema } from '../schema/utils';
+
 import {
   getPackagePoliciesHandler,
   getOnePackagePolicyHandler,
@@ -48,11 +61,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
           fleetAuthz,
           getRouteRequiredAuthz('get', PACKAGE_POLICY_API_ROUTES.LIST_PATTERN)
         ).granted,
+      description: 'List package policies',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: GetPackagePoliciesRequestSchema },
+        validate: {
+          request: GetPackagePoliciesRequestSchema,
+          response: {
+            200: {
+              body: () => ListResponseSchema(PackagePolicyResponseSchema),
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       getPackagePoliciesHandler
     );
@@ -66,11 +93,28 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
           fleetAuthz,
           getRouteRequiredAuthz('post', PACKAGE_POLICY_API_ROUTES.BULK_GET_PATTERN)
         ).granted,
+      description: 'Bulk get package policies',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: BulkGetPackagePoliciesRequestSchema },
+        validate: {
+          request: BulkGetPackagePoliciesRequestSchema,
+          response: {
+            200: {
+              body: () => BulkGetPackagePoliciesResponseBodySchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            404: {
+              body: notFoundResponse,
+            },
+          },
+        },
       },
       bulkGetPackagePoliciesHandler
     );
@@ -84,11 +128,31 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
           fleetAuthz,
           getRouteRequiredAuthz('get', PACKAGE_POLICY_API_ROUTES.INFO_PATTERN)
         ).granted,
+      description: 'Get package policy by ID',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: GetOnePackagePolicyRequestSchema },
+        validate: {
+          request: GetOnePackagePolicyRequestSchema,
+          response: {
+            200: {
+              body: () =>
+                schema.object({
+                  item: PackagePolicyResponseSchema,
+                }),
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            404: {
+              body: notFoundResponse,
+            },
+          },
+        },
       },
       getOnePackagePolicyHandler
     );
@@ -103,20 +167,48 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: {},
+        validate: {
+          request: {},
+          response: {
+            200: {
+              body: () => OrphanedPackagePoliciesResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       getOrphanedPackagePolicies
     );
 
   // Create
+  // Authz check moved to service here: https://github.com/elastic/kibana/pull/140458
   router.versioned
     .post({
       path: PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN,
+      description: 'Create package policy',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: CreatePackagePolicyRequestSchema },
+        validate: {
+          request: CreatePackagePolicyRequestSchema,
+          response: {
+            200: {
+              body: () => CreatePackagePolicyResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            409: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       createPackagePolicyHandler
     );
@@ -130,11 +222,31 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
           fleetAuthz,
           getRouteRequiredAuthz('put', PACKAGE_POLICY_API_ROUTES.UPDATE_PATTERN)
         ).granted,
+      description: 'Update package policy by ID',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: UpdatePackagePolicyRequestSchema },
+        validate: {
+          request: UpdatePackagePolicyRequestSchema,
+          response: {
+            200: {
+              body: () =>
+                schema.object({
+                  item: PackagePolicyResponseSchema,
+                }),
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            403: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
 
       updatePackagePolicyHandler
@@ -147,11 +259,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         integrations: { writeIntegrationPolicies: true },
       },
+      description: 'Bulk delete package policies',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: DeletePackagePoliciesRequestSchema },
+        validate: {
+          request: DeletePackagePoliciesRequestSchema,
+          response: {
+            200: {
+              body: () => DeletePackagePoliciesResponseBodySchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       deletePackagePolicyHandler
     );
@@ -162,11 +288,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         integrations: { writeIntegrationPolicies: true },
       },
+      description: 'Delete package policy by ID',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: DeleteOnePackagePolicyRequestSchema },
+        validate: {
+          request: DeleteOnePackagePolicyRequestSchema,
+          response: {
+            200: {
+              body: () => DeleteOnePackagePolicyResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       deleteOnePackagePolicyHandler
     );
@@ -178,11 +318,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         integrations: { writeIntegrationPolicies: true },
       },
+      description: 'Upgrade package policy to a newer package version',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: UpgradePackagePoliciesRequestSchema },
+        validate: {
+          request: UpgradePackagePoliciesRequestSchema,
+          response: {
+            200: {
+              body: () => UpgradePackagePoliciesResponseBodySchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       upgradePackagePolicyHandler
     );
@@ -194,11 +348,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         integrations: { readIntegrationPolicies: true },
       },
+      description: 'Dry run package policy upgrade',
+      options: {
+        tags: ['oas-tag:Fleet package policies'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: DryRunPackagePoliciesRequestSchema },
+        validate: {
+          request: DryRunPackagePoliciesRequestSchema,
+          response: {
+            200: {
+              body: () => DryRunPackagePoliciesResponseBodySchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       dryRunUpgradePackagePolicyHandler
     );
diff --git a/x-pack/plugins/fleet/server/routes/schema/errors.ts b/x-pack/plugins/fleet/server/routes/schema/errors.ts
new file mode 100644
index 0000000000000..1d8f0f5d5b92d
--- /dev/null
+++ b/x-pack/plugins/fleet/server/routes/schema/errors.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { schema } from '@kbn/config-schema';
+
+export const genericErrorResponse = () =>
+  schema.object(
+    {
+      statusCode: schema.number(),
+      error: schema.string(),
+      message: schema.string(),
+    },
+    {
+      meta: { description: 'Generic Error' },
+    }
+  );
+
+export const notFoundResponse = () =>
+  schema.object({
+    message: schema.string(),
+  });
+
+export const internalErrorResponse = () =>
+  schema.object(
+    {
+      message: schema.string(),
+    },
+    { meta: { description: 'Internal Server Error' } }
+  );
diff --git a/x-pack/plugins/fleet/server/routes/schema/utils.ts b/x-pack/plugins/fleet/server/routes/schema/utils.ts
new file mode 100644
index 0000000000000..2634b58cdcb34
--- /dev/null
+++ b/x-pack/plugins/fleet/server/routes/schema/utils.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { Type } from '@kbn/config-schema';
+import { schema } from '@kbn/config-schema';
+export const ListResponseSchema = (itemSchema: Type<any>) =>
+  schema.object({
+    items: schema.arrayOf(itemSchema),
+    total: schema.number(),
+    page: schema.number(),
+    perPage: schema.number(),
+  });
diff --git a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts
index 0a39101db8481..029efc146e09b 100644
--- a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts
+++ b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts
@@ -4,12 +4,20 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
-import { savedObjectsClientMock } from '@kbn/core/server/mocks';
+import { httpServerMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
 
 import { agentPolicyService } from '../../services';
 import { getFleetServerPolicies } from '../../services/fleet_server';
 
-import { getFleetServerOrAgentPolicies, getDownloadSource } from './enrollment_settings_handler';
+import type { FleetRequestHandlerContext } from '../../types';
+import { GetEnrollmentSettingsResponseSchema } from '../../types';
+import { xpackMocks } from '../../mocks';
+
+import {
+  getFleetServerOrAgentPolicies,
+  getDownloadSource,
+  getEnrollmentSettingsHandler,
+} from './enrollment_settings_handler';
 
 jest.mock('../../services', () => ({
   agentPolicyService: {
@@ -42,6 +50,27 @@ jest.mock('../../services', () => ({
 
 jest.mock('../../services/fleet_server', () => ({
   getFleetServerPolicies: jest.fn(),
+  hasFleetServersForPolicies: jest.fn().mockResolvedValue(true),
+}));
+
+jest.mock('../../services/fleet_server_host', () => ({
+  getFleetServerHostsForAgentPolicy: jest.fn().mockResolvedValue({
+    id: 'host-1',
+    is_default: true,
+    is_preconfigured: true,
+    name: 'Host 1',
+    host_urls: ['http://localhost:8220'],
+    proxy_id: 'proxy-1',
+  }),
+}));
+
+jest.mock('../../services/fleet_proxies', () => ({
+  getFleetProxy: jest.fn().mockResolvedValue({
+    id: 'proxy-1',
+    name: 'Proxy 1',
+    url: 'https://proxy-1/',
+    is_preconfigured: true,
+  }),
 }));
 
 describe('EnrollmentSettingsHandler utils', () => {
@@ -206,5 +235,74 @@ describe('EnrollmentSettingsHandler utils', () => {
         proxy_id: 'proxy-1',
       });
     });
+
+    describe('schema validation', () => {
+      let context: FleetRequestHandlerContext;
+      let response: ReturnType<typeof httpServerMock.createResponseFactory>;
+
+      beforeEach(() => {
+        context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
+        response = httpServerMock.createResponseFactory();
+      });
+
+      it('should return valid enrollment settings', async () => {
+        const fleetServerPolicies = [
+          {
+            id: 'fs-policy-1',
+            name: 'FS Policy 1',
+            is_managed: true,
+            is_default_fleet_server: true,
+            has_fleet_server: true,
+            download_source_id: 'source-2',
+            fleet_server_host_id: undefined,
+          },
+        ];
+        (getFleetServerPolicies as jest.Mock).mockResolvedValueOnce(fleetServerPolicies);
+        const expectedResponse = {
+          fleet_server: {
+            has_active: true,
+            host_proxy: {
+              id: 'proxy-1',
+              name: 'Proxy 1',
+              is_preconfigured: true,
+              url: 'https://proxy-1/',
+            },
+
+            host: {
+              host_urls: ['http://localhost:8220'],
+              id: 'host-1',
+              is_default: true,
+              is_preconfigured: true,
+              name: 'Host 1',
+              proxy_id: 'proxy-1',
+            },
+            policies: [
+              {
+                download_source_id: 'source-2',
+                fleet_server_host_id: undefined,
+                has_fleet_server: true,
+                id: 'fs-policy-1',
+                is_default_fleet_server: true,
+                is_managed: true,
+                name: 'FS Policy 1',
+                space_ids: undefined,
+              },
+            ],
+          },
+          download_source: {
+            host: 'https://source-1/',
+            id: 'source-1',
+            is_default: true,
+            name: 'Source 1',
+          },
+        };
+        await getEnrollmentSettingsHandler(context, {} as any, response);
+        expect(response.ok).toHaveBeenCalledWith({
+          body: expectedResponse,
+        });
+        const validationResp = GetEnrollmentSettingsResponseSchema.validate(expectedResponse);
+        expect(validationResp).toEqual(expectedResponse);
+      });
+    });
   });
 });
diff --git a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts
index 17cb27296d9e4..73a7d03a14592 100644
--- a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts
+++ b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts
@@ -47,7 +47,6 @@ export const getEnrollmentSettingsHandler: FleetRequestHandler<
       fleet_server_host_id: undefined,
       download_source_id: undefined,
     };
-
     // Check if there is any active fleet server enrolled into the fleet server policies policies
     if (fleetServerPolicies) {
       settingsResponse.fleet_server.policies = fleetServerPolicies;
diff --git a/x-pack/plugins/fleet/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts
index b9f672627daa7..b101937e45c27 100644
--- a/x-pack/plugins/fleet/server/routes/settings/index.ts
+++ b/x-pack/plugins/fleet/server/routes/settings/index.ts
@@ -15,9 +15,14 @@ import {
   GetEnrollmentSettingsRequestSchema,
   GetSpaceSettingsRequestSchema,
   PutSpaceSettingsRequestSchema,
+  SpaceSettingsResponseSchema,
+  SettingsResponseSchema,
+  GetEnrollmentSettingsResponseSchema,
 } from '../../types';
 import type { FleetConfigType } from '../../config';
 
+import { genericErrorResponse, notFoundResponse } from '../schema/errors';
+
 import { getEnrollmentSettingsHandler } from './enrollment_settings_handler';
 
 import {
@@ -45,7 +50,14 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
       .addVersion(
         {
           version: API_VERSIONS.public.v1,
-          validate: { request: GetSpaceSettingsRequestSchema },
+          validate: {
+            request: GetSpaceSettingsRequestSchema,
+            response: {
+              200: {
+                body: () => SpaceSettingsResponseSchema,
+              },
+            },
+          },
         },
         getSpaceSettingsHandler
       );
@@ -61,7 +73,14 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
       .addVersion(
         {
           version: API_VERSIONS.public.v1,
-          validate: { request: PutSpaceSettingsRequestSchema },
+          validate: {
+            request: PutSpaceSettingsRequestSchema,
+            response: {
+              200: {
+                body: () => SpaceSettingsResponseSchema,
+              },
+            },
+          },
         },
         putSpaceSettingsHandler
       );
@@ -74,11 +93,27 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
         fleet: { readSettings: true },
       },
       description: `Get settings`,
+      options: {
+        tags: ['oas-tag:Fleet internals'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: GetSettingsRequestSchema },
+        validate: {
+          request: GetSettingsRequestSchema,
+          response: {
+            200: {
+              body: () => SettingsResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            404: {
+              body: notFoundResponse,
+            },
+          },
+        },
       },
       getSettingsHandler
     );
@@ -89,11 +124,27 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
         fleet: { allSettings: true },
       },
       description: `Update settings`,
+      options: {
+        tags: ['oas-tag:Fleet internals'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: PutSettingsRequestSchema },
+        validate: {
+          request: PutSettingsRequestSchema,
+          response: {
+            200: {
+              body: () => SettingsResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            404: {
+              body: notFoundResponse,
+            },
+          },
+        },
       },
       putSettingsHandler
     );
@@ -104,11 +155,24 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
         return authz.fleet.addAgents || authz.fleet.addFleetServers;
       },
       description: `Get enrollment settings`,
+      options: {
+        tags: ['oas-tag:Fleet internals'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: { request: GetEnrollmentSettingsRequestSchema },
+        validate: {
+          request: GetEnrollmentSettingsRequestSchema,
+          response: {
+            200: {
+              body: () => GetEnrollmentSettingsResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       getEnrollmentSettingsHandler
     );
diff --git a/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts b/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts
new file mode 100644
index 0000000000000..0b52c44cde269
--- /dev/null
+++ b/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts
@@ -0,0 +1,85 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { httpServerMock } from '@kbn/core-http-server-mocks';
+
+import { xpackMocks } from '../../mocks';
+import type { FleetRequestHandlerContext } from '../..';
+import { SettingsResponseSchema, SpaceSettingsResponseSchema } from '../../types';
+
+import { getSettingsHandler, getSpaceSettingsHandler } from './settings_handler';
+
+jest.mock('../../services/spaces/space_settings', () => ({
+  getSpaceSettings: jest
+    .fn()
+    .mockResolvedValue({ allowed_namespace_prefixes: [], managed_by: 'kibana' }),
+  saveSpaceSettings: jest.fn(),
+}));
+
+jest.mock('../../services', () => ({
+  settingsService: {
+    getSettings: jest.fn().mockResolvedValue({
+      id: '1',
+      version: '1',
+      preconfigured_fields: ['fleet_server_hosts'],
+      secret_storage_requirements_met: true,
+      output_secret_storage_requirements_met: true,
+      has_seen_add_data_notice: true,
+      fleet_server_hosts: ['http://localhost:8220'],
+      prerelease_integrations_enabled: true,
+    }),
+  },
+  appContextService: {
+    getLogger: jest.fn().mockReturnValue({ error: jest.fn() }),
+    getInternalUserSOClientWithoutSpaceExtension: jest.fn(),
+  },
+  agentPolicyService: {
+    get: jest.fn(),
+    getByIDs: jest.fn(),
+  },
+}));
+
+describe('SettingsHandler', () => {
+  let context: FleetRequestHandlerContext;
+  let response: ReturnType<typeof httpServerMock.createResponseFactory>;
+
+  beforeEach(() => {
+    context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
+    response = httpServerMock.createResponseFactory();
+  });
+
+  it('should return valid space settings', async () => {
+    await getSpaceSettingsHandler(context, {} as any, response);
+    const expectedResponse = { item: { allowed_namespace_prefixes: [], managed_by: 'kibana' } };
+    expect(response.ok).toHaveBeenCalledWith({
+      body: expectedResponse,
+    });
+    const validationResp = SpaceSettingsResponseSchema.validate(expectedResponse);
+    expect(validationResp).toEqual(expectedResponse);
+  });
+
+  it('should return valid settings', async () => {
+    await getSettingsHandler(context, {} as any, response);
+    const expectedResponse = {
+      item: {
+        id: '1',
+        version: '1',
+        preconfigured_fields: ['fleet_server_hosts'],
+        secret_storage_requirements_met: true,
+        output_secret_storage_requirements_met: true,
+        has_seen_add_data_notice: true,
+        fleet_server_hosts: ['http://localhost:8220'],
+        prerelease_integrations_enabled: true,
+      },
+    };
+    expect(response.ok).toHaveBeenCalledWith({
+      body: expectedResponse,
+    });
+    const validationResp = SettingsResponseSchema.validate(expectedResponse);
+    expect(validationResp).toEqual(expectedResponse);
+  });
+});
diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts
index 6923d00c18222..79544e7a4e932 100644
--- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts
@@ -25,6 +25,7 @@ import { hasFleetServers } from '../../services/fleet_server';
 import { createFleetAuthzMock } from '../../../common/mocks';
 
 import { fleetSetupHandler, getFleetStatusHandler } from './handlers';
+import { FleetSetupResponseSchema, GetAgentsSetupResponseSchema } from '.';
 
 jest.mock('../../services/setup', () => {
   return {
@@ -94,6 +95,8 @@ describe('FleetSetupHandler', () => {
     };
     expect(response.customError).toHaveBeenCalledTimes(0);
     expect(response.ok).toHaveBeenCalledWith({ body: expectedBody });
+    const validationResp = FleetSetupResponseSchema.validate(expectedBody);
+    expect(validationResp).toEqual(expectedBody);
   });
 
   it('POST /setup fails w/500 on custom error', async () => {
@@ -209,6 +212,8 @@ describe('FleetStatusHandler', () => {
     };
     expect(response.customError).toHaveBeenCalledTimes(0);
     expect(response.ok).toHaveBeenCalledWith({ body: expectedBody });
+    const validationResp = GetAgentsSetupResponseSchema.validate(expectedBody);
+    expect(validationResp).toEqual(expectedBody);
   });
 
   it('POST /status  w/200 with fleet server standalone', async () => {
diff --git a/x-pack/plugins/fleet/server/routes/setup/index.ts b/x-pack/plugins/fleet/server/routes/setup/index.ts
index 7052aacfc329d..4b6fd2316832d 100644
--- a/x-pack/plugins/fleet/server/routes/setup/index.ts
+++ b/x-pack/plugins/fleet/server/routes/setup/index.ts
@@ -4,6 +4,7 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+import { schema } from '@kbn/config-schema';
 
 import type { FleetAuthzRouter } from '../../services/security';
 
@@ -12,8 +13,28 @@ import { API_VERSIONS } from '../../../common/constants';
 
 import type { FleetConfigType } from '../../../common/types';
 
+import { genericErrorResponse, internalErrorResponse } from '../schema/errors';
+
 import { getFleetStatusHandler, fleetSetupHandler } from './handlers';
 
+export const FleetSetupResponseSchema = schema.object(
+  {
+    isInitialized: schema.boolean(),
+    nonFatalErrors: schema.arrayOf(
+      schema.object({
+        name: schema.string(),
+        message: schema.string(),
+      })
+    ),
+  },
+  {
+    meta: {
+      description:
+        "A summary of the result of Fleet's `setup` lifecycle. If `isInitialized` is true, Fleet is ready to accept agent enrollment. `nonFatalErrors` may include useful insight into non-blocking issues with Fleet setup.",
+    },
+  }
+);
+
 export const registerFleetSetupRoute = (router: FleetAuthzRouter) => {
   router.versioned
     .post({
@@ -22,16 +43,59 @@ export const registerFleetSetupRoute = (router: FleetAuthzRouter) => {
         fleet: { setup: true },
       },
       description: `Initiate Fleet setup`,
+      options: {
+        tags: ['oas-tag:Fleet internals'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: false,
+        validate: {
+          request: {},
+          response: {
+            200: {
+              body: () => FleetSetupResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+            500: {
+              body: internalErrorResponse,
+            },
+          },
+        },
       },
       fleetSetupHandler
     );
 };
 
+export const GetAgentsSetupResponseSchema = schema.object(
+  {
+    isReady: schema.boolean(),
+    missing_requirements: schema.arrayOf(
+      schema.oneOf([
+        schema.literal('security_required'),
+        schema.literal('tls_required'),
+        schema.literal('api_keys'),
+        schema.literal('fleet_admin_user'),
+        schema.literal('fleet_server'),
+      ])
+    ),
+    missing_optional_features: schema.arrayOf(
+      schema.oneOf([schema.literal('encrypted_saved_object_encryption_key_required')])
+    ),
+    package_verification_key_id: schema.maybe(schema.string()),
+    is_space_awareness_enabled: schema.maybe(schema.boolean()),
+    is_secrets_storage_enabled: schema.maybe(schema.boolean()),
+  },
+  {
+    meta: {
+      description:
+        'A summary of the agent setup status. `isReady` indicates whether the setup is ready. If the setup is not ready, `missing_requirements` lists which requirements are missing.',
+    },
+  }
+);
+
 // That route is used by agent to setup Fleet
 export const registerCreateFleetSetupRoute = (router: FleetAuthzRouter) => {
   router.versioned
@@ -40,11 +104,25 @@ export const registerCreateFleetSetupRoute = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         fleet: { setup: true },
       },
+      description: `Initiate agent setup`,
+      options: {
+        tags: ['oas-tag:Elastic Agents'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: false,
+        validate: {
+          request: {},
+          response: {
+            200: {
+              body: () => FleetSetupResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       fleetSetupHandler
     );
@@ -57,11 +135,25 @@ export const registerGetFleetStatusRoute = (router: FleetAuthzRouter) => {
       fleetAuthz: {
         fleet: { setup: true },
       },
+      description: `Get agent setup info`,
+      options: {
+        tags: ['oas-tag:Elastic Agents'],
+      },
     })
     .addVersion(
       {
         version: API_VERSIONS.public.v1,
-        validate: false,
+        validate: {
+          request: {},
+          response: {
+            200: {
+              body: () => GetAgentsSetupResponseSchema,
+            },
+            400: {
+              body: genericErrorResponse,
+            },
+          },
+        },
       },
       getFleetStatusHandler
     );
diff --git a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts
index 99c349899aaa6..60e0c40c03f40 100644
--- a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts
+++ b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/handler.ts
@@ -9,18 +9,38 @@ import type { TypeOf } from '@kbn/config-schema';
 
 import { createStandaloneAgentApiKey } from '../../services/api_keys';
 import type { FleetRequestHandler, PostStandaloneAgentAPIKeyRequestSchema } from '../../types';
+import {
+  INDEX_PRIVILEGES,
+  canCreateStandaloneAgentApiKey,
+} from '../../services/api_keys/create_standalone_agent_api_key';
+import { FleetUnauthorizedError, defaultFleetErrorHandler } from '../../errors';
 
 export const createStandaloneAgentApiKeyHandler: FleetRequestHandler<
   undefined,
   undefined,
   TypeOf<typeof PostStandaloneAgentAPIKeyRequestSchema.body>
 > = async (context, request, response) => {
-  const coreContext = await context.core;
-  const esClient = coreContext.elasticsearch.client.asCurrentUser;
-  const key = await createStandaloneAgentApiKey(esClient, request.body.name);
-  return response.ok({
-    body: {
-      item: key,
-    },
-  });
+  try {
+    const coreContext = await context.core;
+    const esClient = coreContext.elasticsearch.client.asCurrentUser;
+    const canCreate = await canCreateStandaloneAgentApiKey(esClient);
+
+    if (!canCreate) {
+      throw new FleetUnauthorizedError(
+        `Missing permissions to create standalone API key, You need ${INDEX_PRIVILEGES.privileges.join(
+          ', '
+        )} for indices ${INDEX_PRIVILEGES.names.join(', ')}`
+      );
+    }
+
+    const key = await createStandaloneAgentApiKey(esClient, request.body.name);
+
+    return response.ok({
+      body: {
+        item: key,
+      },
+    });
+  } catch (error) {
+    return defaultFleetErrorHandler({ error, response });
+  }
 };
diff --git a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts
index 9255f058aee46..f0103c23e65dd 100644
--- a/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts
+++ b/x-pack/plugins/fleet/server/routes/standalone_agent_api_key/index.ts
@@ -21,13 +21,15 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
       path: CREATE_STANDALONE_AGENT_API_KEY_ROUTE,
       access: 'internal',
       fleetAuthz: {
-        fleet: { all: true },
+        fleet: { addAgents: true },
       },
     })
     .addVersion(
       {
         version: API_VERSIONS.internal.v1,
-        validate: { request: PostStandaloneAgentAPIKeyRequestSchema },
+        validate: {
+          request: PostStandaloneAgentAPIKeyRequestSchema,
+        },
       },
       createStandaloneAgentApiKeyHandler
     );
diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts
index b51f19291c58a..d7432fa7f2f51 100644
--- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts
@@ -28,9 +28,11 @@ import type { FleetRequestHandlerContext } from '../..';
 import type { MockedFleetAppContext } from '../../mocks';
 import { createAppContextStartContractMock, xpackMocks } from '../../mocks';
 import { agentPolicyService, appContextService } from '../../services';
-import type {
-  GetUninstallTokenRequestSchema,
-  GetUninstallTokensMetadataRequestSchema,
+import {
+  GetUninstallTokensMetadataResponseSchema,
+  type GetUninstallTokenRequestSchema,
+  type GetUninstallTokensMetadataRequestSchema,
+  GetUninstallTokenResponseSchema,
 } from '../../types/rest_spec/uninstall_token';
 
 import { createAgentPolicyMock } from '../../../common/mocks';
@@ -116,6 +118,10 @@ describe('uninstall token handlers', () => {
       expect(response.ok).toHaveBeenCalledWith({
         body: uninstallTokensResponseFixture,
       });
+      const validateResp = GetUninstallTokensMetadataResponseSchema.validate(
+        uninstallTokensResponseFixture
+      );
+      expect(validateResp).toEqual(uninstallTokensResponseFixture);
     });
 
     it('should return internal error when uninstallTokenService throws error', async () => {
@@ -131,18 +137,19 @@ describe('uninstall token handlers', () => {
   });
 
   describe('getUninstallTokenHandler', () => {
-    const uninstallTokenFixture: UninstallToken = {
-      id: 'id-1',
-      policy_id: 'policy-id-1',
-      policy_name: null,
-      created_at: '2023-06-15T16:46:48.274Z',
-      token: '123456789',
-    };
+    let uninstallTokenFixture: UninstallToken;
 
     let getTokenMock: jest.Mock;
     let request: KibanaRequest<TypeOf<typeof GetUninstallTokenRequestSchema.params>>;
 
     beforeEach(async () => {
+      uninstallTokenFixture = {
+        id: 'id-1',
+        policy_id: 'policy-id-1',
+        policy_name: null,
+        created_at: '2023-06-15T16:46:48.274Z',
+        token: '123456789',
+      };
       const uninstallTokenService = (await context.fleet).uninstallTokenService.asCurrentUser;
       getTokenMock = uninstallTokenService.getToken as jest.Mock;
 
@@ -165,6 +172,10 @@ describe('uninstall token handlers', () => {
           item: uninstallTokenFixture,
         },
       });
+      const validateResp = GetUninstallTokenResponseSchema.validate({
+        item: uninstallTokenFixture,
+      });
+      expect(validateResp).toEqual({ item: uninstallTokenFixture });
     });
 
     it('should return internal error when uninstallTokenService throws error', async () => {
diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts
index 24d85b8d14250..2eb9a83456845 100644
--- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts
+++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts
@@ -84,7 +84,6 @@ export const getUninstallTokenHandler: FleetRequestHandler<
         body: { message: `Uninstall Token not found with id ${uninstallTokenId}` },
       });
     }
-
     const body: GetUninstallTokenResponse = {
       item: token,
     };
diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/index.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/index.ts
index 9fb91b45fa373..a90dd678e99dd 100644
--- a/x-pack/plugins/fleet/server/routes/uninstall_token/index.ts
+++ b/x-pack/plugins/fleet/server/routes/uninstall_token/index.ts
@@ -4,16 +4,21 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
+
 import { UNINSTALL_TOKEN_ROUTES, API_VERSIONS } from '../../../common/constants';
 import type { FleetConfigType } from '../../config';
 
 import type { FleetAuthzRouter } from '../../services/security';
 import {
   GetUninstallTokenRequestSchema,
+  GetUninstallTokenResponseSchema,
   GetUninstallTokensMetadataRequestSchema,
+  GetUninstallTokensMetadataResponseSchema,
 } from '../../types/rest_spec/uninstall_token';
 import { parseExperimentalConfigValue } from '../../../common/experimental_features';
 
+import { genericErrorResponse } from '../schema/errors';
+
 import { getUninstallTokenHandler, getUninstallTokensMetadataHandler } from './handlers';
 
 export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => {
@@ -26,11 +31,25 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
         fleetAuthz: {
           fleet: { allAgents: true },
         },
+        description: 'List metadata for latest uninstall tokens per agent policy',
+        options: {
+          tags: ['oas-tag:Fleet uninstall tokens'],
+        },
       })
       .addVersion(
         {
           version: API_VERSIONS.public.v1,
-          validate: { request: GetUninstallTokensMetadataRequestSchema },
+          validate: {
+            request: GetUninstallTokensMetadataRequestSchema,
+            response: {
+              200: {
+                body: () => GetUninstallTokensMetadataResponseSchema,
+              },
+              400: {
+                body: genericErrorResponse,
+              },
+            },
+          },
         },
         getUninstallTokensMetadataHandler
       );
@@ -41,11 +60,25 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
         fleetAuthz: {
           fleet: { allAgents: true },
         },
+        description: 'Get one decrypted uninstall token by its ID',
+        options: {
+          tags: ['oas-tag:Fleet uninstall tokens'],
+        },
       })
       .addVersion(
         {
           version: API_VERSIONS.public.v1,
-          validate: { request: GetUninstallTokenRequestSchema },
+          validate: {
+            request: GetUninstallTokenRequestSchema,
+            response: {
+              200: {
+                body: () => GetUninstallTokenResponseSchema,
+              },
+              400: {
+                body: genericErrorResponse,
+              },
+            },
+          },
         },
         getUninstallTokenHandler
       );
diff --git a/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts
index 011d8dfe8ec82..51cf0b7849a03 100644
--- a/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts
+++ b/x-pack/plugins/fleet/server/services/api_keys/create_standalone_agent_api_key.ts
@@ -7,6 +7,21 @@
 
 import type { ElasticsearchClient } from '@kbn/core/server';
 
+export const CLUSTER_PRIVILEGES = ['monitor'];
+
+export const INDEX_PRIVILEGES = {
+  names: ['logs-*-*', 'metrics-*-*', 'traces-*-*', 'synthetics-*-*'],
+  privileges: ['auto_configure', 'create_doc'],
+};
+
+export async function canCreateStandaloneAgentApiKey(esClient: ElasticsearchClient) {
+  const res = await esClient.security.hasPrivileges({
+    cluster: CLUSTER_PRIVILEGES,
+    index: [INDEX_PRIVILEGES],
+  });
+
+  return res.has_all_requested;
+}
 export function createStandaloneAgentApiKey(esClient: ElasticsearchClient, name: string) {
   // Based on https://www.elastic.co/guide/en/fleet/master/grant-access-to-elasticsearch.html#create-api-key-standalone-agent
   return esClient.security.createApiKey({
@@ -17,13 +32,8 @@ export function createStandaloneAgentApiKey(esClient: ElasticsearchClient, name:
       },
       role_descriptors: {
         standalone_agent: {
-          cluster: ['monitor'],
-          indices: [
-            {
-              names: ['logs-*-*', 'metrics-*-*', 'traces-*-*', 'synthetics-*-*'],
-              privileges: ['auto_configure', 'create_doc'],
-            },
-          ],
+          cluster: CLUSTER_PRIVILEGES,
+          indices: [INDEX_PRIVILEGES],
         },
       },
     },
diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts
index 68829b734eeaf..394a2365f3c03 100644
--- a/x-pack/plugins/fleet/server/services/settings.ts
+++ b/x-pack/plugins/fleet/server/services/settings.ts
@@ -36,7 +36,15 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise
   return {
     id: settingsSo.id,
     version: settingsSo.version,
-    ...settingsSo.attributes,
+    secret_storage_requirements_met: settingsSo.attributes.secret_storage_requirements_met,
+    output_secret_storage_requirements_met:
+      settingsSo.attributes.output_secret_storage_requirements_met,
+    has_seen_add_data_notice: settingsSo.attributes.has_seen_add_data_notice,
+    prerelease_integrations_enabled: settingsSo.attributes.prerelease_integrations_enabled,
+    use_space_awareness_migration_status:
+      settingsSo.attributes.use_space_awareness_migration_status,
+    use_space_awareness_migration_started_at:
+      settingsSo.attributes.use_space_awareness_migration_started_at,
     fleet_server_hosts: fleetServerHosts.items.flatMap((item) => item.host_urls),
     preconfigured_fields: getConfigFleetServerHosts() ? ['fleet_server_hosts'] : [],
   };
diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts
index e105bc82b27db..68d3a089c2342 100644
--- a/x-pack/plugins/fleet/server/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts
@@ -16,15 +16,24 @@ export const PackagePolicyNamespaceSchema = schema.string({
       return namespaceValidation.error;
     }
   },
+  meta: {
+    description:
+      "The package policy namespace. Leave blank to inherit the agent policy's namespace.",
+  },
 });
 
-const ConfigRecordSchema = schema.recordOf(
+export const ConfigRecordSchema = schema.recordOf(
   schema.string(),
   schema.object({
     type: schema.maybe(schema.string()),
     value: schema.maybe(schema.any()),
     frozen: schema.maybe(schema.boolean()),
-  })
+  }),
+  {
+    meta: {
+      description: 'Package variable (see integration documentation for more information)',
+    },
+  }
 );
 
 const PackagePolicyStreamsSchema = {
@@ -50,33 +59,18 @@ const PackagePolicyStreamsSchema = {
     ),
   }),
   vars: schema.maybe(ConfigRecordSchema),
-  config: schema.maybe(
-    schema.recordOf(
-      schema.string(),
-      schema.object({
-        type: schema.maybe(schema.string()),
-        value: schema.maybe(schema.any()),
-      })
-    )
-  ),
+  config: schema.maybe(ConfigRecordSchema),
   compiled_stream: schema.maybe(schema.any()),
 };
 
-const PackagePolicyInputsSchema = {
+export const PackagePolicyInputsSchema = {
+  id: schema.maybe(schema.string()),
   type: schema.string(),
   policy_template: schema.maybe(schema.string()),
   enabled: schema.boolean(),
   keep_enabled: schema.maybe(schema.boolean()),
   vars: schema.maybe(ConfigRecordSchema),
-  config: schema.maybe(
-    schema.recordOf(
-      schema.string(),
-      schema.object({
-        type: schema.maybe(schema.string()),
-        value: schema.maybe(schema.any()),
-      })
-    )
-  ),
+  config: schema.maybe(ConfigRecordSchema),
   streams: schema.arrayOf(schema.object(PackagePolicyStreamsSchema)),
 };
 
@@ -92,44 +86,91 @@ const ExperimentalDataStreamFeatures = schema.arrayOf(
   })
 );
 
-const PackagePolicyBaseSchema = {
-  name: schema.string(),
-  description: schema.maybe(schema.string()),
+export const PackagePolicyPackageSchema = schema.object({
+  name: schema.string({
+    meta: {
+      description: 'Package name',
+    },
+  }),
+  title: schema.maybe(schema.string()),
+  version: schema.string({
+    meta: {
+      description: 'Package version',
+    },
+  }),
+  experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
+  requires_root: schema.maybe(schema.boolean()),
+});
+
+export const PackagePolicyBaseSchema = {
+  name: schema.string({
+    meta: {
+      description: 'Package policy name (should be unique)',
+    },
+  }),
+  description: schema.maybe(
+    schema.string({
+      meta: {
+        description: 'Package policy description',
+      },
+    })
+  ),
   namespace: schema.maybe(PackagePolicyNamespaceSchema),
-  policy_id: schema.nullable(schema.maybe(schema.string())),
-  policy_ids: schema.maybe(schema.arrayOf(schema.string())),
-  output_id: schema.nullable(schema.maybe(schema.string())),
+  policy_id: schema.maybe(
+    schema.oneOf([
+      schema.literal(null),
+      schema.string({
+        meta: {
+          description: 'Agent policy ID where that package policy will be added',
+          deprecated: true,
+        },
+      }),
+    ])
+  ),
+  policy_ids: schema.maybe(
+    schema.arrayOf(
+      schema.string({
+        meta: {
+          description: 'Agent policy IDs where that package policy will be added',
+        },
+      })
+    )
+  ),
+  output_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
   enabled: schema.boolean(),
   is_managed: schema.maybe(schema.boolean()),
-  package: schema.maybe(
-    schema.object({
-      name: schema.string(),
-      title: schema.string(),
-      version: schema.string(),
-      experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
-      requires_root: schema.maybe(schema.boolean()),
-    })
-  ),
+  package: schema.maybe(PackagePolicyPackageSchema),
+
   inputs: schema.arrayOf(schema.object(PackagePolicyInputsSchema)),
   vars: schema.maybe(ConfigRecordSchema),
   overrides: schema.maybe(
-    schema.nullable(
-      schema.object({
-        inputs: schema.maybe(
-          schema.recordOf(schema.string(), schema.any(), {
-            validate: (val) => {
-              if (
-                Object.keys(val).some(
-                  (key) => key.match(/^compiled_inputs(\.)?/) || key.match(/^compiled_stream(\.)?/)
-                )
-              ) {
-                return 'Overrides of compiled_inputs and compiled_stream are not allowed';
-              }
-            },
-          })
-        ),
-      })
-    )
+    schema.oneOf([
+      schema.literal(null),
+      schema.object(
+        {
+          inputs: schema.maybe(
+            schema.recordOf(schema.string(), schema.any(), {
+              validate: (val) => {
+                if (
+                  Object.keys(val).some(
+                    (key) =>
+                      key.match(/^compiled_inputs(\.)?/) || key.match(/^compiled_stream(\.)?/)
+                  )
+                ) {
+                  return 'Overrides of compiled_inputs and compiled_stream are not allowed';
+                }
+              },
+            })
+          ),
+        },
+        {
+          meta: {
+            description:
+              'Override settings that are defined in the package policy. The override option should be used only in unusual circumstances and not as a routine procedure.',
+          },
+        }
+      ),
+    ])
   ),
 };
 
@@ -142,15 +183,7 @@ export const NewPackagePolicySchema = schema.object({
 const CreatePackagePolicyProps = {
   ...PackagePolicyBaseSchema,
   enabled: schema.maybe(schema.boolean()),
-  package: schema.maybe(
-    schema.object({
-      name: schema.string(),
-      title: schema.maybe(schema.string()),
-      version: schema.string(),
-      experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
-      requires_root: schema.maybe(schema.boolean()),
-    })
-  ),
+  package: schema.maybe(PackagePolicyPackageSchema),
   inputs: schema.arrayOf(
     schema.object({
       ...PackagePolicyInputsSchema,
@@ -161,11 +194,24 @@ const CreatePackagePolicyProps = {
 
 export const CreatePackagePolicyRequestBodySchema = schema.object({
   ...CreatePackagePolicyProps,
-  id: schema.maybe(schema.string()),
-  force: schema.maybe(schema.boolean()),
+  id: schema.maybe(
+    schema.string({
+      meta: {
+        description: 'Package policy unique identifier',
+      },
+    })
+  ),
+  force: schema.maybe(
+    schema.boolean({
+      meta: {
+        description:
+          'Force package policy creation even if package is not verified, or if the agent policy is managed.',
+      },
+    })
+  ),
 });
 
-const SimplifiedVarsSchema = schema.recordOf(
+export const SimplifiedVarsSchema = schema.recordOf(
   schema.string(),
   schema.nullable(
     schema.oneOf([
@@ -180,6 +226,55 @@ const SimplifiedVarsSchema = schema.recordOf(
         isSecretRef: schema.boolean(),
       }),
     ])
+  ),
+  {
+    meta: {
+      description:
+        'Input/stream level variable (see integration documentation for more information)',
+    },
+  }
+);
+
+export const SimplifiedPackagePolicyInputsSchema = schema.maybe(
+  schema.recordOf(
+    schema.string(),
+    schema.object({
+      enabled: schema.maybe(
+        schema.boolean({
+          meta: {
+            description: 'enable or disable that input, (default to true)',
+          },
+        })
+      ),
+      vars: schema.maybe(SimplifiedVarsSchema),
+      streams: schema.maybe(
+        schema.recordOf(
+          schema.string(),
+          schema.object({
+            enabled: schema.maybe(
+              schema.boolean({
+                meta: {
+                  description: 'enable or disable that stream, (default to true)',
+                },
+              })
+            ),
+            vars: schema.maybe(SimplifiedVarsSchema),
+          }),
+          {
+            meta: {
+              description:
+                'Input streams (see integration documentation to know what streams are available)',
+            },
+          }
+        )
+      ),
+    }),
+    {
+      meta: {
+        description:
+          'Package policy inputs (see integration documentation to know what inputs are available)',
+      },
+    }
   )
 );
 
@@ -188,26 +283,9 @@ export const SimplifiedPackagePolicyBaseSchema = schema.object({
   name: schema.string(),
   description: schema.maybe(schema.string()),
   namespace: schema.maybe(schema.string()),
-  output_id: schema.nullable(schema.maybe(schema.string())),
+  output_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
   vars: schema.maybe(SimplifiedVarsSchema),
-  inputs: schema.maybe(
-    schema.recordOf(
-      schema.string(),
-      schema.object({
-        enabled: schema.maybe(schema.boolean()),
-        vars: schema.maybe(SimplifiedVarsSchema),
-        streams: schema.maybe(
-          schema.recordOf(
-            schema.string(),
-            schema.object({
-              enabled: schema.maybe(schema.boolean()),
-              vars: schema.maybe(SimplifiedVarsSchema),
-            })
-          )
-        ),
-      })
-    )
-  ),
+  inputs: SimplifiedPackagePolicyInputsSchema,
 });
 
 export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolicyBaseSchema.extends(
@@ -221,15 +299,10 @@ export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolic
 
 export const SimplifiedCreatePackagePolicyRequestBodySchema =
   SimplifiedPackagePolicyBaseSchema.extends({
-    policy_id: schema.nullable(schema.maybe(schema.string())),
+    policy_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
     policy_ids: schema.maybe(schema.arrayOf(schema.string())),
     force: schema.maybe(schema.boolean()),
-    package: schema.object({
-      name: schema.string(),
-      version: schema.string(),
-      experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures),
-      requires_root: schema.maybe(schema.boolean()),
-    }),
+    package: PackagePolicyPackageSchema,
   });
 
 export const UpdatePackagePolicyRequestBodySchema = schema.object({
@@ -261,15 +334,19 @@ export const PackagePolicySchema = schema.object({
   updated_by: schema.string(),
   created_at: schema.string(),
   created_by: schema.string(),
-  elasticsearch: schema.maybe(
-    schema.object({
-      privileges: schema.maybe(
-        schema.object({
-          cluster: schema.maybe(schema.arrayOf(schema.string())),
-        })
-      ),
-    })
-  ),
+  elasticsearch: schema
+    .maybe(
+      schema.object({
+        privileges: schema.maybe(
+          schema.object({
+            cluster: schema.maybe(schema.arrayOf(schema.string())),
+          })
+        ),
+      })
+    )
+    .extendsDeep({
+      unknowns: 'allow',
+    }),
   inputs: schema.arrayOf(
     schema.object({
       ...PackagePolicyInputsSchema,
@@ -284,3 +361,46 @@ export const PackagePolicySchema = schema.object({
     )
   ),
 });
+
+export const PackagePolicyResponseSchema = PackagePolicySchema.extends({
+  vars: schema.maybe(schema.oneOf([ConfigRecordSchema, schema.maybe(SimplifiedVarsSchema)])),
+  inputs: schema.oneOf([
+    schema.arrayOf(
+      schema.object({
+        ...PackagePolicyInputsSchema,
+        compiled_input: schema.maybe(schema.any()),
+      })
+    ),
+    SimplifiedPackagePolicyInputsSchema,
+  ]),
+  spaceIds: schema.maybe(schema.arrayOf(schema.string())),
+  agents: schema.maybe(schema.number()),
+});
+
+export const OrphanedPackagePoliciesResponseSchema = schema.object({
+  items: schema.arrayOf(PackagePolicyResponseSchema),
+  total: schema.number(),
+});
+
+export const DryRunPackagePolicySchema = schema.object({
+  ...PackagePolicyBaseSchema,
+  id: schema.maybe(schema.string()),
+  force: schema.maybe(schema.boolean()),
+  errors: schema.maybe(
+    schema.arrayOf(
+      schema.object({
+        message: schema.string(),
+        key: schema.maybe(schema.string()),
+      })
+    )
+  ),
+  missingVars: schema.maybe(schema.arrayOf(schema.string())),
+});
+
+export const PackagePolicyStatusResponseSchema = schema.object({
+  id: schema.string(),
+  success: schema.boolean(),
+  name: schema.maybe(schema.string()),
+  statusCode: schema.maybe(schema.number()),
+  body: schema.maybe(schema.object({ message: schema.string() })),
+});
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/common.ts b/x-pack/plugins/fleet/server/types/rest_spec/common.ts
index 0c5f16ff87f90..2be083d677dd3 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/common.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/common.ts
@@ -23,7 +23,10 @@ export const ListWithKuerySchema = schema.object({
 });
 
 export const BulkRequestBodySchema = schema.object({
-  ids: schema.arrayOf(schema.string(), { minSize: 1 }),
+  ids: schema.arrayOf(schema.string(), {
+    minSize: 1,
+    meta: { description: 'list of package policy ids' },
+  }),
   ignoreMissing: schema.maybe(schema.boolean()),
 });
 
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
index 88b4452a5fe7a..1cf332efba843 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts
@@ -9,6 +9,10 @@ import { schema } from '@kbn/config-schema';
 
 import {
   CreatePackagePolicyRequestBodySchema,
+  DryRunPackagePolicySchema,
+  PackagePolicyPackageSchema,
+  PackagePolicyResponseSchema,
+  PackagePolicyStatusResponseSchema,
   SimplifiedCreatePackagePolicyRequestBodySchema,
   UpdatePackagePolicyRequestBodySchema,
 } from '../models';
@@ -59,6 +63,10 @@ export const BulkGetPackagePoliciesRequestSchema = {
   }),
 };
 
+export const BulkGetPackagePoliciesResponseBodySchema = schema.object({
+  items: schema.arrayOf(PackagePolicyResponseSchema),
+});
+
 export const GetOnePackagePolicyRequestSchema = {
   params: schema.object({
     packagePolicyId: schema.string(),
@@ -71,10 +79,14 @@ export const GetOnePackagePolicyRequestSchema = {
 };
 
 export const CreatePackagePolicyRequestSchema = {
-  body: schema.oneOf([
-    CreatePackagePolicyRequestBodySchema,
-    SimplifiedCreatePackagePolicyRequestBodySchema,
-  ]),
+  body: schema.oneOf(
+    [CreatePackagePolicyRequestBodySchema, SimplifiedCreatePackagePolicyRequestBodySchema],
+    {
+      meta: {
+        description: 'You should use inputs as an object and not use the deprecated inputs array.',
+      },
+    }
+  ),
   query: schema.object({
     format: schema.maybe(
       schema.oneOf([schema.literal(inputsFormat.Simplified), schema.literal(inputsFormat.Legacy)])
@@ -82,6 +94,10 @@ export const CreatePackagePolicyRequestSchema = {
   }),
 };
 
+export const CreatePackagePolicyResponseSchema = schema.object({
+  item: PackagePolicyResponseSchema,
+});
+
 export const UpdatePackagePolicyRequestSchema = {
   ...GetOnePackagePolicyRequestSchema,
   body: schema.oneOf([
@@ -102,6 +118,25 @@ export const DeletePackagePoliciesRequestSchema = {
   }),
 };
 
+export const DeletePackagePoliciesResponseBodySchema = schema.arrayOf(
+  PackagePolicyStatusResponseSchema.extends({
+    policy_id: schema.maybe(
+      schema.oneOf([
+        schema.literal(null),
+        schema.string({
+          meta: {
+            description: 'Use `policy_ids` instead',
+            deprecated: true,
+          },
+        }),
+      ])
+    ),
+    policy_ids: schema.arrayOf(schema.string()),
+    output_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+    package: PackagePolicyPackageSchema,
+  })
+);
+
 export const DeleteOnePackagePolicyRequestSchema = {
   params: schema.object({
     packagePolicyId: schema.string(),
@@ -111,15 +146,104 @@ export const DeleteOnePackagePolicyRequestSchema = {
   }),
 };
 
+export const DeleteOnePackagePolicyResponseSchema = schema.object({
+  id: schema.string(),
+});
+
 export const UpgradePackagePoliciesRequestSchema = {
   body: schema.object({
     packagePolicyIds: schema.arrayOf(schema.string()),
   }),
 };
 
+export const UpgradePackagePoliciesResponseBodySchema = schema.arrayOf(
+  PackagePolicyStatusResponseSchema
+);
+
 export const DryRunPackagePoliciesRequestSchema = {
   body: schema.object({
     packagePolicyIds: schema.arrayOf(schema.string()),
     packageVersion: schema.maybe(schema.string()),
   }),
 };
+
+export const DryRunPackagePoliciesResponseBodySchema = schema.arrayOf(
+  schema.object({
+    name: schema.maybe(schema.string()),
+    statusCode: schema.maybe(schema.number()),
+    body: schema.maybe(schema.object({ message: schema.string() })),
+    hasErrors: schema.boolean(),
+    diff: schema.maybe(
+      schema.arrayOf(
+        schema.oneOf([
+          PackagePolicyResponseSchema.extends({
+            id: schema.maybe(schema.string()),
+          }),
+          DryRunPackagePolicySchema,
+        ])
+      )
+    ),
+    agent_diff: schema.maybe(
+      schema.arrayOf(
+        schema.arrayOf(
+          schema
+            .object({
+              id: schema.string(),
+              name: schema.string(),
+              revision: schema.number(),
+              type: schema.string(),
+              data_stream: schema.object({
+                namespace: schema.string(),
+              }),
+              use_output: schema.string(),
+              package_policy_id: schema.string(),
+              meta: schema.maybe(
+                schema.object({
+                  package: schema
+                    .object({
+                      name: schema.string(),
+                      version: schema.string(),
+                    })
+                    .extendsDeep({
+                      // equivalent of allowing extra keys like `[key: string]: any;`
+                      unknowns: 'allow',
+                    }),
+                })
+              ),
+              streams: schema.maybe(
+                schema.arrayOf(
+                  schema
+                    .object({
+                      id: schema.string(),
+                      data_stream: schema.object({
+                        dataset: schema.string(),
+                        type: schema.maybe(schema.string()),
+                      }),
+                    })
+                    .extendsDeep({
+                      unknowns: 'allow',
+                    })
+                )
+              ),
+              processors: schema.maybe(
+                schema.arrayOf(
+                  schema.object({
+                    add_fields: schema.object({
+                      target: schema.string(),
+                      fields: schema.recordOf(
+                        schema.string(),
+                        schema.oneOf([schema.string(), schema.number()])
+                      ),
+                    }),
+                  })
+                )
+              ),
+            })
+            .extendsDeep({
+              unknowns: 'allow',
+            })
+        )
+      )
+    ),
+  })
+);
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts
index 10db7b0f4def7..6553d2e976bed 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts
@@ -41,6 +41,30 @@ export const PutSettingsRequestSchema = {
 
 export const GetSpaceSettingsRequestSchema = {};
 
+export const SpaceSettingsResponseSchema = schema.object({
+  item: schema.object({
+    managed_by: schema.maybe(schema.string()),
+    allowed_namespace_prefixes: schema.arrayOf(schema.string()),
+  }),
+});
+
+export const SettingsResponseSchema = schema.object({
+  item: schema.object({
+    has_seen_add_data_notice: schema.maybe(schema.boolean()),
+    fleet_server_hosts: schema.maybe(schema.arrayOf(schema.string())),
+    prerelease_integrations_enabled: schema.boolean(),
+    id: schema.string(),
+    version: schema.maybe(schema.string()),
+    preconfigured_fields: schema.maybe(schema.arrayOf(schema.literal('fleet_server_hosts'))),
+    secret_storage_requirements_met: schema.maybe(schema.boolean()),
+    output_secret_storage_requirements_met: schema.maybe(schema.boolean()),
+    use_space_awareness_migration_status: schema.maybe(
+      schema.oneOf([schema.literal('pending'), schema.literal('success'), schema.literal('error')])
+    ),
+    use_space_awareness_migration_started_at: schema.maybe(schema.string()),
+  }),
+});
+
 export const PutSpaceSettingsRequestSchema = {
   body: schema.object({
     allowed_namespace_prefixes: schema.maybe(
@@ -64,3 +88,70 @@ export const GetEnrollmentSettingsRequestSchema = {
     })
   ),
 };
+
+export const GetEnrollmentSettingsResponseSchema = schema.object({
+  fleet_server: schema.object({
+    policies: schema.arrayOf(
+      schema.object({
+        id: schema.string(),
+        name: schema.string(),
+        is_managed: schema.boolean(),
+        is_default_fleet_server: schema.maybe(schema.boolean()),
+        has_fleet_server: schema.maybe(schema.boolean()),
+        fleet_server_host_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+        download_source_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+        space_ids: schema.maybe(schema.arrayOf(schema.string())),
+      })
+    ),
+    has_active: schema.boolean(),
+    host: schema.maybe(
+      schema.object({
+        id: schema.string(),
+        name: schema.string(),
+        host_urls: schema.arrayOf(schema.string()),
+        is_default: schema.boolean(),
+        is_preconfigured: schema.boolean(),
+        is_internal: schema.maybe(schema.boolean()),
+        proxy_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+      })
+    ),
+    host_proxy: schema.maybe(
+      schema.object({
+        id: schema.string(),
+        proxy_headers: schema.maybe(
+          schema.recordOf(
+            schema.string(),
+            schema.oneOf([schema.string(), schema.number(), schema.boolean()])
+          )
+        ),
+        name: schema.string(),
+        url: schema.string(),
+        certificate_authorities: schema.maybe(
+          schema.oneOf([schema.literal(null), schema.string()])
+        ),
+        certificate: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+        certificate_key: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+        is_preconfigured: schema.boolean(),
+      })
+    ),
+  }),
+  download_source: schema.maybe(
+    schema.object({
+      id: schema.string(),
+      name: schema.string(),
+      host: schema.string(),
+      is_default: schema.boolean(),
+      proxy_id: schema.maybe(
+        schema.oneOf([
+          schema.literal(null),
+          schema.string({
+            meta: {
+              description:
+                'The ID of the proxy to use for this download source. See the proxies API for more information.',
+            },
+          }),
+        ])
+      ),
+    })
+  ),
+});
diff --git a/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts b/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts
index 924e1da2cb9e8..7915e5bbc8b07 100644
--- a/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts
+++ b/x-pack/plugins/fleet/server/types/rest_spec/uninstall_token.ts
@@ -6,17 +6,48 @@
  */
 import { schema } from '@kbn/config-schema';
 
+import { ListResponseSchema } from '../../routes/schema/utils';
+
 export const GetUninstallTokensMetadataRequestSchema = {
   query: schema.object({
-    policyId: schema.maybe(schema.string({ maxLength: 50 })),
+    policyId: schema.maybe(
+      schema.string({
+        maxLength: 50,
+        meta: { description: 'Partial match filtering for policy IDs' },
+      })
+    ),
     search: schema.maybe(schema.string({ maxLength: 50 })),
-    perPage: schema.maybe(schema.number({ defaultValue: 20, min: 5 })),
+    perPage: schema.maybe(
+      schema.number({
+        defaultValue: 20,
+        min: 5,
+        meta: { description: 'The number of items to return' },
+      })
+    ),
     page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })),
   }),
 };
 
+const UninstallTokenMetadataSchema = schema.object({
+  id: schema.string(),
+  policy_id: schema.string(),
+  policy_name: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
+  created_at: schema.string(),
+  namespaces: schema.maybe(schema.arrayOf(schema.string())),
+});
+
+export const GetUninstallTokensMetadataResponseSchema = ListResponseSchema(
+  UninstallTokenMetadataSchema
+);
+
 export const GetUninstallTokenRequestSchema = {
   params: schema.object({
     uninstallTokenId: schema.string(),
   }),
 };
+
+export const GetUninstallTokenResponseSchema = schema.object({
+  item: UninstallTokenMetadataSchema.extends({
+    token: schema.string(),
+  }),
+});
diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts
index 1bb954cf990d2..9be09fe4ee554 100644
--- a/x-pack/plugins/fleet/server/types/so_attributes.ts
+++ b/x-pack/plugins/fleet/server/types/so_attributes.ts
@@ -239,6 +239,8 @@ export interface SettingsSOAttributes {
   fleet_server_hosts?: string[];
   secret_storage_requirements_met?: boolean;
   output_secret_storage_requirements_met?: boolean;
+  use_space_awareness_migration_status?: 'pending' | 'success' | 'error';
+  use_space_awareness_migration_started_at?: string | null;
 }
 
 export interface SpaceSettingsSOAttributes {
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/build_integration.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/build_integration.ts
index 3161f06f8a6ae..81f2231bcb05d 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/build_integration.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/build_integration.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { Integration } from '../../common/api/model/common_attributes';
+import { Integration } from '../../common';
 
 export const testIntegration: Integration = {
   name: 'integration',
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts
index 5f68dfc4668ec..80366e7bd6f93 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { SamplesFormatName } from '../../common/api/model/common_attributes';
+import { SamplesFormatName } from '../../common';
 import type { Pipeline } from '../../common';
 
 export const categorizationInitialPipeline: Pipeline = {
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts
index eba49baaa8595..67e6c7d8f6a5e 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { SamplesFormatName } from '../../common/api/model/common_attributes';
+import { SamplesFormatName } from '../../common';
 
 export const ecsMappingExpectedResults = {
   mapping: {
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/kv.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/kv.ts
index 587d8987c0960..49c8d2e16809b 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/kv.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/kv.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { SamplesFormatName } from '../../common/api/model/common_attributes';
+import { SamplesFormatName } from '../../common';
 
 export const kvState = {
   lastExecutedChain: 'testchain',
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/log_type_detection.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/log_type_detection.ts
index 31d7208eb4d87..b3c1e4c05ebd9 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/log_type_detection.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/log_type_detection.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { SamplesFormatName } from '../../common/api/model/common_attributes';
+import { SamplesFormatName } from '../../common';
 
 export const logFormatDetectionTestState = {
   lastExecutedChain: 'testchain',
diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts
index 9d0915735d52a..d96d845ae43b6 100644
--- a/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts
+++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { SamplesFormatName } from '../../common/api/model/common_attributes';
+import { SamplesFormatName } from '../../common';
 import type { Pipeline } from '../../common';
 
 export const relatedInitialPipeline: Pipeline = {
diff --git a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.ts b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.gen.ts
similarity index 95%
rename from x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.ts
rename to x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.gen.ts
index 597c44fde54a5..a224bb3cbe241 100644
--- a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.ts
+++ b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.gen.ts
@@ -17,13 +17,13 @@
 import { z } from '@kbn/zod';
 
 import {
+  PackageName,
+  DataStreamName,
   LogSamples,
   Connector,
   LangSmithOptions,
-  DataStreamName,
-  PackageName,
-} from '../model/common_attributes';
-import { AnalyzeLogsAPIResponse } from '../model/response_schemas';
+} from '../model/common_attributes.gen';
+import { AnalyzeLogsAPIResponse } from '../model/response_schemas.gen';
 
 export type AnalyzeLogsRequestBody = z.infer<typeof AnalyzeLogsRequestBody>;
 export const AnalyzeLogsRequestBody = z.object({
diff --git a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.schema.yaml
index 23050bc6a50fc..165a2cff91a06 100644
--- a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Analyzes log samples and processes them.
       operationId: AnalyzeLogs
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Analyzes log samples and processes them
       tags:
         - Analyze Logs API
diff --git a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.test.ts b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.test.ts
index 08595c1ce1911..30848ca8945cf 100644
--- a/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.test.ts
+++ b/x-pack/plugins/integration_assistant/common/api/analyze_logs/analyze_logs_route.test.ts
@@ -6,7 +6,7 @@
  */
 
 import { expectParseSuccess } from '@kbn/zod-helpers';
-import { AnalyzeLogsRequestBody } from './analyze_logs_route';
+import { AnalyzeLogsRequestBody } from './analyze_logs_route.gen';
 import { getAnalyzeLogsRequestBody } from '../model/api_test.mock';
 
 describe('Analyze Logs request schema', () => {
diff --git a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.gen.ts
similarity index 64%
rename from x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts
rename to x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.gen.ts
index ce9c919c0d43d..46db1066ffdff 100644
--- a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts
+++ b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.gen.ts
@@ -5,9 +5,18 @@
  * 2.0.
  */
 
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Integration Assistatnt Build Integrarion API endpoint
+ *   version: 1
+ */
+
 import { z } from '@kbn/zod';
 
-import { Integration } from '../model/common_attributes';
+import { Integration } from '../model/common_attributes.gen';
 
 export type BuildIntegrationRequestBody = z.infer<typeof BuildIntegrationRequestBody>;
 export const BuildIntegrationRequestBody = z.object({
diff --git a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml
index 798ead34114a6..7a5a994cff4df 100644
--- a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Builds Integration with the given input samples
       operationId: BuildIntegration
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Build Integration for the given input samples
       tags:
         - Build Integration API
diff --git a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.gen.ts
similarity index 78%
rename from x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts
rename to x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.gen.ts
index c78aa745671e9..0b2492e41ac12 100644
--- a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts
+++ b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.gen.ts
@@ -5,27 +5,36 @@
  * 2.0.
  */
 
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Integration Assistatnt Categorization API endpoint
+ *   version: 1
+ */
+
 import { z } from '@kbn/zod';
 
 import {
-  Connector,
-  DataStreamName,
-  LangSmithOptions,
   PackageName,
-  Pipeline,
+  DataStreamName,
   RawSamples,
+  Pipeline,
+  Connector,
   SamplesFormat,
-} from '../model/common_attributes';
-import { CategorizationAPIResponse } from '../model/response_schemas';
+  LangSmithOptions,
+} from '../model/common_attributes.gen';
+import { CategorizationAPIResponse } from '../model/response_schemas.gen';
 
 export type CategorizationRequestBody = z.infer<typeof CategorizationRequestBody>;
 export const CategorizationRequestBody = z.object({
   packageName: PackageName,
   dataStreamName: DataStreamName,
   rawSamples: RawSamples,
-  samplesFormat: SamplesFormat,
   currentPipeline: Pipeline,
   connectorId: Connector,
+  samplesFormat: SamplesFormat,
   langSmithOptions: LangSmithOptions.optional(),
 });
 export type CategorizationRequestBodyInput = z.input<typeof CategorizationRequestBody>;
diff --git a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml
index e548f4d816776..4a8a28abf3a7e 100644
--- a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Builds Categorization processors based on the samples
       operationId: Categorization
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Perform Categorization for the given ecs mappings.
       tags:
         - Categorization API
diff --git a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.test.ts b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.test.ts
index f7ef31f5fdb99..fc7f6f9736d36 100644
--- a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.test.ts
+++ b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.test.ts
@@ -6,7 +6,7 @@
  */
 
 import { expectParseSuccess } from '@kbn/zod-helpers';
-import { CategorizationRequestBody } from './categorization_route';
+import { CategorizationRequestBody } from './categorization_route.gen';
 import { getCategorizationRequestMock } from '../model/api_test.mock';
 
 describe('Categorization request schema', () => {
diff --git a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.gen.ts
similarity index 70%
rename from x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts
rename to x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.gen.ts
index e2a9255490f5e..18888108396f4 100644
--- a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts
+++ b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.gen.ts
@@ -5,10 +5,19 @@
  * 2.0.
  */
 
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Integration Assistatnt Check Pipeline API endpoint
+ *   version: 1
+ */
+
 import { z } from '@kbn/zod';
 
-import { Pipeline, RawSamples } from '../model/common_attributes';
-import { CheckPipelineAPIResponse } from '../model/response_schemas';
+import { RawSamples, Pipeline } from '../model/common_attributes.gen';
+import { CheckPipelineAPIResponse } from '../model/response_schemas.gen';
 
 export type CheckPipelineRequestBody = z.infer<typeof CheckPipelineRequestBody>;
 export const CheckPipelineRequestBody = z.object({
diff --git a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml
index 22785fc40bbf2..b7f20616007ea 100644
--- a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Checks if the pipeline is valid for the given samples
       operationId: CheckPipeline
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Check latest pipeline against the input samples.
       tags:
         - Check Pipeline API
diff --git a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.gen.ts
similarity index 91%
rename from x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts
rename to x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.gen.ts
index 58143ec7177d7..867f792405cbd 100644
--- a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts
+++ b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.gen.ts
@@ -20,13 +20,13 @@ import {
   PackageName,
   DataStreamName,
   RawSamples,
+  SamplesFormat,
   Mapping,
   Connector,
   LangSmithOptions,
-  SamplesFormat,
-} from '../model/common_attributes';
-import { ESProcessorItem } from '../model/processor_attributes';
-import { EcsMappingAPIResponse } from '../model/response_schemas';
+} from '../model/common_attributes.gen';
+import { ESProcessorItem } from '../model/processor_attributes.gen';
+import { EcsMappingAPIResponse } from '../model/response_schemas.gen';
 
 export type EcsMappingRequestBody = z.infer<typeof EcsMappingRequestBody>;
 export const EcsMappingRequestBody = z.object({
diff --git a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml
index 7026fc6d86f96..6bc125a74f52a 100644
--- a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Builds ECS Mapping based on the input samples
       operationId: EcsMapping
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Perform ECS mapping for the given input JSON samples
       tags:
         - ECS Mapping API
diff --git a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.test.ts b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.test.ts
index 770c3ff96f675..de4820633b825 100644
--- a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.test.ts
+++ b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.test.ts
@@ -6,7 +6,7 @@
  */
 
 import { expectParseSuccess } from '@kbn/zod-helpers';
-import { EcsMappingRequestBody } from './ecs_route';
+import { EcsMappingRequestBody } from './ecs_route.gen';
 import { getEcsMappingRequestMock } from '../model/api_test.mock';
 
 describe('Ecs Mapping request schema', () => {
diff --git a/x-pack/plugins/integration_assistant/common/api/model/api_test.mock.ts b/x-pack/plugins/integration_assistant/common/api/model/api_test.mock.ts
index 34ce49ae5b776..de29624cbb21a 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/api_test.mock.ts
+++ b/x-pack/plugins/integration_assistant/common/api/model/api_test.mock.ts
@@ -5,12 +5,12 @@
  * 2.0.
  */
 
-import type { AnalyzeLogsRequestBody } from '../analyze_logs/analyze_logs_route';
-import type { BuildIntegrationRequestBody } from '../build_integration/build_integration';
-import type { CategorizationRequestBody } from '../categorization/categorization_route';
-import type { EcsMappingRequestBody } from '../ecs/ecs_route';
-import type { RelatedRequestBody } from '../related/related_route';
-import type { DataStream, Integration, Pipeline } from './common_attributes';
+import type { AnalyzeLogsRequestBody } from '../analyze_logs/analyze_logs_route.gen';
+import type { BuildIntegrationRequestBody } from '../build_integration/build_integration.gen';
+import type { CategorizationRequestBody } from '../categorization/categorization_route.gen';
+import type { EcsMappingRequestBody } from '../ecs/ecs_route.gen';
+import type { RelatedRequestBody } from '../related/related_route.gen';
+import type { DataStream, Integration, Pipeline } from './common_attributes.gen';
 
 const rawSamples = ['{"test1": "test1"}'];
 
diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts
similarity index 94%
rename from x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts
rename to x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts
index 964aa9386a87f..59fe7d461698f 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts
+++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.gen.ts
@@ -5,9 +5,18 @@
  * 2.0.
  */
 
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Common Rule Attributes
+ *   version: not applicable
+ */
+
 import { z } from '@kbn/zod';
 
-import { ESProcessorItem } from './processor_attributes';
+import { ESProcessorItem } from './processor_attributes.gen';
 
 /**
  * Package name for the integration to be built.
diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml
index 4c4810b22f9be..0af01834970c7 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml
@@ -4,7 +4,7 @@ info:
   version: "not applicable"
 paths: {}
 components:
-  x-codegen-enabled: false
+  x-codegen-enabled: true
   schemas:
     PackageName:
       type: string
diff --git a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.gen.ts b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.gen.ts
new file mode 100644
index 0000000000000..5916bcac4d839
--- /dev/null
+++ b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.gen.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Common Rule Attributes
+ *   version: not applicable
+ */
+
+import type { ZodTypeDef } from '@kbn/zod';
+import { z } from '@kbn/zod';
+
+/**
+ * Processor options for the Elasticsearch processor.
+ */
+export interface ESProcessorOptions {
+  /**
+   * An array of items to execute if the processor fails.
+   */
+  on_failure?: ESProcessorItem[];
+  /**
+   * If true, the processor continues to the next processor if the current processor fails.
+   */
+  ignore_failure?: boolean;
+  /**
+   * If true, the processor continues to the next processor if the field is missing.
+   */
+  ignore_missing?: boolean;
+  /**
+   * Conditionally execute the processor.
+   */
+  if?: string;
+  /**
+   * A tag to assign to the document after processing.
+   */
+  tag?: string;
+  [key: string]: unknown;
+}
+export interface ESProcessorOptionsInput {
+  /**
+   * An array of items to execute if the processor fails.
+   */
+  on_failure?: ESProcessorItemInput[];
+  /**
+   * If true, the processor continues to the next processor if the current processor fails.
+   */
+  ignore_failure?: boolean;
+  /**
+   * If true, the processor continues to the next processor if the field is missing.
+   */
+  ignore_missing?: boolean;
+  /**
+   * Conditionally execute the processor.
+   */
+  if?: string;
+  /**
+   * A tag to assign to the document after processing.
+   */
+  tag?: string;
+  [key: string]: unknown;
+}
+export const ESProcessorOptions: z.ZodType<
+  ESProcessorOptions,
+  ZodTypeDef,
+  ESProcessorOptionsInput
+> = z
+  .object({
+    /**
+     * An array of items to execute if the processor fails.
+     */
+    on_failure: z.array(z.lazy(() => ESProcessorItem)).optional(),
+    /**
+     * If true, the processor continues to the next processor if the current processor fails.
+     */
+    ignore_failure: z.boolean().optional(),
+    /**
+     * If true, the processor continues to the next processor if the field is missing.
+     */
+    ignore_missing: z.boolean().optional(),
+    /**
+     * Conditionally execute the processor.
+     */
+    if: z.string().optional(),
+    /**
+     * A tag to assign to the document after processing.
+     */
+    tag: z.string().optional(),
+  })
+  .catchall(z.unknown());
+
+/**
+ * Processor item for the Elasticsearch processor.
+ */
+export interface ESProcessorItem {
+  [key: string]: ESProcessorOptions;
+}
+export interface ESProcessorItemInput {
+  [key: string]: ESProcessorOptionsInput;
+}
+export const ESProcessorItem: z.ZodType<ESProcessorItem, ZodTypeDef, ESProcessorItemInput> = z
+  .object({})
+  .catchall(z.lazy(() => ESProcessorOptions));
diff --git a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml
index a0c930361737a..837ff5525df88 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml
@@ -4,7 +4,7 @@ info:
   version: "not applicable"
 paths: {}
 components:
-  x-codegen-enabled: false
+  x-codegen-enabled: true
   schemas:
     ESProcessorItem:
       type: object
diff --git a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts
deleted file mode 100644
index 4bbebb6b4686c..0000000000000
--- a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { z } from '@kbn/zod';
-
-/**
- * Processor item for the Elasticsearch processor.
- */
-export type ESProcessorItem = Record<string, ESProcessorOptions>;
-export const ESProcessorItem: z.ZodType<ESProcessorItem> = z
-  .object({})
-  .catchall(z.lazy(() => ESProcessorOptions));
-
-/**
- * Processor options for the Elasticsearch processor.
- */
-export interface ESProcessorOptions {
-  on_failure?: ESProcessorItem[];
-  ignore_failure?: boolean;
-  ignore_missing?: boolean;
-  if?: string;
-  tag?: string;
-  [key: string]: unknown;
-}
-export const ESProcessorOptions = z
-  .object({
-    /**
-     * An array of items to execute if the processor fails.
-     */
-    on_failure: z.array(ESProcessorItem).optional(),
-    /**
-     * If true, the processor continues to the next processor if the current processor fails.
-     */
-    ignore_failure: z.boolean().optional(),
-    /**
-     * If true, the processor continues to the next processor if the field is missing.
-     */
-    ignore_missing: z.boolean().optional(),
-    /**
-     * Conditionally execute the processor.
-     */
-    if: z.string().optional(),
-    /**
-     * A tag to assign to the document after processing.
-     */
-    tag: z.string().optional(),
-  })
-  .catchall(z.unknown());
diff --git a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.gen.ts
similarity index 91%
rename from x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts
rename to x-pack/plugins/integration_assistant/common/api/model/response_schemas.gen.ts
index c496aa8493723..acb4954c21b90 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts
+++ b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.gen.ts
@@ -16,8 +16,8 @@
 
 import { z } from '@kbn/zod';
 
-import { Docs, Mapping, Pipeline, SamplesFormat } from './common_attributes';
-import { ESProcessorItem } from './processor_attributes';
+import { Mapping, Pipeline, Docs, SamplesFormat } from './common_attributes.gen';
+import { ESProcessorItem } from './processor_attributes.gen';
 
 export type EcsMappingAPIResponse = z.infer<typeof EcsMappingAPIResponse>;
 export const EcsMappingAPIResponse = z.object({
@@ -52,9 +52,9 @@ export const CheckPipelineAPIResponse = z.object({
 
 export type AnalyzeLogsAPIResponse = z.infer<typeof AnalyzeLogsAPIResponse>;
 export const AnalyzeLogsAPIResponse = z.object({
+  additionalProcessors: z.array(ESProcessorItem).optional(),
   results: z.object({
     samplesFormat: SamplesFormat,
     parsedSamples: z.array(z.string()),
   }),
-  additionalProcessors: z.array(ESProcessorItem).optional(),
 });
diff --git a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml
index 72f853822b09e..c504ad8b17d16 100644
--- a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml
@@ -4,7 +4,7 @@ info:
   version: "not applicable"
 paths: {}
 components:
-  x-codegen-enabled: false
+  x-codegen-enabled: true
   schemas:
     EcsMappingAPIResponse:
       type: object
diff --git a/x-pack/plugins/integration_assistant/common/api/related/related_route.ts b/x-pack/plugins/integration_assistant/common/api/related/related_route.gen.ts
similarity index 73%
rename from x-pack/plugins/integration_assistant/common/api/related/related_route.ts
rename to x-pack/plugins/integration_assistant/common/api/related/related_route.gen.ts
index 14796dc5350ba..eceb01679a442 100644
--- a/x-pack/plugins/integration_assistant/common/api/related/related_route.ts
+++ b/x-pack/plugins/integration_assistant/common/api/related/related_route.gen.ts
@@ -5,27 +5,36 @@
  * 2.0.
  */
 
+/*
+ * NOTICE: Do not edit this file manually.
+ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
+ *
+ * info:
+ *   title: Integration Assistatnt Related API endpoint
+ *   version: 1
+ */
+
 import { z } from '@kbn/zod';
 
 import {
-  Connector,
-  DataStreamName,
-  LangSmithOptions,
   PackageName,
-  Pipeline,
+  DataStreamName,
   RawSamples,
+  Pipeline,
+  Connector,
   SamplesFormat,
-} from '../model/common_attributes';
-import { RelatedAPIResponse } from '../model/response_schemas';
+  LangSmithOptions,
+} from '../model/common_attributes.gen';
+import { RelatedAPIResponse } from '../model/response_schemas.gen';
 
 export type RelatedRequestBody = z.infer<typeof RelatedRequestBody>;
 export const RelatedRequestBody = z.object({
   packageName: PackageName,
   dataStreamName: DataStreamName,
   rawSamples: RawSamples,
-  samplesFormat: SamplesFormat,
   currentPipeline: Pipeline,
   connectorId: Connector,
+  samplesFormat: SamplesFormat,
   langSmithOptions: LangSmithOptions.optional(),
 });
 export type RelatedRequestBodyInput = z.input<typeof RelatedRequestBody>;
diff --git a/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml
index 71fee7e616709..9ca2f6ab7bae0 100644
--- a/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml
+++ b/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml
@@ -7,7 +7,7 @@ paths:
     post:
       summary: Builds related.* fields for integration with the given input samples
       operationId: Related
-      x-codegen-enabled: false
+      x-codegen-enabled: true
       description: Add Related mappings for the given samples.
       tags:
         - Related API
diff --git a/x-pack/plugins/integration_assistant/common/api/related/related_route.test.ts b/x-pack/plugins/integration_assistant/common/api/related/related_route.test.ts
index 8f69c13303056..fd6a81a61a1ad 100644
--- a/x-pack/plugins/integration_assistant/common/api/related/related_route.test.ts
+++ b/x-pack/plugins/integration_assistant/common/api/related/related_route.test.ts
@@ -6,7 +6,7 @@
  */
 
 import { expectParseSuccess } from '@kbn/zod-helpers';
-import { RelatedRequestBody } from './related_route';
+import { RelatedRequestBody } from './related_route.gen';
 import { getRelatedRequestMock } from '../model/api_test.mock';
 
 describe('Related request schema', () => {
diff --git a/x-pack/plugins/integration_assistant/common/index.ts b/x-pack/plugins/integration_assistant/common/index.ts
index 5310fa67c8cac..21ee814655e10 100644
--- a/x-pack/plugins/integration_assistant/common/index.ts
+++ b/x-pack/plugins/integration_assistant/common/index.ts
@@ -4,18 +4,21 @@
  * 2.0; you may not use this file except in compliance with the Elastic License
  * 2.0.
  */
-export { BuildIntegrationRequestBody } from './api/build_integration/build_integration';
+export { BuildIntegrationRequestBody } from './api/build_integration/build_integration.gen';
 export {
   CategorizationRequestBody,
   CategorizationResponse,
-} from './api/categorization/categorization_route';
+} from './api/categorization/categorization_route.gen';
 export {
   CheckPipelineRequestBody,
   CheckPipelineResponse,
-} from './api/check_pipeline/check_pipeline';
-export { EcsMappingRequestBody, EcsMappingResponse } from './api/ecs/ecs_route';
-export { RelatedRequestBody, RelatedResponse } from './api/related/related_route';
-export { AnalyzeLogsRequestBody, AnalyzeLogsResponse } from './api/analyze_logs/analyze_logs_route';
+} from './api/check_pipeline/check_pipeline.gen';
+export { EcsMappingRequestBody, EcsMappingResponse } from './api/ecs/ecs_route.gen';
+export { RelatedRequestBody, RelatedResponse } from './api/related/related_route.gen';
+export {
+  AnalyzeLogsRequestBody,
+  AnalyzeLogsResponse,
+} from './api/analyze_logs/analyze_logs_route.gen';
 
 export type {
   DataStream,
@@ -24,8 +27,10 @@ export type {
   Pipeline,
   Docs,
   SamplesFormat,
-} from './api/model/common_attributes';
-export type { ESProcessorItem } from './api/model/processor_attributes';
+  LangSmithOptions,
+} from './api/model/common_attributes.gen';
+export { SamplesFormatName } from './api/model/common_attributes.gen';
+export type { ESProcessorItem } from './api/model/processor_attributes.gen';
 
 export {
   CATEGORIZATION_GRAPH_PATH,
diff --git a/x-pack/plugins/integration_assistant/public/common/lib/lang_smith.ts b/x-pack/plugins/integration_assistant/public/common/lib/lang_smith.ts
index 7234870439930..a8c4f258e20da 100644
--- a/x-pack/plugins/integration_assistant/public/common/lib/lang_smith.ts
+++ b/x-pack/plugins/integration_assistant/public/common/lib/lang_smith.ts
@@ -11,7 +11,7 @@ import {
 } from '@kbn/elastic-assistant/impl/assistant_context/constants';
 import { Storage } from '@kbn/kibana-utils-plugin/public';
 import type { TraceOptions } from '@kbn/elastic-assistant/impl/assistant/types';
-import type { LangSmithOptions } from '../../../common/api/model/common_attributes';
+import type { LangSmithOptions } from '../../../common';
 
 const sessionStorage = new Storage(window.sessionStorage);
 
diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts
index 242f6955700e8..e2d91c094d520 100644
--- a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts
+++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts
@@ -8,10 +8,9 @@
 import { safeLoad } from 'js-yaml';
 import { Environment, FileSystemLoader } from 'nunjucks';
 import { join as joinPath } from 'path';
-import { Pipeline } from '../../../common/api/model/common_attributes';
+import { Pipeline, ESProcessorItem } from '../../../common';
 import type { EcsMappingState } from '../../types';
 import { ECS_TYPES } from './constants';
-import { ESProcessorItem } from '../../../common/api/model/processor_attributes';
 import { deepCopy } from '../../util/util';
 
 interface IngestPipeline {
diff --git a/x-pack/plugins/integration_assistant/server/util/samples.test.ts b/x-pack/plugins/integration_assistant/server/util/samples.test.ts
new file mode 100644
index 0000000000000..131135e842334
--- /dev/null
+++ b/x-pack/plugins/integration_assistant/server/util/samples.test.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { merge } from './samples';
+
+describe('merge', () => {
+  it('Should return source if target is empty', async () => {
+    const target = {};
+    const source = { target: 'user.name', confidence: 0.9, type: 'string', date_formats: [] };
+
+    const result = merge(target, source);
+
+    expect(result).toEqual(source);
+  });
+
+  it('Should return target if source is empty', async () => {
+    const target = { hostname: '0.0.0.0', 'teleport.internal/resource-id': '1234' };
+    const source = {};
+
+    const result = merge(target, source);
+
+    expect(result).toEqual(target);
+  });
+
+  it('Should return one result', async () => {
+    const target = {
+      aaa: {
+        ei: 0,
+        event: 'cert.create',
+        uid: '1234',
+        cluster_name: 'cluster.com',
+        identity: { user: 'teleport-admin' },
+        server_labels: { hostname: 'some-hostname' },
+      },
+    };
+    const source = {
+      aaa: {
+        ei: 0,
+        event: 'session.start',
+        uid: '4567',
+        cluster_name: 'cluster.com',
+        user: 'teleport-admin',
+        server_labels: { hostname: 'some-other-hostname', id: '1234' },
+      },
+    };
+
+    const result = merge(target, source);
+
+    expect(result).toEqual({
+      aaa: {
+        ei: 0,
+        event: 'cert.create',
+        uid: '1234',
+        cluster_name: 'cluster.com',
+        identity: { user: 'teleport-admin' },
+        server_labels: { hostname: 'some-hostname', id: '1234' },
+        user: 'teleport-admin',
+      },
+    });
+  });
+
+  it('Should not merge built-in properties of neither target nor source', async () => {
+    const target = {
+      __proto__: 'some properties',
+      constructor: 'some other properties',
+      hostname: '0.0.0.0',
+      'teleport.internal/resource-id': '1234',
+    };
+    const source = {
+      __proto__: 'some properties of source',
+      constructor: 'some other properties of source',
+    };
+
+    const result = merge(target, source);
+
+    expect(result).toEqual({ hostname: '0.0.0.0', 'teleport.internal/resource-id': '1234' });
+  });
+
+  it('Should keep source object if it collides with target key that is not an object', async () => {
+    const target = {
+      hostname: '',
+      'teleport.internal/resource-id': '1234',
+      date_formats: 'format',
+    };
+    const source = {
+      target: 'user.name',
+      confidence: 0.9,
+      type: 'string',
+      date_formats: { key: 'value' },
+    };
+
+    const result = merge(target, source);
+
+    expect(result).toEqual({
+      'teleport.internal/resource-id': '1234',
+      target: 'user.name',
+      confidence: 0.9,
+      type: 'string',
+      hostname: '',
+      date_formats: { key: 'value' },
+    });
+  });
+
+  it('Should copy array into the result', async () => {
+    const target = { date_formats: ['a', 'b'] };
+    const source = { target: 'user.name', confidence: 0.9, type: 'string', date_formats: ['c'] };
+
+    const result = merge(target, source);
+
+    expect(result).toEqual(source);
+  });
+});
diff --git a/x-pack/plugins/integration_assistant/server/util/samples.ts b/x-pack/plugins/integration_assistant/server/util/samples.ts
index 766856f644a86..745c3d214095f 100644
--- a/x-pack/plugins/integration_assistant/server/util/samples.ts
+++ b/x-pack/plugins/integration_assistant/server/util/samples.ts
@@ -163,39 +163,61 @@ export function generateFields(mergedDocs: string): string {
   return yaml.safeDump(fieldsStructure, { sortKeys: false });
 }
 
-function isEmptyValue(value: unknown): boolean {
-  return (
-    value === null ||
-    value === undefined ||
-    (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) ||
-    (Array.isArray(value) && value.length === 0)
-  );
-}
-
 export function merge(
   target: Record<string, any>,
   source: Record<string, any>
 ): Record<string, unknown> {
+  const filteredTarget = filterOwnProperties(target);
   for (const [key, sourceValue] of Object.entries(source)) {
-    const targetValue = target[key];
-    if (Array.isArray(sourceValue)) {
-      // Directly assign arrays
-      target[key] = sourceValue;
-    } else if (
-      typeof sourceValue === 'object' &&
-      sourceValue !== null &&
-      !Array.isArray(targetValue)
-    ) {
-      if (typeof targetValue !== 'object' || isEmptyValue(targetValue)) {
-        target[key] = merge({}, sourceValue);
-      } else {
-        target[key] = merge(targetValue, sourceValue);
+    if (!isBuiltInProperties(key, source)) {
+      const targetValue = filteredTarget[key];
+      if (Array.isArray(sourceValue)) {
+        // Directly assign arrays
+        filteredTarget[key] = sourceValue;
+      } else if (isObject(sourceValue) && !Array.isArray(targetValue)) {
+        if (!isObject(targetValue) || isEmptyValue(targetValue)) {
+          filteredTarget[key] = merge({}, sourceValue);
+        } else {
+          filteredTarget[key] = merge(targetValue, sourceValue);
+        }
+      } else if (
+        !(key in filteredTarget) ||
+        (isEmptyValue(targetValue) && !isEmptyValue(sourceValue))
+      ) {
+        filteredTarget[key] = sourceValue;
       }
-    } else if (!(key in target) || (isEmptyValue(targetValue) && !isEmptyValue(sourceValue))) {
-      target[key] = sourceValue;
     }
   }
-  return target;
+  return filteredTarget;
+}
+
+function isEmptyValue(value: unknown): boolean {
+  if (value == null) return true;
+  if (isObject(value)) {
+    if (Array.isArray(value)) return value.length === 0;
+    return value && Object.keys(value).length === 0;
+  }
+  return false;
+}
+
+function isObject(value: any): boolean {
+  return typeof value === 'object' && value !== null;
+}
+
+function isBuiltInProperties(key: string, obj: Record<string, any>): boolean {
+  return key === 'constructor' || !Object.prototype.hasOwnProperty.call(obj, key);
+}
+
+function filterOwnProperties(obj: Record<string, any>): Record<string, any> {
+  const ownProps: Record<string, any> = {};
+
+  for (const key of Object.getOwnPropertyNames(obj)) {
+    if (!isBuiltInProperties(key, obj)) {
+      ownProps[key] = (obj as any)[key];
+    }
+  }
+
+  return ownProps;
 }
 
 export function mergeSamples(objects: any[]): string {
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx
index 0f370aabe1731..2e91865083b8c 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/error_group_details/error_sampler/error_sample_contextual_insight.tsx
@@ -38,19 +38,19 @@ export function ErrorSampleContextualInsight({
       instructions: `I'm an SRE. I am looking at an exception and trying to understand what it means.
 
       Your task is to describe what the error means and what it could be caused by.
-      
+
       The error occurred on a service called ${serviceName}, which is a ${runtimeName} service written in ${languageName}. The
       runtime version is ${runtimeVersion}.
-      
+
       The request it occurred for is called ${transactionName}.
-      
+
       ${
         logStacktrace
           ? `The log stacktrace:
       ${logStacktrace}`
           : ''
       }
-      
+
       ${
         exceptionStacktrace
           ? `The exception stacktrace:
diff --git a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_dataset_info.ts b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_dataset_info.ts
index 72fb4c8c7d200..a27bdcc3dc813 100644
--- a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_dataset_info.ts
+++ b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_dataset_info.ts
@@ -164,6 +164,7 @@ export function registerGetApmDatasetInfoFunction({
           `,
         },
       };
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_downstream_dependencies.ts b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_downstream_dependencies.ts
index 9b80d694151ff..8a95fe9c89869 100644
--- a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_downstream_dependencies.ts
+++ b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_downstream_dependencies.ts
@@ -22,16 +22,16 @@ export function registerGetApmDownstreamDependenciesFunction({
   registerFunction(
     {
       name: 'get_apm_downstream_dependencies',
-      description: `Get the downstream dependencies (services or uninstrumented backends) for a 
-      service. This allows you to map the downstream dependency name to a service, by 
-      returning both span.destination.service.resource and service.name. Use this to 
+      description: `Get the downstream dependencies (services or uninstrumented backends) for a
+      service. This allows you to map the downstream dependency name to a service, by
+      returning both span.destination.service.resource and service.name. Use this to
       drilldown further if needed.`,
       descriptionForUser: i18n.translate(
         'xpack.apm.observabilityAiAssistant.functions.registerGetApmDownstreamDependencies.descriptionForUser',
         {
-          defaultMessage: `Get the downstream dependencies (services or uninstrumented backends) for a 
-      service. This allows you to map the dowstream dependency name to a service, by 
-      returning both span.destination.service.resource and service.name. Use this to 
+          defaultMessage: `Get the downstream dependencies (services or uninstrumented backends) for a
+      service. This allows you to map the dowstream dependency name to a service, by
+      returning both span.destination.service.resource and service.name. Use this to
       drilldown further if needed.`,
         }
       ),
@@ -67,6 +67,7 @@ export function registerGetApmDownstreamDependenciesFunction({
           randomSampler,
         }),
       };
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_services_list.ts b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_services_list.ts
index b24c24425b413..f768c30d8af21 100644
--- a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_services_list.ts
+++ b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_services_list.ts
@@ -84,6 +84,7 @@ export function registerGetApmServicesListFunction({
           arguments: args,
         }),
       };
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_timeseries.ts b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_timeseries.ts
index 63bdbd422c658..dc9152d268adb 100644
--- a/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_timeseries.ts
+++ b/x-pack/plugins/observability_solution/apm/server/assistant_functions/get_apm_timeseries.ts
@@ -138,7 +138,8 @@ export function registerGetApmTimeseriesFunction({
         content: timeseries.map((series): Omit<ApmTimeseries, 'data'> => omit(series, 'data')),
         data: timeseries,
       };
-    }
+    },
+    ['observability']
   );
 }
 
diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/process_row.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/process_row.tsx
index 17657e9366d7d..93d6b6e8efd91 100644
--- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/process_row.tsx
+++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/process_row.tsx
@@ -48,7 +48,7 @@ export const ContextualInsightProcessRow = ({ command }: { command: string }) =>
       with the arguments to the process you should then explain its arguments and how they influence the behaviour
       of the process. If I do not provide any arguments then explain the behaviour of the process when no arguments are
       provided.
-      
+
       Here is an example with arguments.
       Process: metricbeat -c /etc/metricbeat.yml -d autodiscover,kafka -e -system.hostfs=/hostfs
       Explanation: Metricbeat is part of the Elastic Stack. It is a lightweight shipper that you can install on your
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts
index bd786e9ba3c75..8bdb76388cb56 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts
@@ -8,6 +8,7 @@ import type { JSONSchema7TypeName } from 'json-schema';
 import type { Observable } from 'rxjs';
 import { ChatCompletionChunkEvent, MessageAddEvent } from '../conversation_complete';
 import { FunctionVisibility } from './function_visibility';
+import { AssistantScope } from '../types';
 export { FunctionVisibility };
 
 type JSONSchemaOrPrimitive = CompatibleJSONSchema | string | number | boolean;
@@ -41,6 +42,7 @@ export interface FunctionDefinition<TParameters extends CompatibleJSONSchema = a
   visibility?: FunctionVisibility;
   descriptionForUser?: string;
   parameters?: TParameters;
+  scopes?: AssistantScope[];
 }
 
 export type FunctionRegistry = Map<string, FunctionDefinition>;
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts
index 7f8a2be739dd1..2c65ee6dc06b2 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts
@@ -157,3 +157,5 @@ export interface ObservabilityAIAssistantScreenContext {
   actions?: Array<ScreenContextActionDefinition<any>>;
   starterPrompts?: StarterPrompt[];
 }
+
+export type AssistantScope = 'observability' | 'search' | 'all';
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
index acd5e2710c9dd..97dac18d4f733 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/insight/insight.tsx
@@ -56,6 +56,7 @@ function ChatContent({
 }) {
   const service = useObservabilityAIAssistant();
   const chatService = useObservabilityAIAssistantChatService();
+  const { scope } = service;
 
   const initialMessagesRef = useRef(initialMessages);
 
@@ -68,6 +69,7 @@ function ChatContent({
     initialMessages,
     persist: false,
     disableFunctions: true,
+    scope,
   });
 
   const lastAssistantResponse = getLastMessageOfType(
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.test.ts
index 1f36b49175eea..7a88e4bd5486b 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.test.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.test.ts
@@ -80,6 +80,7 @@ describe('useChat', () => {
           service: {
             getScreenContexts: () => [],
           } as unknown as ObservabilityAIAssistantService,
+          scope: 'observability',
         } as UseChatProps,
       });
     });
@@ -109,6 +110,7 @@ describe('useChat', () => {
           service: {
             getScreenContexts: () => [],
           } as unknown as ObservabilityAIAssistantService,
+          scope: 'observability',
         } as UseChatProps,
       });
 
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.ts
index 712b102c36f85..b51b33797e285 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_chat.ts
@@ -10,6 +10,7 @@ import { merge } from 'lodash';
 import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 import { AbortError } from '@kbn/kibana-utils-plugin/common';
 import type { NotificationsStart } from '@kbn/core/public';
+import { AssistantScope } from '../../common/types';
 import {
   MessageRole,
   type Message,
@@ -55,6 +56,7 @@ interface UseChatPropsWithoutContext {
   disableFunctions?: boolean;
   onConversationUpdate?: (event: ConversationCreateEvent | ConversationUpdateEvent) => void;
   onChatComplete?: (messages: Message[]) => void;
+  scope: AssistantScope;
 }
 
 export type UseChatProps = Omit<UseChatPropsWithoutContext, 'notifications'>;
@@ -70,6 +72,7 @@ function useChatWithoutContext({
   onChatComplete,
   persist,
   disableFunctions,
+  scope,
 }: UseChatPropsWithoutContext): UseChatResult {
   const [chatState, setChatState] = useState(ChatState.Ready);
   const systemMessage = useMemo(() => {
@@ -161,6 +164,7 @@ function useChatWithoutContext({
         disableFunctions: disableFunctions ?? false,
         signal: abortControllerRef.current.signal,
         conversationId,
+        scope,
       });
 
       function getPendingMessages() {
@@ -259,6 +263,7 @@ function useChatWithoutContext({
       disableFunctions,
       service,
       systemMessage,
+      scope,
     ]
   );
 
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx
index b5d85be11dfe2..349f044206267 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx
@@ -60,6 +60,7 @@ export const mockService: ObservabilityAIAssistantService = {
     predefinedConversation$: new Observable(),
   },
   navigate: async () => of(),
+  scope: 'all',
 };
 
 function createSetupContract(): ObservabilityAIAssistantPublicSetup {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/plugin.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/plugin.tsx
index fd2a60dcdfc3a..05c4bc93cd2ae 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/plugin.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/plugin.tsx
@@ -65,6 +65,7 @@ export class ObservabilityAIAssistantPlugin
         coreStart.application.capabilities.observabilityAIAssistant[
           aiAssistantCapabilities.show
         ] === true,
+      scope: 'observability',
     }));
 
     const withProviders = <P extends {}, R = {}>(
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts
index 59b3e7c9087d9..9d8338f2d3892 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts
@@ -102,6 +102,7 @@ describe('complete', () => {
         disableFunctions: false,
         signal: new AbortController().signal,
         ...params,
+        scope: 'all',
       },
       requestCallback
     );
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts
index 85f5f0397ff31..6e03683b44064 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts
@@ -43,6 +43,7 @@ export function complete(
     disableFunctions,
     signal,
     instructions,
+    scope,
   }: {
     client: Pick<ObservabilityAIAssistantChatService, 'chat' | 'complete'>;
     getScreenContexts: () => ObservabilityAIAssistantScreenContext[];
@@ -65,6 +66,7 @@ export function complete(
           screenContexts,
           conversationId,
           instructions,
+          scope,
         },
       },
     }).pipe(shareReplay());
@@ -131,6 +133,7 @@ export function complete(
               persist,
               disableFunctions,
               instructions,
+              scope,
             },
             requestCallback
           ).subscribe(subscriber);
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts
index 6d0332602c869..c495d33301882 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts
@@ -70,6 +70,7 @@ describe('createChatService', () => {
       apiClient: clientSpy,
       registrations: [],
       signal: new AbortController().signal,
+      scope: 'observability',
     });
   });
 
@@ -79,7 +80,12 @@ describe('createChatService', () => {
 
   describe('chat', () => {
     function chat({ signal }: { signal: AbortSignal } = { signal: new AbortController().signal }) {
-      return service.chat('my_test', { signal, messages: [], connectorId: '' });
+      return service.chat('my_test', {
+        signal,
+        messages: [],
+        connectorId: '',
+        scope: 'observability',
+      });
     }
 
     it('correctly parses a stream of JSON lines', async () => {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts
index fc8c06ef55f51..e1829308fe69a 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts
@@ -23,6 +23,7 @@ import {
   throwError,
   timestamp,
 } from 'rxjs';
+import { AssistantScope } from '../../common/types';
 import { ChatCompletionChunkEvent, Message, MessageRole } from '../../common';
 import {
   StreamingChatResponseEventType,
@@ -137,19 +138,26 @@ export async function createChatService({
   signal: setupAbortSignal,
   registrations,
   apiClient,
+  scope,
 }: {
   analytics: AnalyticsServiceStart;
   signal: AbortSignal;
   registrations: ChatRegistrationRenderFunction[];
   apiClient: ObservabilityAIAssistantAPIClient;
+  scope: AssistantScope;
 }): Promise<ObservabilityAIAssistantChatService> {
   const functionRegistry: FunctionRegistry = new Map();
 
   const renderFunctionRegistry: Map<string, RenderFunction<unknown, FunctionResponse>> = new Map();
 
   const [{ functionDefinitions, systemMessage }] = await Promise.all([
-    apiClient('GET /internal/observability_ai_assistant/functions', {
+    apiClient('GET /internal/observability_ai_assistant/{scope}/functions', {
       signal: setupAbortSignal,
+      params: {
+        path: {
+          scope,
+        },
+      },
     }),
     ...registrations.map((registration) => {
       return registration({
@@ -196,6 +204,7 @@ export async function createChatService({
             connectorId,
             functionCall,
             functions: functions ?? [],
+            scope,
           },
         },
         signal,
@@ -228,6 +237,7 @@ export async function createChatService({
           signal,
           client,
           instructions,
+          scope,
         },
         ({ params }) => {
           return callStreamingApi('POST /internal/observability_ai_assistant/chat/complete', {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_service.ts
index 7232078d2efe8..ae95cb26148e9 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_service.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_service.ts
@@ -8,7 +8,11 @@
 import type { AnalyticsServiceStart, CoreStart } from '@kbn/core/public';
 import { compact, without } from 'lodash';
 import { BehaviorSubject, debounceTime, filter, lastValueFrom, of, Subject, take } from 'rxjs';
-import type { Message, ObservabilityAIAssistantScreenContext } from '../../common/types';
+import type {
+  AssistantScope,
+  Message,
+  ObservabilityAIAssistantScreenContext,
+} from '../../common/types';
 import { createFunctionRequestMessage } from '../../common/utils/create_function_request_message';
 import { createFunctionResponseMessage } from '../../common/utils/create_function_response_message';
 import { createCallObservabilityAIAssistantAPI } from '../api';
@@ -19,10 +23,12 @@ export function createService({
   analytics,
   coreStart,
   enabled,
+  scope,
 }: {
   analytics: AnalyticsServiceStart;
   coreStart: CoreStart;
   enabled: boolean;
+  scope: AssistantScope;
 }): ObservabilityAIAssistantService {
   const apiClient = createCallObservabilityAIAssistantAPI(coreStart);
 
@@ -42,7 +48,7 @@ export function createService({
     },
     start: async ({ signal }) => {
       const mod = await import('./create_chat_service');
-      return await mod.createChatService({ analytics, apiClient, signal, registrations });
+      return await mod.createChatService({ analytics, apiClient, signal, registrations, scope });
     },
     callApi: apiClient,
     getScreenContexts() {
@@ -89,5 +95,6 @@ export function createService({
       },
       predefinedConversation$: predefinedConversation$.asObservable(),
     },
+    scope,
   };
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx
index d3b52f6803621..f48d7868def87 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx
@@ -52,4 +52,5 @@ export const createStorybookService = (): ObservabilityAIAssistantService => ({
     predefinedConversation$: new Observable(),
   },
   navigate: async () => of(),
+  scope: 'observability',
 });
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts
index 71a8a7e402748..8b265d433f515 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts
@@ -19,6 +19,7 @@ import type {
   ObservabilityAIAssistantScreenContext,
   PendingMessage,
   AdHocInstruction,
+  AssistantScope,
 } from '../common/types';
 import type { TelemetryEventTypeWithPayload } from './analytics';
 import type { ObservabilityAIAssistantAPIClient } from './api';
@@ -52,6 +53,7 @@ export interface ObservabilityAIAssistantChatService {
       functions?: Array<Pick<FunctionDefinition, 'name' | 'description' | 'parameters'>>;
       functionCall?: string;
       signal: AbortSignal;
+      scope: AssistantScope;
     }
   ) => Observable<ChatCompletionChunkEvent>;
   complete: (options: {
@@ -67,8 +69,13 @@ export interface ObservabilityAIAssistantChatService {
         };
     signal: AbortSignal;
     instructions?: AdHocInstruction[];
+    scope: AssistantScope;
   }) => Observable<StreamingChatResponseEventWithoutError>;
-  getFunctions: (options?: { contexts?: string[]; filter?: string }) => FunctionDefinition[];
+  getFunctions: (options?: {
+    contexts?: string[];
+    filter?: string;
+    scope: AssistantScope;
+  }) => FunctionDefinition[];
   hasFunction: (name: string) => boolean;
   getSystemMessage: () => Message;
   hasRenderFunction: (name: string) => boolean;
@@ -76,7 +83,8 @@ export interface ObservabilityAIAssistantChatService {
     name: string,
     args: string | undefined,
     response: { data?: string; content?: string },
-    onActionClick: ChatActionClickHandler
+    onActionClick: ChatActionClickHandler,
+    scope?: AssistantScope
   ) => React.ReactNode;
 }
 
@@ -94,6 +102,7 @@ export interface ObservabilityAIAssistantService {
   getScreenContexts: () => ObservabilityAIAssistantScreenContext[];
   conversations: ObservabilityAIAssistantConversationService;
   navigate: (callback: () => void) => Promise<Observable<MessageAddEvent>>;
+  scope: AssistantScope;
 }
 
 export type RenderFunction<TArguments, TResponse extends FunctionResponse> = (options: {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts
index fd57968617187..61448d297e4d3 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts
@@ -115,6 +115,7 @@ export function registerContextFunction({
             subscriber.complete();
           });
       });
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/elasticsearch.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/elasticsearch.ts
index 6008b53dd42c5..71a0cfa4bbde0 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/elasticsearch.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/elasticsearch.ts
@@ -48,6 +48,7 @@ export function registerElasticsearchFunction({
       });
 
       return { content: { response } };
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/execute_connector.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/execute_connector.ts
index 0088e35a6f6af..bfe04cb56e8cf 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/execute_connector.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/execute_connector.ts
@@ -38,6 +38,7 @@ export function registerExecuteConnectorFunction({
       ).getActionsClientWithRequest(resources.request);
       const content = await actionsClient.execute({ actionId: id, params });
       return { content };
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts
index 57cac3a4e0c0f..9b20d364ef7d9 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts
@@ -94,6 +94,7 @@ export function registerGetDatasetInfoFunction({
           stats: relevantFieldNames.stats,
         },
       };
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts
index 5b16b79bd9980..a5333ee1a7ffc 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts
@@ -38,7 +38,8 @@ export const registerFunctions: RegistrationCallback = async ({
 
   const isServerless = !!resources.plugins.serverless;
 
-  functions.registerInstruction(`You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.
+  functions.registerInstruction({
+    instruction: `You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.
 
   It's very important to not assume what the user is meaning. Ask them for clarification if needed.
 
@@ -48,59 +49,64 @@ export const registerFunctions: RegistrationCallback = async ({
   /\". Always put a field value in double quotes. Best: service.name:\"opbeans-go\". Wrong: service.name:opbeans-go. This is very important!
 
   You can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.
-  
+
   Note that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language.
 
   If you want to call a function or tool, only call it a single time per message. Wait until the function has been executed and its results
   returned to you, before executing the same tool or another tool again if needed.
 
   DO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (\`service.name == "foo"\`) with "kqlFilter" (\`service.name:"foo"\`).
-  
+
   The user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the ${
     isServerless ? `Project settings.` : `Stack Management app under the option AI Assistants`
   }.
-  If the user asks how to change the language, reply in the same language the user asked in.`);
+  If the user asks how to change the language, reply in the same language the user asked in.`,
+    scopes: ['observability'],
+  });
 
   const { ready: isReady } = await client.getKnowledgeBaseStatus();
 
-  functions.registerInstruction(({ availableFunctionNames }) => {
-    const instructions: string[] = [];
-
-    if (
-      availableFunctionNames.includes(QUERY_FUNCTION_NAME) &&
-      availableFunctionNames.includes(GET_DATASET_INFO_FUNCTION_NAME)
-    ) {
-      instructions.push(`You MUST use the "${GET_DATASET_INFO_FUNCTION_NAME}" ${
-        functions.hasFunction('get_apm_dataset_info') ? 'or the get_apm_dataset_info' : ''
-      } function before calling the "${QUERY_FUNCTION_NAME}" or the "changes" functions.
-        
+  functions.registerInstruction({
+    instruction: ({ availableFunctionNames }) => {
+      const instructions: string[] = [];
+
+      if (
+        availableFunctionNames.includes(QUERY_FUNCTION_NAME) &&
+        availableFunctionNames.includes(GET_DATASET_INFO_FUNCTION_NAME)
+      ) {
+        instructions.push(`You MUST use the "${GET_DATASET_INFO_FUNCTION_NAME}" ${
+          functions.hasFunction('get_apm_dataset_info') ? 'or the get_apm_dataset_info' : ''
+        } function before calling the "${QUERY_FUNCTION_NAME}" or the "changes" functions.
+
       If a function requires an index, you MUST use the results from the dataset info functions.`);
-    }
+      }
 
-    if (availableFunctionNames.includes(GET_DATA_ON_SCREEN_FUNCTION_NAME)) {
-      instructions.push(`You have access to data on the screen by calling the "${GET_DATA_ON_SCREEN_FUNCTION_NAME}" function.
+      if (availableFunctionNames.includes(GET_DATA_ON_SCREEN_FUNCTION_NAME)) {
+        instructions.push(`You have access to data on the screen by calling the "${GET_DATA_ON_SCREEN_FUNCTION_NAME}" function.
         Use it to help the user understand what they are looking at. A short summary of what they are looking at is available in the return of the "${CONTEXT_FUNCTION_NAME}" function.
         Data that is compact enough automatically gets included in the response for the "${CONTEXT_FUNCTION_NAME}" function.`);
-    }
+      }
 
-    if (isReady) {
-      if (availableFunctionNames.includes(SUMMARIZE_FUNCTION_NAME)) {
-        instructions.push(`You can use the "${SUMMARIZE_FUNCTION_NAME}" function to store new information you have learned in a knowledge database.
+      if (isReady) {
+        if (availableFunctionNames.includes(SUMMARIZE_FUNCTION_NAME)) {
+          instructions.push(`You can use the "${SUMMARIZE_FUNCTION_NAME}" function to store new information you have learned in a knowledge database.
           Only use this function when the user asks for it.
           All summaries MUST be created in English, even if the conversation was carried out in a different language.`);
-      }
-
-      if (availableFunctionNames.includes(CONTEXT_FUNCTION_NAME)) {
+        }
+
+        if (availableFunctionNames.includes(CONTEXT_FUNCTION_NAME)) {
+          instructions.push(
+            `Additionally, you can use the "${CONTEXT_FUNCTION_NAME}" function to retrieve relevant information from the knowledge database.`
+          );
+        }
+      } else {
         instructions.push(
-          `Additionally, you can use the "${CONTEXT_FUNCTION_NAME}" function to retrieve relevant information from the knowledge database.`
+          `You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.`
         );
       }
-    } else {
-      instructions.push(
-        `You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.`
-      );
-    }
-    return instructions.map((instruction) => dedent(instruction));
+      return instructions.map((instruction) => dedent(instruction));
+    },
+    scopes: ['all'],
   });
 
   if (isReady) {
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/kibana.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/kibana.ts
index f939e3a79799b..f55a8ba432922 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/kibana.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/kibana.ts
@@ -95,6 +95,7 @@ export function registerKibanaFunction({
       }).then((response) => {
         return { content: response.data };
       });
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/summarize.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/summarize.ts
index 8865861d81f45..a4c34c5caa5a3 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/summarize.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/summarize.ts
@@ -86,6 +86,7 @@ export function registerSummarizationFunction({
             message: `The document has been stored`,
           },
         }));
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
index d57051cf9fb62..f5f235af12544 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts
@@ -10,6 +10,7 @@ import { context as otelContext } from '@opentelemetry/api';
 import * as t from 'io-ts';
 import { from, map } from 'rxjs';
 import { Readable } from 'stream';
+import { AssistantScope } from '../../../common/types';
 import { aiAssistantSimulatedFunctionCalling } from '../..';
 import { createFunctionResponseMessage } from '../../../common/utils/create_function_response_message';
 import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events';
@@ -20,7 +21,7 @@ import { observableIntoStream } from '../../service/util/observable_into_stream'
 import { withAssistantSpan } from '../../service/util/with_assistant_span';
 import { recallAndScore } from '../../utils/recall/recall_and_score';
 import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
-import { functionRt, messageRt, screenContextRt } from '../runtime_types';
+import { assistantScopeType, functionRt, messageRt, screenContextRt } from '../runtime_types';
 import { ObservabilityAIAssistantRouteHandlerResources } from '../types';
 
 const chatCompleteBaseRt = t.type({
@@ -60,6 +61,7 @@ const chatCompleteInternalRt = t.intersection([
   t.type({
     body: t.type({
       screenContexts: t.array(screenContextRt),
+      scope: assistantScopeType,
     }),
   }),
 ]);
@@ -81,10 +83,12 @@ async function initializeChatRequest({
   request,
   plugins: { cloud, actions },
   params: {
-    body: { connectorId },
+    body: { connectorId, scope },
   },
   service,
-}: ObservabilityAIAssistantRouteHandlerResources & { params: { body: { connectorId: string } } }) {
+}: ObservabilityAIAssistantRouteHandlerResources & {
+  params: { body: { connectorId: string; scope: AssistantScope } };
+}) {
   await withAssistantSpan('guard_against_invalid_connector', async () => {
     const actionsClient = await (await actions.start()).getActionsClientWithRequest(request);
 
@@ -97,7 +101,7 @@ async function initializeChatRequest({
   });
 
   const [client, cloudStart, simulateFunctionCalling] = await Promise.all([
-    service.getClient({ request }),
+    service.getClient({ request, scope }),
     cloud?.start(),
     (await context.core).uiSettings.client.get<boolean>(aiAssistantSimulatedFunctionCalling),
   ]);
@@ -132,6 +136,7 @@ const chatRoute = createObservabilityAIAssistantServerRoute({
         messages: t.array(messageRt),
         connectorId: t.string,
         functions: t.array(functionRt),
+        scope: assistantScopeType,
       }),
       t.partial({
         functionCall: t.string,
@@ -177,6 +182,7 @@ const chatRecallRoute = createObservabilityAIAssistantServerRoute({
       prompt: t.string,
       context: t.string,
       connectorId: t.string,
+      scope: assistantScopeType,
     }),
   }),
   handler: async (resources): Promise<Readable> => {
@@ -304,6 +310,7 @@ const publicChatCompleteRoute = createObservabilityAIAssistantServerRoute({
       params: {
         body: {
           ...restOfBody,
+          scope: 'observability',
           screenContexts: [
             {
               actions,
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts
index fae7077953699..1506db576275d 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts
@@ -12,9 +12,15 @@ import { KnowledgeBaseEntryRole } from '../../../common/types';
 import type { RecalledEntry } from '../../service/knowledge_base_service';
 import { getSystemMessageFromInstructions } from '../../service/util/get_system_message_from_instructions';
 import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
+import { assistantScopeType } from '../runtime_types';
 
 const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
-  endpoint: 'GET /internal/observability_ai_assistant/functions',
+  endpoint: 'GET /internal/observability_ai_assistant/{scope}/functions',
+  params: t.type({
+    path: t.type({
+      scope: assistantScopeType,
+    }),
+  }),
   options: {
     tags: ['access:ai_assistant'],
   },
@@ -24,7 +30,13 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
     functionDefinitions: FunctionDefinition[];
     systemMessage: string;
   }> => {
-    const { service, request } = resources;
+    const {
+      service,
+      request,
+      params: {
+        path: { scope },
+      },
+    } = resources;
 
     const controller = new AbortController();
     request.events.aborted$.subscribe(() => {
@@ -44,14 +56,14 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({
       client.getKnowledgeBaseUserInstructions(),
     ]);
 
-    const functionDefinitions = functionClient.getFunctions().map((fn) => fn.definition);
+    const functionDefinitions = functionClient.getFunctions({ scope }).map((fn) => fn.definition);
 
     const availableFunctionNames = functionDefinitions.map((def) => def.name);
 
     return {
       functionDefinitions: functionClient.getFunctions().map((fn) => fn.definition),
       systemMessage: getSystemMessageFromInstructions({
-        applicationInstructions: functionClient.getInstructions(),
+        applicationInstructions: functionClient.getInstructions(scope),
         userInstructions,
         adHocInstructions: [],
         availableFunctionNames,
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/runtime_types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/runtime_types.ts
index b7f8b9daa8a58..3ead874f22ca1 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/runtime_types.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/runtime_types.ts
@@ -73,6 +73,12 @@ export const baseConversationRt: t.Type<ConversationRequestBase> = t.type({
   public: toBooleanRt,
 });
 
+export const assistantScopeType = t.union([
+  t.literal('observability'),
+  t.literal('search'),
+  t.literal('all'),
+]);
+
 export const conversationCreateRt: t.Type<ConversationCreateRequest> = t.intersection([
   baseConversationRt,
   t.type({
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts
index 3d83c470de0c5..ea265c580b50f 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.test.ts
@@ -34,7 +34,8 @@ describe('chatFunctionClient', () => {
             required: ['foo'],
           },
         },
-        respondFn
+        respondFn,
+        ['all']
       );
     });
 
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts
index 039d7347c715e..d8fbe456da879 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/chat_function_client/index.ts
@@ -10,13 +10,18 @@ import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv';
 import dedent from 'dedent';
 import { compact, keyBy } from 'lodash';
 import { FunctionVisibility, type FunctionResponse } from '../../../common/functions/types';
-import type { Message, ObservabilityAIAssistantScreenContextRequest } from '../../../common/types';
+import type {
+  AssistantScope,
+  Message,
+  ObservabilityAIAssistantScreenContextRequest,
+} from '../../../common/types';
 import { filterFunctionDefinitions } from '../../../common/utils/filter_function_definitions';
 import type {
   FunctionCallChatFunction,
   FunctionHandler,
   FunctionHandlerRegistry,
   InstructionOrCallback,
+  InstructionOrCallbackWithScopes,
   RegisterFunction,
   RegisterInstruction,
 } from '../types';
@@ -34,7 +39,7 @@ const ajv = new Ajv({
 export const GET_DATA_ON_SCREEN_FUNCTION_NAME = 'get_data_on_screen';
 
 export class ChatFunctionClient {
-  private readonly instructions: InstructionOrCallback[] = [];
+  private readonly instructions: InstructionOrCallbackWithScopes[] = [];
   private readonly functionRegistry: FunctionHandlerRegistry = new Map();
   private readonly validators: Map<string, ValidateFunction> = new Map();
 
@@ -73,7 +78,8 @@ export class ChatFunctionClient {
           return {
             content: allData.filter((data) => dataNames.includes(data.name)),
           };
-        }
+        },
+        ['all']
       );
     }
 
@@ -84,11 +90,11 @@ export class ChatFunctionClient {
     });
   }
 
-  registerFunction: RegisterFunction = (definition, respond) => {
+  registerFunction: RegisterFunction = (definition, respond, scopes) => {
     if (definition.parameters) {
       this.validators.set(definition.name, ajv.compile(definition.parameters));
     }
-    this.functionRegistry.set(definition.name, { definition, respond });
+    this.functionRegistry.set(definition.name, { handler: { definition, respond }, scopes });
   };
 
   registerInstruction: RegisterInstruction = (instruction) => {
@@ -107,8 +113,12 @@ export class ChatFunctionClient {
     }
   }
 
-  getInstructions(): InstructionOrCallback[] {
-    return this.instructions;
+  getInstructions(scope: AssistantScope): InstructionOrCallback[] {
+    return this.instructions
+      .filter(
+        (instruction) => instruction.scopes.includes(scope) || instruction.scopes.includes('all')
+      )
+      .map((i) => i.instruction);
   }
 
   hasAction(name: string) {
@@ -117,10 +127,16 @@ export class ChatFunctionClient {
 
   getFunctions({
     filter,
+    scope,
   }: {
     filter?: string;
+    scope?: AssistantScope;
   } = {}): FunctionHandler[] {
-    const allFunctions = Array.from(this.functionRegistry.values());
+    const allFunctions = Array.from(this.functionRegistry.values())
+      .filter(({ handler, scopes }) =>
+        scope ? scopes.includes(scope) || scopes.includes('all') : true
+      )
+      .map(({ handler }) => handler);
 
     const functionsByName = keyBy(allFunctions, (definition) => definition.definition.name);
 
@@ -167,7 +183,7 @@ export class ChatFunctionClient {
 
     this.validate(name, parsedArguments);
 
-    return await fn.respond(
+    return await fn.handler.respond(
       {
         arguments: parsedArguments,
         messages,
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts
index a3c1d72fefbab..5a7cf81a40122 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts
@@ -187,6 +187,7 @@ describe('Observability AI Assistant client', () => {
       user: {
         name: 'johndoe',
       },
+      scope: 'all',
     });
   }
 
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts
index 1e995b66059c2..fc14558776434 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts
@@ -52,6 +52,7 @@ import {
   type KnowledgeBaseEntry,
   type Message,
   type AdHocInstruction,
+  AssistantScope,
 } from '../../../common/types';
 import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events';
 import { CONTEXT_FUNCTION_NAME } from '../../functions/context';
@@ -100,6 +101,7 @@ export class ObservabilityAIAssistantClient {
         name: string;
       };
       knowledgeBaseService: KnowledgeBaseService;
+      scope: AssistantScope;
     }
   ) {}
 
@@ -215,11 +217,11 @@ export class ObservabilityAIAssistantClient {
             // this is what we eventually store in the conversation
             const messagesWithUpdatedSystemMessage = replaceSystemMessage(
               getSystemMessageFromInstructions({
-                applicationInstructions: functionClient.getInstructions(),
+                applicationInstructions: functionClient.getInstructions(this.dependencies.scope),
                 userInstructions,
                 adHocInstructions,
                 availableFunctionNames: functionClient
-                  .getFunctions()
+                  .getFunctions({ scope: this.dependencies.scope })
                   .map((fn) => fn.definition.name),
               }),
               initialMessages
@@ -299,6 +301,7 @@ export class ObservabilityAIAssistantClient {
                 disableFunctions,
                 tracer: completeTracer,
                 connectorId,
+                scope: this.dependencies.scope,
                 useSimulatedFunctionCalling: simulateFunctionCalling === true,
               })
             );
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts
index 66204c96f31cb..b91600323d41e 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts
@@ -28,7 +28,7 @@ import {
   MessageOrChatEvent,
 } from '../../../../common/conversation_complete';
 import { FunctionVisibility } from '../../../../common/functions/types';
-import { AdHocInstruction, Instruction } from '../../../../common/types';
+import { AdHocInstruction, AssistantScope, Instruction } from '../../../../common/types';
 import { createFunctionResponseMessage } from '../../../../common/utils/create_function_response_message';
 import { emitWithConcatenatedMessage } from '../../../../common/utils/emit_with_concatenated_message';
 import { withoutTokenCountEvents } from '../../../../common/utils/without_token_count_events';
@@ -184,6 +184,7 @@ export function continueConversation({
   disableFunctions,
   tracer,
   connectorId,
+  scope,
   useSimulatedFunctionCalling,
 }: {
   messages: Message[];
@@ -201,6 +202,7 @@ export function continueConversation({
       };
   tracer: LangTracer;
   connectorId: string;
+  scope: AssistantScope;
   useSimulatedFunctionCalling: boolean;
 }): Observable<MessageOrChatEvent> {
   let nextFunctionCallsLeft = functionCallsLeft;
@@ -215,7 +217,7 @@ export function continueConversation({
 
   const messagesWithUpdatedSystemMessage = replaceSystemMessage(
     getSystemMessageFromInstructions({
-      applicationInstructions: functionClient.getInstructions(),
+      applicationInstructions: functionClient.getInstructions(scope),
       userInstructions,
       adHocInstructions,
       availableFunctionNames: definitions.map((def) => def.name),
@@ -344,6 +346,7 @@ export function continueConversation({
               disableFunctions,
               tracer,
               connectorId,
+              scope,
               useSimulatedFunctionCalling,
             });
           })
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts
index c087e5940f0b7..359692809f3a4 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/index.ts
@@ -13,6 +13,7 @@ import { getSpaceIdFromPath } from '@kbn/spaces-plugin/common';
 import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
 import { once } from 'lodash';
 import {
+  AssistantScope,
   KnowledgeBaseEntryRole,
   ObservabilityAIAssistantScreenContextRequest,
 } from '../../common/types';
@@ -248,8 +249,10 @@ export class ObservabilityAIAssistantService {
 
   async getClient({
     request,
+    scope,
   }: {
     request: KibanaRequest;
+    scope?: AssistantScope;
   }): Promise<ObservabilityAIAssistantClient> {
     const controller = new AbortController();
 
@@ -288,6 +291,7 @@ export class ObservabilityAIAssistantService {
           }
         : undefined,
       knowledgeBaseService: this.kbService!,
+      scope: scope || 'all',
     });
   }
 
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts
index ebc54daf36739..66510008df967 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/types.ts
@@ -17,6 +17,7 @@ import type {
   Message,
   ObservabilityAIAssistantScreenContextRequest,
   InstructionOrPlainText,
+  AssistantScope,
 } from '../../common/types';
 import type { ObservabilityAIAssistantRouteHandlerResources } from '../routes/types';
 import { ChatFunctionClient } from './chat_function_client';
@@ -67,13 +68,18 @@ export interface FunctionHandler {
 
 export type InstructionOrCallback = InstructionOrPlainText | RegisterInstructionCallback;
 
-type RegisterInstructionCallback = ({
+export interface InstructionOrCallbackWithScopes {
+  instruction: InstructionOrCallback;
+  scopes: AssistantScope[];
+}
+
+export type RegisterInstructionCallback = ({
   availableFunctionNames,
 }: {
   availableFunctionNames: string[];
 }) => InstructionOrPlainText | InstructionOrPlainText[] | undefined;
 
-export type RegisterInstruction = (...instructions: InstructionOrCallback[]) => void;
+export type RegisterInstruction = (...instruction: InstructionOrCallbackWithScopes[]) => void;
 
 export type RegisterFunction = <
   TParameters extends CompatibleJSONSchema = any,
@@ -81,9 +87,13 @@ export type RegisterFunction = <
   TArguments = FromSchema<TParameters>
 >(
   definition: FunctionDefinition<TParameters>,
-  respond: RespondFunction<TArguments, TResponse>
+  respond: RespondFunction<TArguments, TResponse>,
+  scopes: AssistantScope[]
 ) => void;
-export type FunctionHandlerRegistry = Map<string, FunctionHandler>;
+export type FunctionHandlerRegistry = Map<
+  string,
+  { handler: FunctionHandler; scopes: AssistantScope[] }
+>;
 
 export type RegistrationCallback = ({}: {
   signal: AbortSignal;
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
index a6795c13cab2c..8bc8f54e9ac8d 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts
@@ -15,5 +15,6 @@ export function useConversation() {
     stop: () => {},
     messages: [],
     saveTitle: () => {},
+    scope: 'all',
   };
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx
index 3ebca243a56b3..150847a011207 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx
@@ -55,6 +55,7 @@ const mockService: MockedService = {
     predefinedConversation$: new Observable(),
   },
   navigate: jest.fn().mockReturnValue(of()),
+  scope: 'all',
 };
 
 const mockChatService = createMockChatService();
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts
index 0616e9dbaca32..617b1b302473f 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts
@@ -63,6 +63,7 @@ export function useConversation({
   onConversationUpdate,
 }: UseConversationProps): UseConversationResult {
   const service = useObservabilityAIAssistantAppService();
+  const { scope } = service;
 
   const {
     services: {
@@ -126,6 +127,7 @@ export function useConversation({
       onConversationUpdate?.({ conversation: event.conversation });
     },
     persist: true,
+    scope,
   });
 
   const [displayedConversationId, setDisplayedConversationId] = useState(initialConversationId);
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts
index 6fa07af24208c..030994fa44acf 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts
@@ -100,6 +100,7 @@ function runEvaluations() {
             evaluationConnectorId: evaluationConnector.id!,
             persist: argv.persist,
             suite: mocha.suite,
+            scope: 'all',
           });
 
           const header: string[][] = [
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts
index 61ed156530100..8246a9ceae71f 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts
@@ -18,7 +18,10 @@ import {
   StreamingChatResponseEvent,
   StreamingChatResponseEventType,
 } from '@kbn/observability-ai-assistant-plugin/common';
-import type { ObservabilityAIAssistantScreenContext } from '@kbn/observability-ai-assistant-plugin/common/types';
+import type {
+  AssistantScope,
+  ObservabilityAIAssistantScreenContext,
+} from '@kbn/observability-ai-assistant-plugin/common/types';
 import { throwSerializedChatCompletionErrors } from '@kbn/observability-ai-assistant-plugin/common/utils/throw_serialized_chat_completion_errors';
 import {
   isSupportedConnectorType,
@@ -238,11 +241,13 @@ export class KibanaClient {
     evaluationConnectorId,
     persist,
     suite,
+    scope,
   }: {
     connectorId: string;
     evaluationConnectorId: string;
     persist: boolean;
     suite?: Mocha.Suite;
+    scope: AssistantScope;
   }): ChatClient {
     function getMessages(message: string | Array<Message['message']>): Array<Message['message']> {
       if (typeof message === 'string') {
@@ -370,6 +375,7 @@ export class KibanaClient {
             connectorId: connectorIdOverride || connectorId,
             functions: functions.map((fn) => pick(fn, 'name', 'description', 'parameters')),
             functionCall,
+            scope,
           };
 
         return that.axios.post(
@@ -459,6 +465,7 @@ export class KibanaClient {
                 connectorId,
                 persist,
                 title: currentTitle,
+                scope,
               },
               { responseType: 'stream', timeout: NaN }
             )
@@ -534,7 +541,7 @@ export class KibanaClient {
                 which helps our users make sense of their Observability data.
 
                 Your goal is to verify whether a conversation between the user and the assistant matches the given criteria.
-                
+
                 For each criterion, calculate a score. Explain your score, by describing what the assistant did right, and describing and quoting what the
                 assistant did wrong, where it could improve, and what the root cause was in case of a failure.`,
               },
@@ -544,13 +551,13 @@ export class KibanaClient {
               message: {
                 role: MessageRole.User,
                 content: `Evaluate the conversation according to the following criteria, using the "scores" tool:
-                
+
                 ${criteria.map((criterion, index) => {
                   return `${index}: ${criterion}`;
                 })}
-                
+
                 This is the conversation:
-                
+
                 ${JSON.stringify(
                   messages
                     .filter((msg) => msg.role !== MessageRole.System)
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/alerts.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/alerts.ts
index 03c61843e702a..1d0056fa2f66c 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/alerts.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/alerts.ts
@@ -131,13 +131,14 @@ export function registerAlertsFunction({
           fields: fields.length === 0 ? defaultFields : fields,
         },
       };
-    }
+    },
+    ['observability']
   );
 
   functions.registerFunction(
     {
       name: 'alerts',
-      description: `Get alerts for Observability.  Make sure get_alerts_dataset_info was called before.      
+      description: `Get alerts for Observability.  Make sure get_alerts_dataset_info was called before.
         Use this to get open (and optionally recovered) alerts for Observability assets, like services,
         hosts or containers.
         Display the response in tabular format if appropriate.
@@ -220,6 +221,7 @@ export function registerAlertsFunction({
           alerts,
         },
       };
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/changes/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/changes/index.ts
index dc0e26ea9c777..71872782e27b0 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/changes/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/changes/index.ts
@@ -149,6 +149,7 @@ export function registerChangesFunction({
           },
         },
       };
-    }
+    },
+    ['observability']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/lens.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/lens.ts
index dbae57c08c9e2..bb07d701f1708 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/lens.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/lens.ts
@@ -8,9 +8,13 @@ import type { ChatFunctionClient } from '@kbn/observability-ai-assistant-plugin/
 import { lensFunctionDefinition } from '../../common/functions/lens';
 
 export function registerLensFunction({ functions }: { functions: ChatFunctionClient }) {
-  functions.registerFunction(lensFunctionDefinition, async () => {
-    return {
-      content: {},
-    };
-  });
+  functions.registerFunction(
+    lensFunctionDefinition,
+    async () => {
+      return {
+        content: {},
+      };
+    },
+    ['all']
+  );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts
index c5cd40aade7c4..8f7eb7b6b4e1f 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts
@@ -20,6 +20,7 @@ import {
 import { createFunctionResponseMessage } from '@kbn/observability-ai-assistant-plugin/common/utils/create_function_response_message';
 import { map } from 'rxjs';
 import { v4 } from 'uuid';
+import { RegisterInstructionCallback } from '@kbn/observability-ai-assistant-plugin/server/service/types';
 import type { FunctionRegistrationParameters } from '..';
 import { runAndValidateEsqlQuery } from './validate_esql_query';
 import { convertMessagesForInference } from '../../../common/convert_messages_for_inference';
@@ -32,7 +33,7 @@ export function registerQueryFunction({
   resources,
   pluginsStart,
 }: FunctionRegistrationParameters) {
-  functions.registerInstruction(({ availableFunctionNames }) =>
+  const instruction: RegisterInstructionCallback = ({ availableFunctionNames }) =>
     availableFunctionNames.includes(QUERY_FUNCTION_NAME)
       ? `You MUST use the "${QUERY_FUNCTION_NAME}" function when the user wants to:
   - visualize data
@@ -51,8 +52,8 @@ export function registerQueryFunction({
 
   When the "visualize_query" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a "visualize_query" function call with your own visualization attempt.
   If the "${EXECUTE_QUERY_NAME}" function has been called, summarize these results for the user. The user does not see a visualization in this case.`
-      : undefined
-  );
+      : undefined;
+  functions.registerInstruction({ instruction, scopes: ['all'] });
 
   functions.registerFunction(
     {
@@ -65,7 +66,7 @@ export function registerQueryFunction({
         such as a metric or list of things, but does not want to visualize it in
         a table or chart. You do NOT need to ask permission to execute the query
         after generating it, use the "${EXECUTE_QUERY_NAME}" function directly instead.
-        
+
         Do not use when the user just asks for an example.`,
       parameters: {
         type: 'object',
@@ -102,7 +103,8 @@ export function registerQueryFunction({
           rows,
         },
       };
-    }
+    },
+    ['all']
   );
   functions.registerFunction(
     {
@@ -186,6 +188,7 @@ export function registerQueryFunction({
           return messageAddEvent;
         })
       );
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts
index 4eeba0450e6e4..bda75eafc9ade 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts
@@ -61,6 +61,7 @@ export function registerVisualizeESQLFunction({
           ],
         },
       };
-    }
+    },
+    ['all']
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts
index 34b9dd36ea77f..d99e822484b67 100644
--- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts
+++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts
@@ -154,7 +154,7 @@ async function executor(
   }
 
   const resources = await initResources(request);
-  const client = await resources.service.getClient({ request });
+  const client = await resources.service.getClient({ request, scope: 'observability' });
   const functionClient = await resources.service.getFunctionClient({
     signal: new AbortController().signal,
     resources,
@@ -227,7 +227,7 @@ If available, include the link of the conversation at the end of your answer.`
             role: MessageRole.System,
             content: getSystemMessageFromInstructions({
               availableFunctionNames: functionClient.getFunctions().map((fn) => fn.definition.name),
-              applicationInstructions: functionClient.getInstructions(),
+              applicationInstructions: functionClient.getInstructions('observability'),
               userInstructions: [],
               adHocInstructions: [],
             }),
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/firehose.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/firehose.tsx
index 638d931997ec3..3f986f080fc3e 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/firehose.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/firehose.tsx
@@ -19,7 +19,7 @@ export const FirehosePage = () => (
         headlineCopy={i18n.translate(
           'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.firehose.text',
           {
-            defaultMessage: 'Setting up Amazon Data Firehose',
+            defaultMessage: 'Set up Amazon Data Firehose',
           }
         )}
         captionCopy={i18n.translate(
@@ -29,6 +29,7 @@ export const FirehosePage = () => (
               'This installation is tailored for setting up Firehose in your Observability project with minimal configuration.',
           }
         )}
+        isTechnicalPreview={true}
       />
     }
   >
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/auto_refresh_callout.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/auto_refresh_callout.tsx
new file mode 100644
index 0000000000000..e4cd98c37ee1c
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/auto_refresh_callout.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiIcon,
+  EuiText,
+  useEuiTheme,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
+import { HAS_DATA_FETCH_INTERVAL } from './utils';
+
+export function AutoRefreshCallout() {
+  const { euiTheme } = useEuiTheme();
+  const messageId = useGeneratedHtmlId();
+
+  return (
+    <EuiFlexGroup>
+      <EuiFlexItem
+        role="status"
+        aria-labelledby={messageId}
+        grow={false}
+        css={css`
+          background-color: ${euiTheme.colors.lightestShade};
+          padding: ${euiTheme.size.m} ${euiTheme.size.base};
+          border-radius: ${euiTheme.border.radius.medium};
+        `}
+      >
+        <EuiFlexGroup gutterSize="s" alignItems="center">
+          <EuiIcon type="timeRefresh" size="m" />
+          <EuiText size="s">
+            <p id={messageId}>
+              {i18n.translate(
+                'xpack.observability_onboarding.firehosePanel.autorefreshCalloutLabel',
+                {
+                  defaultMessage: 'Auto-refreshing every {intervalSeconds} s',
+                  values: { intervalSeconds: Math.round(HAS_DATA_FETCH_INTERVAL / 1000) },
+                }
+              )}
+            </p>
+          </EuiText>
+        </EuiFlexGroup>
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_command_snippet.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_command_snippet.tsx
index 774f02c23a902..dedc05c701e00 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_command_snippet.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_command_snippet.tsx
@@ -5,7 +5,6 @@
  * 2.0.
  */
 
-import React from 'react';
 import {
   EuiAccordion,
   EuiCodeBlock,
@@ -14,19 +13,20 @@ import {
   EuiText,
   useGeneratedHtmlId,
 } from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n-react';
 import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import React from 'react';
 import {
   FIREHOSE_CLOUDFORMATION_STACK_NAME,
   FIREHOSE_LOGS_STREAM_NAME,
   FIREHOSE_METRICS_STREAM_NAME,
 } from '../../../../common/aws_firehose';
 import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';
+import { DownloadTemplateCallout } from './download_template_callout';
 import { buildCreateStackCommand, buildStackStatusCommand } from './utils';
 
 interface Props {
   encodedApiKey: string;
-  onboardingId: string;
   elasticsearchUrl: string;
   templateUrl: string;
   isCopyPrimaryAction: boolean;
@@ -57,7 +57,7 @@ export function CreateStackCommandSnippet({
         <p>
           <FormattedMessage
             id="xpack.observability_onboarding.firehosePanel.createFirehoseStreamDescription"
-            defaultMessage="Run the command bellow in your terminal where you have {awsCLIInstallGuideLink} configured. The command will create a CloudFormation stack that includes a Firehose delivery, backup S3 bucket, CloudWatch subscription filter and metrics stream along with required IAM roles."
+            defaultMessage="Run the command bellow in your terminal where you have {awsCLIInstallGuideLink} configured. The command will create a CloudFormation stack from our template that includes a Firehose delivery, backup S3 bucket, CloudWatch subscription filter and metrics stream along with required IAM roles."
             values={{
               awsCLIInstallGuideLink: (
                 <EuiLink
@@ -75,6 +75,10 @@ export function CreateStackCommandSnippet({
             }}
           />
         </p>
+
+        <p>
+          <DownloadTemplateCallout />
+        </p>
       </EuiText>
 
       <EuiSpacer />
@@ -94,7 +98,15 @@ export function CreateStackCommandSnippet({
 
       <EuiSpacer />
 
-      <EuiAccordion id={stackStatusAccordionId} buttonContent="Check stack status">
+      <EuiAccordion
+        id={stackStatusAccordionId}
+        buttonContent={i18n.translate(
+          'xpack.observability_onboarding.firehosePanel.stackStatusAccordionButtonLabel',
+          {
+            defaultMessage: 'Check status of the CloudFormation stack',
+          }
+        )}
+      >
         <EuiSpacer size="xs" />
         <EuiCodeBlock language="text" paddingSize="m" fontSize="m" isCopyable>
           {stackStatusCommand}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_in_aws_console.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_in_aws_console.tsx
new file mode 100644
index 0000000000000..3c62632b382ad
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/create_stack_in_aws_console.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import React from 'react';
+import {
+  FIREHOSE_CLOUDFORMATION_STACK_NAME,
+  FIREHOSE_LOGS_STREAM_NAME,
+  FIREHOSE_METRICS_STREAM_NAME,
+} from '../../../../common/aws_firehose';
+import { DownloadTemplateCallout } from './download_template_callout';
+import { buildCreateStackAWSConsoleURL } from './utils';
+
+interface Props {
+  encodedApiKey: string;
+  elasticsearchUrl: string;
+  templateUrl: string;
+  isPrimaryAction: boolean;
+}
+
+export function CreateStackInAWSConsole({
+  encodedApiKey,
+  elasticsearchUrl,
+  templateUrl,
+  isPrimaryAction,
+}: Props) {
+  const awsConsoleURL = buildCreateStackAWSConsoleURL({
+    templateUrl,
+    stackName: FIREHOSE_CLOUDFORMATION_STACK_NAME,
+    logsStreamName: FIREHOSE_LOGS_STREAM_NAME,
+    metricsStreamName: FIREHOSE_METRICS_STREAM_NAME,
+    elasticsearchUrl,
+    encodedApiKey,
+  });
+
+  return (
+    <>
+      <EuiText>
+        <p>
+          <FormattedMessage
+            id="xpack.observability_onboarding.firehosePanel.createFirehoseStreamInAWSConsoleDescription"
+            defaultMessage="Click the button below to create a CloudFormation stack from our template. The stack will include a Firehose delivery stream, backup S3 bucket, CloudWatch subscription filter, metrics stream, and necessary IAM roles. Keep this page open, and return once you've submitted the form in AWS Console"
+          />
+        </p>
+        <p>
+          <DownloadTemplateCallout />
+        </p>
+      </EuiText>
+
+      <EuiSpacer size="m" />
+
+      <EuiButton
+        data-test-subj="observabilityOnboardingCreateStackInAWSConsoleButton"
+        href={awsConsoleURL}
+        target="_blank"
+        iconSide="right"
+        iconType="popout"
+        fill={isPrimaryAction}
+      >
+        {i18n.translate(
+          'xpack.observability_onboarding.createStackInAWSConsole.createFirehoseStreamInAWSConsoleButtonLabel',
+          { defaultMessage: 'Create Firehose Stream in AWS' }
+        )}
+      </EuiButton>
+    </>
+  );
+}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/download_template_callout.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/download_template_callout.tsx
new file mode 100644
index 0000000000000..9a6e9b97bde84
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/download_template_callout.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { FIREHOSE_CLOUDFORMATION_TEMPLATE_URL } from '../../../../common/aws_firehose';
+
+export function DownloadTemplateCallout() {
+  return (
+    <FormattedMessage
+      id="xpack.observability_onboarding.firehosePanel.downloadTemplateDescription"
+      defaultMessage="If needed, you can {downloadLink} to use it as part of an existing IaC setup."
+      values={{
+        downloadLink: (
+          <EuiLink
+            data-test-subj="observabilityOnboardingFirehosePanelDownloadCloudFormationTemplateLink"
+            href={FIREHOSE_CLOUDFORMATION_TEMPLATE_URL}
+            download={true}
+          >
+            {i18n.translate(
+              'xpack.observability_onboarding.firehosePanel.downloadCloudFormationTemplateButtonLabel',
+              { defaultMessage: 'download and modify the CloudFormation template' }
+            )}
+          </EuiLink>
+        ),
+      }}
+    />
+  );
+}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/index.tsx
index 520171c835808..00a5b582ba1e7 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/index.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/index.tsx
@@ -5,23 +5,57 @@
  * 2.0.
  */
 
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
+import { i18n } from '@kbn/i18n';
 import {
+  EuiButtonGroup,
+  EuiLink,
   EuiPanel,
   EuiSkeletonRectangle,
   EuiSkeletonText,
   EuiSpacer,
   EuiSteps,
   EuiStepStatus,
+  EuiText,
 } from '@elastic/eui';
 import useEvent from 'react-use/lib/useEvent';
+import { FormattedMessage } from '@kbn/i18n-react';
 import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
 import { EmptyPrompt } from '../shared/empty_prompt';
 import { CreateStackCommandSnippet } from './create_stack_command_snippet';
 import { VisualizeData } from './visualize_data';
+import { CreateStackInAWSConsole } from './create_stack_in_aws_console';
+import { FeedbackButtons } from '../shared/feedback_buttons';
+
+enum CreateStackOption {
+  AWS_CONSOLE_UI = 'createCloudFormationOptionAWSConsoleUI',
+  AWS_CLI = 'createCloudFormationOptionAWSCLI',
+}
+
+const OPTIONS = [
+  {
+    id: CreateStackOption.AWS_CONSOLE_UI,
+    label: i18n.translate(
+      'xpack.observability_onboarding.firehosePanel.createStackAWSConsoleOptionLabel',
+      {
+        defaultMessage: 'Via AWS Console',
+      }
+    ),
+  },
+  {
+    id: CreateStackOption.AWS_CLI,
+    label: i18n.translate(
+      'xpack.observability_onboarding.firehosePanel.createStackAWSCLIOptionLabel',
+      { defaultMessage: 'Via AWS CLI' }
+    ),
+  },
+];
 
 export function FirehosePanel() {
   const [windowLostFocus, setWindowLostFocus] = useState(false);
+  const [selectedOptionId, setSelectedOptionId] = useState<CreateStackOption>(
+    CreateStackOption.AWS_CONSOLE_UI
+  );
   const { data, status, error, refetch } = useFetcher(
     (callApi) => {
       return callApi('POST /internal/observability_onboarding/firehose/flow');
@@ -32,6 +66,10 @@ export function FirehosePanel() {
 
   useEvent('blur', () => setWindowLostFocus(true), window);
 
+  const onOptionChange = useCallback((id: string) => {
+    setSelectedOptionId(id as CreateStackOption);
+  }, []);
+
   if (error !== undefined) {
     return <EmptyPrompt error={error} onRetryClick={refetch} />;
   }
@@ -41,7 +79,45 @@ export function FirehosePanel() {
 
   const steps = [
     {
-      title: 'Create a Firehose delivery stream and ingest CloudWatch logs',
+      title: i18n.translate('xpack.observability_onboarding.firehosePanel.prerequisitesTitle', {
+        defaultMessage: 'Prerequisites',
+      }),
+      children: (
+        <>
+          <EuiText>
+            <p>
+              <FormattedMessage
+                id="xpack.observability_onboarding.firehosePanel.prerequisitesDescription"
+                defaultMessage="You must have an active AWS account and the necessary permissions to create delivery streams."
+              />
+            </p>
+            <p>
+              <FormattedMessage
+                id="xpack.observability_onboarding.firehosePanel.prerequisitesDocumentation"
+                defaultMessage="{documentationLink} for more info."
+                values={{
+                  documentationLink: (
+                    <EuiLink
+                      data-test-subj="observabilityOnboardingFirehosePanelCheckTheDocumentationLink"
+                      href="https://www.elastic.co/docs/current/integrations/awsfirehose"
+                      external
+                      target="_blank"
+                    >
+                      {i18n.translate(
+                        'xpack.observability_onboarding.firehosePanel.documentationLinkLabel',
+                        { defaultMessage: 'Check the documentation' }
+                      )}
+                    </EuiLink>
+                  ),
+                }}
+              />
+            </p>
+          </EuiText>
+        </>
+      ),
+    },
+    {
+      title: 'Create a Firehose delivery stream to ingest CloudWatch logs and metrics',
       children: (
         <>
           {status !== FETCH_STATUS.SUCCESS && (
@@ -52,13 +128,41 @@ export function FirehosePanel() {
             </>
           )}
           {status === FETCH_STATUS.SUCCESS && data !== undefined && (
-            <CreateStackCommandSnippet
-              templateUrl={data.templateUrl}
-              encodedApiKey={data.apiKeyEncoded}
-              onboardingId={data.onboardingId}
-              elasticsearchUrl={data.elasticsearchUrl}
-              isCopyPrimaryAction={!isVisualizeStepActive}
-            />
+            <>
+              <EuiButtonGroup
+                legend={i18n.translate(
+                  'xpack.observability_onboarding.firehosePanel.createStackOptionsLegend',
+                  {
+                    defaultMessage: 'Select a preferred option to create a CloudFormation stack',
+                  }
+                )}
+                type="single"
+                buttonSize="m"
+                idSelected={selectedOptionId}
+                onChange={onOptionChange}
+                options={OPTIONS}
+              />
+
+              <EuiSpacer size="l" />
+
+              {selectedOptionId === CreateStackOption.AWS_CONSOLE_UI && (
+                <CreateStackInAWSConsole
+                  templateUrl={data.templateUrl}
+                  encodedApiKey={data.apiKeyEncoded}
+                  elasticsearchUrl={data.elasticsearchUrl}
+                  isPrimaryAction={!isVisualizeStepActive}
+                />
+              )}
+
+              {selectedOptionId === CreateStackOption.AWS_CLI && (
+                <CreateStackCommandSnippet
+                  templateUrl={data.templateUrl}
+                  encodedApiKey={data.apiKeyEncoded}
+                  elasticsearchUrl={data.elasticsearchUrl}
+                  isCopyPrimaryAction={!isVisualizeStepActive}
+                />
+              )}
+            </>
           )}
         </>
       ),
@@ -73,6 +177,7 @@ export function FirehosePanel() {
   return (
     <EuiPanel hasBorder paddingSize="xl">
       <EuiSteps steps={steps} />
+      <FeedbackButtons flow="firehose" />
     </EuiPanel>
   );
 }
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/progress_callout.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/progress_callout.tsx
new file mode 100644
index 0000000000000..b520515c2635d
--- /dev/null
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/progress_callout.tsx
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiFlexGroup, EuiText, EuiIconTip, EuiHorizontalRule } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { css } from '@emotion/react';
+import { ProgressIndicator } from '../shared/progress_indicator';
+
+const SUPPORTED_SERVICES = [
+  'API Gateway',
+  'AWS Usage',
+  'CloudTrail',
+  'DynamoDB',
+  'EBS',
+  'EC2',
+  'ECS',
+  'ELB',
+  'EMR',
+  'Kinesis Data Stream',
+  'Lambda',
+  'MSK',
+  'NAT Gateway',
+  'RDS',
+  'Route53',
+  'S3',
+  'SNS',
+  'SQS',
+  'VPC',
+  'VPN',
+];
+
+export function ProgressCallout() {
+  return (
+    <ProgressIndicator
+      data-test-subj="observabilityOnboardingFirehoseProgressCallout"
+      title={
+        <EuiFlexGroup alignItems="center" gutterSize="s">
+          <EuiText>
+            <p>
+              {i18n.translate('xpack.observability_onboarding.firehosePanel.waitingForDataTitle', {
+                defaultMessage: 'Retrieving data from Amazon Data Firehose',
+              })}
+            </p>
+          </EuiText>
+          <EuiIconTip
+            content={
+              <EuiText size="s">
+                <strong>
+                  {i18n.translate(
+                    'xpack.observability_onboarding.progressCallout.strong.allServicesWeCanLabel',
+                    { defaultMessage: 'All services we can detect' }
+                  )}
+                </strong>
+                <EuiHorizontalRule margin="xs" />
+                <ul>
+                  {SUPPORTED_SERVICES.map((service) => (
+                    <li key={service}>{service}</li>
+                  ))}
+                  <li>
+                    {i18n.translate(
+                      'xpack.observability_onboarding.progressCallout.li.otherLabel',
+                      {
+                        defaultMessage:
+                          'Other (Unsupported logs will be stored in a generic Firehose index).',
+                      }
+                    )}
+                  </li>
+                </ul>
+              </EuiText>
+            }
+            position="top"
+            type="iInCircle"
+          />
+        </EuiFlexGroup>
+      }
+      isLoading={true}
+      css={css`
+        display: inline-block;
+      `}
+    />
+  );
+}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts
index 2aa08f7a6bed9..277c565986d3c 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts
@@ -98,7 +98,7 @@ export function useAWSServiceGetStartedList(): AWSServiceGetStartedConfig[] {
 
   const generateMetricsDiscoverActionLink = useCallback(
     (namespace: string, name: string) => ({
-      id: `logs-explorer-${namespace}`,
+      id: `discover-${namespace}`,
       title: i18n.translate('xpack.observability_onboarding.firehosePanel.exploreDataTitle', {
         defaultMessage: 'See {name} metrics data in Discover',
         values: { name },
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/utils.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/utils.ts
index 7fdfe5890830b..0fa28276f7fcc 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/utils.ts
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/utils.ts
@@ -5,6 +5,8 @@
  * 2.0.
  */
 
+export const HAS_DATA_FETCH_INTERVAL = 5000;
+
 export function buildCreateStackCommand({
   templateUrl,
   stackName,
@@ -48,3 +50,41 @@ export function buildStackStatusCommand({ stackName }: { stackName: string }) {
     .replace(/\n/g, ' ')
     .replace(/\s\s+/g, ' ');
 }
+
+export function buildCreateStackAWSConsoleURL({
+  templateUrl,
+  stackName,
+  logsStreamName,
+  metricsStreamName,
+  elasticsearchUrl,
+  encodedApiKey,
+}: {
+  templateUrl: string;
+  stackName: string;
+  logsStreamName: string;
+  metricsStreamName: string;
+  elasticsearchUrl: string;
+  encodedApiKey: string;
+}): string {
+  const url = new URL('https://console.aws.amazon.com');
+  const params = new URLSearchParams({
+    templateURL: templateUrl,
+    stackName,
+    /**
+     * 'param_' format is enforced by AWS
+     * but template parameters are in CamelCase
+     * which triggers the eslint rule.
+     */
+    /* eslint-disable @typescript-eslint/naming-convention */
+    param_FirehoseStreamNameForLogs: logsStreamName,
+    param_FirehoseStreamNameForMetrics: metricsStreamName,
+    param_ElasticEndpointURL: elasticsearchUrl,
+    param_ElasticAPIKey: encodedApiKey,
+    /* eslint-enable @typescript-eslint/naming-convention */
+  });
+
+  url.pathname = '/cloudformation/home';
+  url.hash = `/stacks/quickcreate?${params.toString()}`;
+
+  return url.toString();
+}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/visualize_data.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/visualize_data.tsx
index aee32dee4fa95..ca16519eb6cc2 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/visualize_data.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/firehose/visualize_data.tsx
@@ -5,27 +5,35 @@
  * 2.0.
  */
 
-import { EuiIcon, EuiSpacer, useEuiTheme, useGeneratedHtmlId } from '@elastic/eui';
-import { css } from '@emotion/react';
+import { EuiIcon, EuiSpacer, EuiText, useGeneratedHtmlId } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import useInterval from 'react-use/lib/useInterval';
+import { union } from 'lodash';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { ObservabilityOnboardingAppServices } from '../../..';
 import {
   FIREHOSE_CLOUDFORMATION_STACK_NAME,
   FIREHOSE_LOGS_STREAM_NAME,
+  type AWSIndexName,
 } from '../../../../common/aws_firehose';
 import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
 import { AccordionWithIcon } from '../shared/accordion_with_icon';
 import { GetStartedPanel } from '../shared/get_started_panel';
-import { ProgressIndicator } from '../shared/progress_indicator';
 import { useAWSServiceGetStartedList } from './use_aws_service_get_started_list';
+import { AutoRefreshCallout } from './auto_refresh_callout';
+import { ProgressCallout } from './progress_callout';
+import { HAS_DATA_FETCH_INTERVAL } from './utils';
 
-const FETCH_INTERVAL = 2000;
 const REQUEST_PENDING_STATUS_LIST = [FETCH_STATUS.LOADING, FETCH_STATUS.NOT_INITIATED];
 
 export function VisualizeData() {
   const accordionId = useGeneratedHtmlId({ prefix: 'accordion' });
-  const { euiTheme } = useEuiTheme();
+  const [orderedPopulatedAWSLogsIndexList, setOrderedPopulatedAWSLogsIndexList] = useState<
+    AWSIndexName[]
+  >([]);
+  const [shouldShowDataReceivedToast, setShouldShowDataReceivedToast] = useState<boolean>(true);
   const {
     data: populatedAWSLogsIndexList,
     status,
@@ -40,6 +48,49 @@ export function VisualizeData() {
       },
     });
   }, []);
+  const {
+    services: { notifications },
+  } = useKibana<ObservabilityOnboardingAppServices>();
+
+  useEffect(() => {
+    if (
+      shouldShowDataReceivedToast &&
+      Array.isArray(populatedAWSLogsIndexList) &&
+      populatedAWSLogsIndexList.length > 0
+    ) {
+      notifications?.toasts.addSuccess(
+        {
+          title: i18n.translate(
+            'xpack.observability_onboarding.firehosePanel.dataReceivedToastTitle',
+            {
+              defaultMessage: 'Your data is on its way',
+            }
+          ),
+          text: i18n.translate(
+            'xpack.observability_onboarding.firehosePanel.dataReceivedToastText',
+            {
+              defaultMessage:
+                'We’ve begun processing your data. In the background, we automatically refresh every few seconds to capture more incoming data.',
+            }
+          ),
+        },
+        {
+          toastLifeTimeMs: 10000,
+        }
+      );
+      setShouldShowDataReceivedToast(false);
+    }
+
+    setOrderedPopulatedAWSLogsIndexList((currentList) =>
+      /**
+       * Using union() to ensure items in the array are unique
+       * add stay in the insertion order to keep the order of
+       * the AWS services in the UI.
+       */
+      union(currentList, populatedAWSLogsIndexList)
+    );
+  }, [notifications?.toasts, populatedAWSLogsIndexList, shouldShowDataReceivedToast]);
+
   const awsServiceGetStartedConfigList = useAWSServiceGetStartedList();
 
   useInterval(() => {
@@ -48,7 +99,7 @@ export function VisualizeData() {
     }
 
     refetch();
-  }, FETCH_INTERVAL);
+  }, HAS_DATA_FETCH_INTERVAL);
 
   if (populatedAWSLogsIndexList === undefined) {
     return null;
@@ -56,58 +107,58 @@ export function VisualizeData() {
 
   return (
     <>
-      <ProgressIndicator
-        title={i18n.translate('xpack.observability_onboarding.firehosePanel.waitingForDataTitle', {
-          defaultMessage: 'Waiting for data from the Firehose stream',
-        })}
-        isLoading={true}
-        css={css`
-          max-width: 40%;
-        `}
-      />
+      <EuiText>
+        <p>
+          <FormattedMessage
+            id="xpack.observability_onboarding.firehosePanel.visualizeDataDescription"
+            defaultMessage="Once the Firehose stream is created, data capture will begin automatically, and the incoming data will be displayed below."
+          />
+        </p>
+      </EuiText>
 
-      <EuiSpacer size="xl" />
+      <EuiSpacer size="m" />
+
+      {orderedPopulatedAWSLogsIndexList.length === 0 && <ProgressCallout />}
+      {orderedPopulatedAWSLogsIndexList.length > 0 && <AutoRefreshCallout />}
+
+      <EuiSpacer size="m" />
 
       <div data-test-subj="observabilityOnboardingAWSServiceList">
-        {awsServiceGetStartedConfigList.map(
-          ({ id, indexNameList, actionLinks, title, logoURL, previewImage }) => {
-            const isEnabled = indexNameList.some((indexName) =>
-              populatedAWSLogsIndexList.includes(indexName)
-            );
-
-            return (
-              <AccordionWithIcon
-                data-test-subj={`observabilityOnboardingAWSService-${id}`}
-                key={id}
-                id={`${accordionId}_${id}`}
-                icon={<EuiIcon type={logoURL} size="l" />}
-                title={i18n.translate(
-                  'xpack.observability_onboarding.firehosePanel.awsServiceDataFoundTitle',
-                  {
-                    defaultMessage: '{title}',
-                    values: { title },
-                  }
-                )}
-                extraAction={
-                  isEnabled ? <EuiIcon type="checkInCircleFilled" color="success" /> : null
-                }
-                isDisabled={!isEnabled}
-                css={{
-                  paddingRight: euiTheme.size.s,
-                  filter: `grayscale(${isEnabled ? 0 : 1})`,
-                }}
-              >
-                <GetStartedPanel
-                  integration="aws"
-                  newTab
-                  isLoading={false}
-                  actionLinks={actionLinks}
-                  previewImage={previewImage}
-                />
-              </AccordionWithIcon>
-            );
+        {orderedPopulatedAWSLogsIndexList.map((indexName, index) => {
+          const getStartedConfig = awsServiceGetStartedConfigList.find(({ indexNameList }) =>
+            indexNameList.includes(indexName)
+          );
+
+          if (!getStartedConfig) {
+            return null;
           }
-        )}
+
+          const { id, actionLinks, title, logoURL, previewImage } = getStartedConfig;
+
+          return (
+            <AccordionWithIcon
+              data-test-subj={`observabilityOnboardingAWSService-${id}`}
+              key={id}
+              id={`${accordionId}_${id}`}
+              icon={<EuiIcon type={logoURL} size="l" />}
+              title={title}
+              initialIsOpen={true}
+              borders={
+                index === 0 || index === orderedPopulatedAWSLogsIndexList.length - 1
+                  ? 'none'
+                  : 'horizontal'
+              }
+            >
+              <GetStartedPanel
+                integration="aws"
+                newTab
+                isLoading={false}
+                actionLinks={actionLinks}
+                previewImage={previewImage}
+              />
+            </AccordionWithIcon>
+          );
+        })}
       </div>
     </>
   );
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/accordion_with_icon.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/accordion_with_icon.tsx
index 2f18c299bbc56..5ac8f2bb05792 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/accordion_with_icon.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/accordion_with_icon.tsx
@@ -15,13 +15,14 @@ import {
 } from '@elastic/eui';
 
 interface AccordionWithIconProps
-  extends Omit<EuiAccordionProps, 'buttonContent' | 'buttonProps' | 'borders' | 'paddingSize'> {
+  extends Omit<EuiAccordionProps, 'buttonContent' | 'buttonProps' | 'paddingSize'> {
   title: string;
   icon: React.ReactNode;
 }
 export const AccordionWithIcon: FunctionComponent<AccordionWithIconProps> = ({
   title,
   icon,
+  borders = 'horizontal',
   children,
   ...rest
 }) => {
@@ -39,7 +40,7 @@ export const AccordionWithIcon: FunctionComponent<AccordionWithIconProps> = ({
         </EuiFlexGroup>
       }
       buttonProps={{ paddingSize: 'l' }}
-      borders="horizontal"
+      borders={borders}
       paddingSize="none"
     >
       <div css={{ paddingLeft: 36, paddingBottom: 24 }}>{children}</div>
diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx
index fb68611a74b7d..4464a4de4fe7b 100644
--- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx
+++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx
@@ -29,7 +29,7 @@ export function FrameInformationAIAssistant({ frame }: Props) {
         instructions: `The library is: ${library}
         The function is: ${functionName}
 
-        Your have two tasks. Your first task is to desribe what the library is and what its use cases are, and to
+        You have two tasks. Your first task is to desribe what the library is and what its use cases are, and to
         describe what the function does. The output format should look as follows:
 
         Library description: Provide a concise description of the library
diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts
index 52fa6e4da10ea..42edbea3394e0 100644
--- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts
+++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts
@@ -19,12 +19,12 @@ export const useOsqueryPolicies = () => {
   return useQuery(
     ['osqueryPolicies'],
     () =>
-      http.get<{ items: Array<{ policy_id: string }> }>(
+      http.get<{ items: Array<{ policy_id: string; policy_ids: string[] }> }>(
         '/internal/osquery/fleet_wrapper/package_policies',
         { version: API_VERSIONS.internal.v1 }
       ),
     {
-      select: (response) => uniq<string>(response.items.map((p) => p.policy_id)),
+      select: (response) => uniq<string>(response.items.flatMap((p) => p.policy_ids)),
       onSuccess: () => setErrorToast(),
       onError: (error: Error) =>
         setErrorToast(error, {
diff --git a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
index bf6ce0dcc73e8..50d4e9007c634 100644
--- a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
+++ b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
@@ -14,25 +14,29 @@ import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kiba
 
 interface NavigationButtonsProps {
   isDisabled?: boolean;
-  agentPolicyId?: string | null;
+  agentPolicyIds?: string[];
 }
 
 const NavigationButtonsComponent: React.FC<NavigationButtonsProps> = ({
   isDisabled = false,
-  agentPolicyId,
+  agentPolicyIds,
 }) => {
   const {
     application: { getUrlForApp, navigateToApp },
   } = useKibana().services;
 
+  const agentPolicyIdsQueryParam = useMemo(
+    () => agentPolicyIds?.map((id) => `agentPolicyId=${id}`).join('&'),
+    [agentPolicyIds]
+  );
   const liveQueryHref = useMemo(
     () =>
       getUrlForApp(PLUGIN_ID, {
-        path: agentPolicyId
-          ? `/live_queries/new?agentPolicyId=${agentPolicyId}`
+        path: agentPolicyIds?.length
+          ? `/live_queries/new?${agentPolicyIdsQueryParam}`
           : '/live_queries/new',
       }),
-    [agentPolicyId, getUrlForApp]
+    [agentPolicyIdsQueryParam, agentPolicyIds?.length, getUrlForApp]
   );
 
   const liveQueryClick = useCallback(
@@ -40,13 +44,13 @@ const NavigationButtonsComponent: React.FC<NavigationButtonsProps> = ({
       if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
         event.preventDefault();
         navigateToApp(PLUGIN_ID, {
-          path: agentPolicyId
-            ? `/live_queries/new?agentPolicyId=${agentPolicyId}`
+          path: agentPolicyIds?.length
+            ? `/live_queries/new?${agentPolicyIdsQueryParam}`
             : '/live_queries/new',
         });
       }
     },
-    [agentPolicyId, navigateToApp]
+    [agentPolicyIdsQueryParam, agentPolicyIds?.length, navigateToApp]
   );
 
   const packsHref = getUrlForApp(PLUGIN_ID, {
diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
index 732c47fcdcfa7..50dfe808139dc 100644
--- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
+++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
@@ -146,14 +146,21 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
     policy?: PackagePolicyEditExtensionComponentProps['policy'];
   }
 >(({ onChange, policy, newPolicy }) => {
-  const [policyAgentsCount, setPolicyAgentsCount] = useState<number | null>(null);
-  const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | null>(null);
+  const [agentlessPolicyIds, setAgentlessPolicyIds] = useState<string[]>([]);
+  const [agentPolicies, setAgentPolicies] = useState<AgentPolicy[]>([]);
   const [editMode] = useState(!!policy);
   const {
     application: { getUrlForApp },
     http,
   } = useKibana().services;
 
+  const policyIdsWithAgents = useMemo(
+    () =>
+      agentlessPolicyIds?.length
+        ? policy?.policy_ids.filter((id) => !agentlessPolicyIds.includes(id))
+        : policy?.policy_ids,
+    [agentlessPolicyIds, policy?.policy_ids]
+  );
   const { form: configForm } = useForm({
     defaultValue: {
       config: JSON.stringify(get(newPolicy, 'inputs[0].config.osquery.value', {}), null, 2),
@@ -185,13 +192,16 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
   const [{ config }] = useFormData({ form: configForm, watch: 'config' });
   const { isValid, setFieldValue } = configForm;
 
-  const agentsLinkHref = useMemo(() => {
-    if (!policy?.policy_id) return '#';
+  const agentsLinkHref = useCallback(
+    (policyId) => {
+      if (!policy?.policy_ids?.length) return '#';
 
-    return getUrlForApp(PLUGIN_ID, {
-      path: pagePathGetters.policy_details({ policyId: policy?.policy_id })[1],
-    });
-  }, [getUrlForApp, policy?.policy_id]);
+      return getUrlForApp(PLUGIN_ID, {
+        path: pagePathGetters.policy_details({ policyId })[1],
+      });
+    },
+    [getUrlForApp, policy?.policy_ids?.length]
+  );
 
   const handleConfigUpload = useCallback(
     (newConfig: any) => {
@@ -248,42 +258,57 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
   );
 
   useEffect(() => {
-    if (editMode && policyAgentsCount === null) {
+    const policyIdsWithNoAgent: string[] = [];
+    if (editMode && !agentlessPolicyIds?.length) {
       const fetchAgentsCount = async () => {
         try {
-          const response = await http.fetch<{ results: { total: number } }>(
-            agentRouteService.getStatusPath(),
-            {
-              query: {
-                policyId: policy?.policy_id,
-              },
-            }
-          );
-          if (response.results) {
-            setPolicyAgentsCount(response.results.total);
+          if (policy?.policy_ids?.length) {
+            await Promise.all(
+              policy.policy_ids.map(async (id: string) => {
+                const response = await http.fetch<{ results: { total: number } }>(
+                  agentRouteService.getStatusPath(),
+                  {
+                    query: {
+                      policyId: id,
+                    },
+                  }
+                );
+                if (response.results.total === 0) {
+                  policyIdsWithNoAgent.push(id);
+                }
+              })
+            );
+            setAgentlessPolicyIds(policyIdsWithNoAgent);
           }
           // eslint-disable-next-line no-empty
         } catch (e) {}
       };
 
       const fetchAgentPolicyDetails = async () => {
-        if (policy?.policy_id) {
+        if (policyIdsWithNoAgent?.length) {
+          const policiesWithoutAgent: AgentPolicy[] = [];
           try {
-            const response = await http.fetch<{ item: AgentPolicy }>(
-              agentPolicyRouteService.getInfoPath(policy?.policy_id)
+            await Promise.all(
+              policyIdsWithNoAgent.map(async (id) => {
+                const response = await http.fetch<{ item: AgentPolicy }>(
+                  agentPolicyRouteService.getInfoPath(id)
+                );
+                if (response.item) {
+                  policiesWithoutAgent.push(response.item);
+                }
+              })
             );
-            if (response.item) {
-              setAgentPolicy(response.item);
+            if (policiesWithoutAgent.length) {
+              setAgentPolicies(policiesWithoutAgent);
             }
             // eslint-disable-next-line no-empty
           } catch (e) {}
         }
       };
 
-      fetchAgentsCount();
-      fetchAgentPolicyDetails();
+      fetchAgentsCount().then(() => fetchAgentPolicyDetails());
     }
-  }, [editMode, http, policy?.policy_id, policyAgentsCount]);
+  }, [editMode, http, agentlessPolicyIds?.length, agentlessPolicyIds, policy?.policy_ids]);
 
   useEffect(() => {
     /*
@@ -363,21 +388,30 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
   return (
     <>
       {!editMode ? <DisabledCallout /> : null}
-      {policyAgentsCount === 0 ? (
+      {agentlessPolicyIds?.length ? (
         <>
           <EuiFlexGroup>
             <EuiFlexItem>
               <EuiCallOut title="No agents in the policy" color="warning" iconType="help">
                 <p>
-                  {`Fleet has detected that you have not assigned yet any agent to the `}
-                  {
-                    <EuiLink href={agentsLinkHref}>
-                      {agentPolicy?.name ?? policy?.policy_id}
-                    </EuiLink>
-                  }
+                  {i18n.translate(
+                    'xpack.osquery.fleetIntegration.osqueryConfig.noAgentsWarningMessage',
+                    {
+                      defaultMessage:
+                        'Fleet has detected that you have not assigned yet any agent to the ',
+                    }
+                  )}
+                  {agentPolicies?.map((agentPolicy, index) => (
+                    <React.Fragment key={agentPolicy.id}>
+                      <EuiLink href={agentsLinkHref(agentPolicy.id)}>
+                        {agentPolicy.name || agentPolicy?.id}
+                      </EuiLink>
+                      {index < agentPolicies.length - 1 && `, `}
+                    </React.Fragment>
+                  ))}
                   {`. `}
                   <br />
-                  <strong>{`Only agents within the policy with active Osquery Manager integration support the functionality presented below.`}</strong>
+                  <strong>{`Only agents within the policies with active Osquery Manager integration support the functionality presented below.`}</strong>
                 </p>
               </EuiCallOut>
             </EuiFlexItem>
@@ -385,10 +419,9 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
           <EuiSpacer />
         </>
       ) : null}
-
       {!permissionDenied && (
         <>
-          <NavigationButtons isDisabled={!editMode} agentPolicyId={policy?.policy_id} />
+          <NavigationButtons isDisabled={!editMode} agentPolicyIds={policyIdsWithAgents} />
           <EuiSpacer size="xxl" />
           <EuiAccordion
             css={euiAccordionCss}
diff --git a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
index 1afd36e406daf..f99e330addfc7 100644
--- a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx
@@ -11,6 +11,7 @@ import React, { useEffect, useMemo, useState } from 'react';
 import { useHistory, useLocation } from 'react-router-dom';
 import qs from 'query-string';
 
+import { isArray } from 'lodash';
 import { WithHeaderLayout } from '../../../components/layouts';
 import { useRouterNavigate } from '../../../common/lib/kibana';
 import { LiveQuery } from '../../../live_queries';
@@ -30,7 +31,11 @@ const NewLiveQueryPageComponent = () => {
   const agentPolicyIds = useMemo(() => {
     const queryParams = qs.parse(location.search);
 
-    return queryParams?.agentPolicyId ? ([queryParams?.agentPolicyId] as string[]) : undefined;
+    return queryParams?.agentPolicyId
+      ? isArray(queryParams?.agentPolicyId)
+        ? queryParams?.agentPolicyId
+        : [queryParams?.agentPolicyId]
+      : undefined;
   }, [location.search]);
 
   useEffect(() => {
@@ -68,7 +73,7 @@ const NewLiveQueryPageComponent = () => {
 
   return (
     <WithHeaderLayout leftColumn={LeftColumn}>
-      <LiveQuery agentPolicyIds={agentPolicyIds} {...initialFormData} />
+      <LiveQuery {...initialFormData} agentPolicyIds={agentPolicyIds} />
     </WithHeaderLayout>
   );
 };
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx
index 0582cc13eb92b..5334143f27047 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/legacy_url_conflict_callout.test.tsx
@@ -31,6 +31,7 @@ const mockSpacesApi: SpacesApi = {
     useSpaces: jest.fn(),
   },
   hasOnlyDefaultSpace: false,
+  isSolutionViewEnabled: true,
 };
 
 describe('<LegacyUrlConflictCallOut />', () => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts
index d7971cce43f80..e706f5fda4b39 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_redirect_legacy_url.test.ts
@@ -32,6 +32,7 @@ const mockSpacesApi: SpacesApi = {
     useSpaces: jest.fn(),
   },
   hasOnlyDefaultSpace: false,
+  isSolutionViewEnabled: true,
 };
 
 describe('useLegacyUrlRedirect', () => {
diff --git a/x-pack/plugins/security_solution/scripts/quickstart/modules/data/index.ts b/x-pack/plugins/security_solution/scripts/quickstart/modules/data/index.ts
index d4ae7a0f52b26..83c55cc29178c 100644
--- a/x-pack/plugins/security_solution/scripts/quickstart/modules/data/index.ts
+++ b/x-pack/plugins/security_solution/scripts/quickstart/modules/data/index.ts
@@ -29,6 +29,30 @@ export const buildLargeDocument = ({
   return doc;
 };
 
+export const buildLargeNestedDocument = ({
+  fieldsPerObject,
+  levels,
+  fieldSize,
+}: {
+  fieldsPerObject: number;
+  levels: number;
+  fieldSize: number;
+}): Record<string, unknown> => {
+  if (levels === 1) {
+    return buildLargeDocument({ numFields: fieldsPerObject, fieldSize });
+  } else {
+    const doc: Record<string, unknown> = {};
+    range(fieldsPerObject).forEach((idx) => {
+      doc[`level_${levels}_field${idx}`] = buildLargeNestedDocument({
+        fieldsPerObject,
+        levels: levels - 1,
+        fieldSize,
+      });
+    });
+    return doc;
+  }
+};
+
 export const addTimestampToDoc = ({
   timestamp = new Date(),
   doc,
diff --git a/x-pack/plugins/security_solution/scripts/quickstart/modules/mappings/index.ts b/x-pack/plugins/security_solution/scripts/quickstart/modules/mappings/index.ts
index 4448d0cee6897..d061d86fe37dd 100644
--- a/x-pack/plugins/security_solution/scripts/quickstart/modules/mappings/index.ts
+++ b/x-pack/plugins/security_solution/scripts/quickstart/modules/mappings/index.ts
@@ -14,7 +14,7 @@ import type {
 import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
 import { ecsFieldMap } from '@kbn/alerts-as-data-utils';
 
-export const getEcsMapping = () => mappingFromFieldMap(ecsFieldMap);
+export const getEcsMapping = () => mappingFromFieldMap(ecsFieldMap, false);
 
 export interface GenerateLargeMappingPropertiesProps {
   size: number;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
index 2d3a4cd40b89e..1e5e70a37ae5f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { isEmpty } from 'lodash';
+import { isEmpty, partition } from 'lodash';
 import agent from 'elastic-apm-node';
 
 import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
@@ -375,8 +375,17 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
             );
 
             const legacySignalFields: string[] = Object.keys(aadFieldConversion);
+            const [ignoreFieldsRegexes, ignoreFieldsStandard] = partition(
+              [...ignoreFields, ...legacySignalFields],
+              (field: string) => field.startsWith('/') && field.endsWith('/')
+            );
+            const ignoreFieldsObject: Record<string, boolean> = {};
+            ignoreFieldsStandard.forEach((field) => {
+              ignoreFieldsObject[field] = true;
+            });
             const wrapHits = wrapHitsFactory({
-              ignoreFields: [...ignoreFields, ...legacySignalFields],
+              ignoreFields: ignoreFieldsObject,
+              ignoreFieldsRegexes,
               mergeStrategy,
               completeRule,
               spaceId,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts
index 2675c3996e865..d865ae6232005 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/build_alert_group_from_sequence.ts
@@ -12,8 +12,8 @@ import { getAlertDetailsUrl } from '../../../../../common/utils/alert_detail_pat
 import { DEFAULT_ALERTS_INDEX } from '../../../../../common/constants';
 import type { ConfigType } from '../../../../config';
 import type { Ancestor, SignalSource, SignalSourceHit } from '../types';
-import { buildAlert, buildAncestors, generateAlertId } from '../factories/utils/build_alert';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { buildAlertFields, buildAncestors, generateAlertId } from '../factories/utils/build_alert';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 import type { EqlSequence } from '../../../../../common/detection_engine/types';
 import { generateBuildingBlockIds } from '../factories/utils/generate_building_block_ids';
 import type { BuildReasonMessage } from '../utils/reason_formatters';
@@ -59,20 +59,21 @@ export const buildAlertGroupFromSequence = (
   let baseAlerts: BaseFieldsLatest[] = [];
   try {
     baseAlerts = sequence.events.map((event) =>
-      buildBulkBody(
+      transformHitToAlert({
         spaceId,
         completeRule,
-        event,
+        doc: event,
         mergeStrategy,
-        [],
-        false,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+        applyOverrides: false,
         buildReasonMessage,
         indicesToQuery,
         alertTimestampOverride,
         ruleExecutionLogger,
-        'placeholder-alert-uuid', // This is overriden below
-        publicBaseUrl
-      )
+        alertUuid: 'placeholder-alert-uuid', // This is overriden below
+        publicBaseUrl,
+      })
     );
   } catch (error) {
     ruleExecutionLogger.error(error);
@@ -153,16 +154,16 @@ export const buildAlertRoot = (
     severity: completeRule.ruleParams.severity,
     mergedDoc: mergedAlerts as SignalSourceHit,
   });
-  const doc = buildAlert(
-    wrappedBuildingBlocks,
+  const doc = buildAlertFields({
+    docs: wrappedBuildingBlocks,
     completeRule,
     spaceId,
     reason,
     indicesToQuery,
-    'placeholder-uuid', // These will be overriden below
+    alertUuid: 'placeholder-uuid', // These will be overriden below
     publicBaseUrl, // Not necessary now, but when the ID is created ahead of time this can be passed
-    alertTimestampOverride
-  );
+    alertTimestampOverride,
+  });
   const alertId = generateAlertId(doc);
   const alertUrl = getAlertDetailsUrl({
     alertId,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_esql_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_esql_alerts.ts
index b0fa2fd6638fa..762f3cff4ce45 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_esql_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_esql_alerts.ts
@@ -16,7 +16,7 @@ import type { ConfigType } from '../../../../config';
 import type { CompleteRule, EsqlRuleParams } from '../../rule_schema';
 import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 import type { SignalSource } from '../types';
 import { generateAlertId } from './utils';
 
@@ -55,20 +55,21 @@ export const wrapEsqlAlerts = ({
       index: i,
     });
 
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      event,
+      doc: event,
       mergeStrategy,
-      [],
-      true,
-      buildReasonMessageForNewTermsAlert,
-      [],
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageForNewTermsAlert,
+      indicesToQuery: [],
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_suppressed_esql_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_suppressed_esql_alerts.ts
index 7b180d1adfa62..2a6e9a42acf28 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_suppressed_esql_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/wrap_suppressed_esql_alerts.ts
@@ -19,7 +19,7 @@ import type { ConfigType } from '../../../../config';
 import type { CompleteRule, EsqlRuleParams } from '../../rule_schema';
 import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 import type { SignalSource } from '../types';
 import { getSuppressionAlertFields, getSuppressionTerms } from '../utils';
 import { generateAlertId } from './utils';
@@ -73,20 +73,21 @@ export const wrapSuppressedEsqlAlerts = ({
 
       const instanceId = objectHash([suppressionTerms, completeRule.alertId, spaceId]);
 
-      const baseAlert: BaseFieldsLatest = buildBulkBody(
+      const baseAlert: BaseFieldsLatest = transformHitToAlert({
         spaceId,
         completeRule,
-        event,
+        doc: event,
         mergeStrategy,
-        [],
-        true,
-        buildReasonMessageForNewTermsAlert,
-        [],
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+        applyOverrides: true,
+        buildReasonMessage: buildReasonMessageForNewTermsAlert,
+        indicesToQuery: [],
         alertTimestampOverride,
         ruleExecutionLogger,
-        id,
-        publicBaseUrl
-      );
+        alertUuid: id,
+        publicBaseUrl,
+      });
 
       return {
         _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/__snapshots__/build_alert.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/__snapshots__/build_alert.test.ts.snap
new file mode 100644
index 0000000000000..17d8ceb0c50b6
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/__snapshots__/build_alert.test.ts.snap
@@ -0,0 +1,223 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`buildAlertFields it creates the expected alert fields 1`] = `
+Object {
+  "@timestamp": "2020-01-01T00:00:00.000Z",
+  "event.kind": "signal",
+  "host.asset.criticality": undefined,
+  "host.risk.calculated_level": undefined,
+  "host.risk.calculated_score_norm": undefined,
+  "kibana.alert.ancestors": Array [
+    Object {
+      "depth": 0,
+      "id": "d5e8eb51-a6a0-456d-8a15-4b79bfec3d71",
+      "index": "myFakeSignalIndex",
+      "rule": undefined,
+      "type": "event",
+    },
+  ],
+  "kibana.alert.building_block_type": "default",
+  "kibana.alert.depth": 1,
+  "kibana.alert.host.criticality_level": undefined,
+  "kibana.alert.original_time": "2020-04-20T21:27:45.000Z",
+  "kibana.alert.reason": "test reason",
+  "kibana.alert.risk_score": 50,
+  "kibana.alert.rule.actions": Array [],
+  "kibana.alert.rule.author": Array [
+    "Elastic",
+  ],
+  "kibana.alert.rule.building_block_type": "default",
+  "kibana.alert.rule.consumer": "siem",
+  "kibana.alert.rule.created_at": "2020-03-27T22:55:59.577Z",
+  "kibana.alert.rule.created_by": "sample user",
+  "kibana.alert.rule.description": "Detecting root and admin users",
+  "kibana.alert.rule.enabled": true,
+  "kibana.alert.rule.exceptions_list": Array [
+    Object {
+      "id": "some_uuid",
+      "list_id": "list_id_single",
+      "namespace_type": "single",
+      "type": "detection",
+    },
+    Object {
+      "id": "endpoint_list",
+      "list_id": "endpoint_list",
+      "namespace_type": "agnostic",
+      "type": "endpoint",
+    },
+  ],
+  "kibana.alert.rule.false_positives": Array [],
+  "kibana.alert.rule.from": "now-6m",
+  "kibana.alert.rule.immutable": false,
+  "kibana.alert.rule.indices": Array [],
+  "kibana.alert.rule.interval": "5m",
+  "kibana.alert.rule.license": "Elastic License",
+  "kibana.alert.rule.max_signals": 10000,
+  "kibana.alert.rule.meta.someMeta": "someField",
+  "kibana.alert.rule.name": "rule-name",
+  "kibana.alert.rule.namespace": undefined,
+  "kibana.alert.rule.note": "# Investigative notes",
+  "kibana.alert.rule.parameters": Object {
+    "alert_suppression": undefined,
+    "author": Array [
+      "Elastic",
+    ],
+    "building_block_type": "default",
+    "data_view_id": undefined,
+    "description": "Detecting root and admin users",
+    "exceptions_list": Array [
+      Object {
+        "id": "some_uuid",
+        "list_id": "list_id_single",
+        "namespace_type": "single",
+        "type": "detection",
+      },
+      Object {
+        "id": "endpoint_list",
+        "list_id": "endpoint_list",
+        "namespace_type": "agnostic",
+        "type": "endpoint",
+      },
+    ],
+    "false_positives": Array [],
+    "filters": Array [
+      Object {
+        "query": Object {
+          "match_phrase": Object {
+            "host.name": "some-host",
+          },
+        },
+      },
+    ],
+    "from": "now-6m",
+    "immutable": false,
+    "index": Array [
+      "auditbeat-*",
+      "filebeat-*",
+      "packetbeat-*",
+      "winlogbeat-*",
+    ],
+    "investigation_fields": undefined,
+    "language": "kuery",
+    "license": "Elastic License",
+    "max_signals": 10000,
+    "meta": Object {
+      "someMeta": "someField",
+    },
+    "namespace": undefined,
+    "note": "# Investigative notes",
+    "query": "user.name: root or user.name: admin",
+    "references": Array [
+      "http://example.com",
+      "https://example.com",
+    ],
+    "related_integrations": Array [],
+    "required_fields": Array [],
+    "response_actions": undefined,
+    "risk_score": 50,
+    "risk_score_mapping": Array [],
+    "rule_id": "rule-1",
+    "rule_name_override": undefined,
+    "rule_source": Object {
+      "type": "internal",
+    },
+    "saved_id": undefined,
+    "setup": "",
+    "severity": "high",
+    "severity_mapping": Array [],
+    "threat": Array [
+      Object {
+        "framework": "MITRE ATT&CK",
+        "tactic": Object {
+          "id": "TA0000",
+          "name": "test tactic",
+          "reference": "https://attack.mitre.org/tactics/TA0000/",
+        },
+        "technique": Array [
+          Object {
+            "id": "T0000",
+            "name": "test technique",
+            "reference": "https://attack.mitre.org/techniques/T0000/",
+            "subtechnique": Array [
+              Object {
+                "id": "T0000.000",
+                "name": "test subtechnique",
+                "reference": "https://attack.mitre.org/techniques/T0000/000/",
+              },
+            ],
+          },
+        ],
+      },
+    ],
+    "timeline_id": "some-timeline-id",
+    "timeline_title": "some-timeline-title",
+    "timestamp_override": undefined,
+    "timestamp_override_fallback_disabled": undefined,
+    "to": "now",
+    "type": "query",
+    "version": 1,
+  },
+  "kibana.alert.rule.references": Array [
+    "http://example.com",
+    "https://example.com",
+  ],
+  "kibana.alert.rule.risk_score": 50,
+  "kibana.alert.rule.risk_score_mapping": Array [],
+  "kibana.alert.rule.rule_id": "rule-1",
+  "kibana.alert.rule.rule_name_override": undefined,
+  "kibana.alert.rule.severity": "high",
+  "kibana.alert.rule.severity_mapping": Array [],
+  "kibana.alert.rule.tags": Array [
+    "some fake tag 1",
+    "some fake tag 2",
+  ],
+  "kibana.alert.rule.threat": Array [
+    Object {
+      "framework": "MITRE ATT&CK",
+      "tactic": Object {
+        "id": "TA0000",
+        "name": "test tactic",
+        "reference": "https://attack.mitre.org/tactics/TA0000/",
+      },
+      "technique": Array [
+        Object {
+          "id": "T0000",
+          "name": "test technique",
+          "reference": "https://attack.mitre.org/techniques/T0000/",
+          "subtechnique": Array [
+            Object {
+              "id": "T0000.000",
+              "name": "test subtechnique",
+              "reference": "https://attack.mitre.org/techniques/T0000/000/",
+            },
+          ],
+        },
+      ],
+    },
+  ],
+  "kibana.alert.rule.throttle": "no_actions",
+  "kibana.alert.rule.timeline_id": "some-timeline-id",
+  "kibana.alert.rule.timeline_title": "some-timeline-title",
+  "kibana.alert.rule.timestamp_override": undefined,
+  "kibana.alert.rule.to": "now",
+  "kibana.alert.rule.type": "query",
+  "kibana.alert.rule.updated_at": "2020-03-27T22:55:59.577Z",
+  "kibana.alert.rule.updated_by": "sample user",
+  "kibana.alert.rule.uuid": "04128c15-0d1b-4716-a4c5-46997ac7f3bd",
+  "kibana.alert.rule.version": 1,
+  "kibana.alert.severity": "high",
+  "kibana.alert.status": "active",
+  "kibana.alert.url": "test/url/app/security/alerts/redirect/test-uuid?index=.alerts-security.alerts-default&timestamp=2020-01-01T00:00:00.000Z",
+  "kibana.alert.user.criticality_level": undefined,
+  "kibana.alert.uuid": "test-uuid",
+  "kibana.alert.workflow_assignee_ids": Array [],
+  "kibana.alert.workflow_status": "open",
+  "kibana.alert.workflow_tags": Array [],
+  "kibana.space_ids": Array [
+    "default",
+  ],
+  "user.asset.criticality": undefined,
+  "user.risk.calculated_level": undefined,
+  "user.risk.calculated_score_norm": undefined,
+}
+`;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts
index 4aaa0189eefc4..b7f83106ea0b9 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts
@@ -8,42 +8,20 @@
 import {
   ALERT_INSTANCE_ID,
   ALERT_NAMESPACE,
-  ALERT_REASON,
-  ALERT_RISK_SCORE,
-  ALERT_RULE_CONSUMER,
-  ALERT_RULE_NAMESPACE,
-  ALERT_RULE_PARAMETERS,
   ALERT_RULE_UUID,
-  ALERT_SEVERITY,
-  ALERT_STATUS,
-  ALERT_STATUS_ACTIVE,
-  ALERT_URL,
   ALERT_UUID,
-  ALERT_WORKFLOW_ASSIGNEE_IDS,
-  ALERT_WORKFLOW_STATUS,
-  ALERT_WORKFLOW_TAGS,
   EVENT_ACTION,
   EVENT_KIND,
   EVENT_MODULE,
-  SPACE_IDS,
   TIMESTAMP,
 } from '@kbn/rule-data-utils';
 import { flattenWithPrefix } from '@kbn/securitysolution-rules';
 
 import { sampleDocNoSortIdWithTimestamp } from '../../__mocks__/es_results';
-import { buildAlert, buildParent, buildAncestors, additionalAlertFields } from './build_alert';
+import { buildAlertFields, buildParent, buildAncestors } from './build_alert';
 import type { Ancestor, SignalSourceHit } from '../../types';
-import { getListArrayMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock';
-import { DEFAULT_ALERTS_INDEX, SERVER_APP_ID } from '../../../../../../common/constants';
 import { EVENT_DATASET } from '../../../../../../common/cti/constants';
-import {
-  ALERT_ANCESTORS,
-  ALERT_ORIGINAL_TIME,
-  ALERT_DEPTH,
-  ALERT_ORIGINAL_EVENT,
-  ALERT_BUILDING_BLOCK_TYPE,
-  ALERT_RULE_INDICES,
-} from '../../../../../../common/field_maps/field_names';
+import { ALERT_ANCESTORS, ALERT_DEPTH } from '../../../../../../common/field_maps/field_names';
 import { getCompleteRuleMock, getQueryRuleParams } from '../../../rule_schema/mocks';
 
 type SignalDoc = SignalSourceHit & {
@@ -51,408 +29,30 @@ type SignalDoc = SignalSourceHit & {
   _source: Required<SignalSourceHit>['_source'] & { [TIMESTAMP]: string };
 };
 
-const SPACE_ID = 'space';
-const reason = 'alert reasonable reason';
-const publicBaseUrl = 'testKibanaBasePath.com';
-const alertUuid = 'test-uuid';
-
-describe('buildAlert', () => {
+describe('buildAlertFields', () => {
   beforeEach(() => {
     jest.clearAllMocks();
   });
 
-  test('it builds an alert as expected without original_event if event does not exist', () => {
-    const doc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
-    delete doc._source.event;
-    const completeRule = getCompleteRuleMock(getQueryRuleParams());
-    const alert = {
-      ...buildAlert(
-        [doc],
-        completeRule,
-        SPACE_ID,
-        reason,
-        completeRule.ruleParams.index as string[],
-        alertUuid,
-        publicBaseUrl,
-        undefined
-      ),
-      ...additionalAlertFields(doc),
-    };
-    const timestamp = alert[TIMESTAMP];
-    const expectedAlertUrl = `${publicBaseUrl}/s/${SPACE_ID}/app/security/alerts/redirect/${alertUuid}?index=${DEFAULT_ALERTS_INDEX}-${SPACE_ID}&timestamp=${timestamp}`;
-    const expected = {
-      [TIMESTAMP]: timestamp,
-      [EVENT_KIND]: 'signal',
-      [SPACE_IDS]: [SPACE_ID],
-      [ALERT_RULE_CONSUMER]: SERVER_APP_ID,
-      [ALERT_ANCESTORS]: [
-        {
-          id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71',
-          type: 'event',
-          index: 'myFakeSignalIndex',
-          depth: 0,
-        },
-      ],
-      [ALERT_ORIGINAL_TIME]: '2020-04-20T21:27:45.000Z',
-      [ALERT_REASON]: 'alert reasonable reason',
-      [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
-      [ALERT_WORKFLOW_STATUS]: 'open',
-      [ALERT_BUILDING_BLOCK_TYPE]: 'default',
-      [ALERT_SEVERITY]: 'high',
-      [ALERT_RISK_SCORE]: 50,
-      [ALERT_RULE_PARAMETERS]: {
-        description: 'Detecting root and admin users',
-        risk_score: 50,
-        severity: 'high',
-        building_block_type: 'default',
-        note: '# Investigative notes',
-        license: 'Elastic License',
-        timeline_id: 'some-timeline-id',
-        timeline_title: 'some-timeline-title',
-        meta: { someMeta: 'someField' },
-        author: ['Elastic'],
-        false_positives: [],
-        from: 'now-6m',
-        rule_id: 'rule-1',
-        max_signals: 10000,
-        risk_score_mapping: [],
-        severity_mapping: [],
-        threat: [
-          {
-            framework: 'MITRE ATT&CK',
-            tactic: {
-              id: 'TA0000',
-              name: 'test tactic',
-              reference: 'https://attack.mitre.org/tactics/TA0000/',
-            },
-            technique: [
-              {
-                id: 'T0000',
-                name: 'test technique',
-                reference: 'https://attack.mitre.org/techniques/T0000/',
-                subtechnique: [
-                  {
-                    id: 'T0000.000',
-                    name: 'test subtechnique',
-                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
-                  },
-                ],
-              },
-            ],
-          },
-        ],
-        to: 'now',
-        references: ['http://example.com', 'https://example.com'],
-        related_integrations: [],
-        required_fields: [],
-        setup: '',
-        version: 1,
-        exceptions_list: [
-          {
-            id: 'some_uuid',
-            list_id: 'list_id_single',
-            namespace_type: 'single',
-            type: 'detection',
-          },
-          {
-            id: 'endpoint_list',
-            list_id: 'endpoint_list',
-            namespace_type: 'agnostic',
-            type: 'endpoint',
-          },
-        ],
-        immutable: false,
-        rule_source: {
-          type: 'internal',
-        },
-        type: 'query',
-        language: 'kuery',
-        index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
-        query: 'user.name: root or user.name: admin',
-        filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
-        investigation_fields: undefined,
-      },
-      [ALERT_RULE_INDICES]: completeRule.ruleParams.index,
-      ...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
-        actions: [],
-        author: ['Elastic'],
-        uuid: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
-        building_block_type: 'default',
-        created_at: '2020-03-27T22:55:59.577Z',
-        updated_at: '2020-03-27T22:55:59.577Z',
-        created_by: 'sample user',
-        description: 'Detecting root and admin users',
-        enabled: true,
-        false_positives: [],
-        from: 'now-6m',
-        immutable: false,
-        license: 'Elastic License',
-        meta: {
-          someMeta: 'someField',
-        },
-        name: 'rule-name',
-        note: '# Investigative notes',
-        references: ['http://example.com', 'https://example.com'],
-        severity: 'high',
-        severity_mapping: [],
-        updated_by: 'sample user',
-        tags: ['some fake tag 1', 'some fake tag 2'],
-        to: 'now',
-        type: 'query',
-        threat: [
-          {
-            framework: 'MITRE ATT&CK',
-            tactic: {
-              id: 'TA0000',
-              name: 'test tactic',
-              reference: 'https://attack.mitre.org/tactics/TA0000/',
-            },
-            technique: [
-              {
-                id: 'T0000',
-                name: 'test technique',
-                reference: 'https://attack.mitre.org/techniques/T0000/',
-                subtechnique: [
-                  {
-                    id: 'T0000.000',
-                    name: 'test subtechnique',
-                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
-                  },
-                ],
-              },
-            ],
-          },
-        ],
-        version: 1,
-        max_signals: 10000,
-        risk_score: 50,
-        risk_score_mapping: [],
-        rule_id: 'rule-1',
-        interval: '5m',
-        exceptions_list: getListArrayMock(),
-        throttle: 'no_actions',
-        timeline_id: 'some-timeline-id',
-        timeline_title: 'some-timeline-title',
-      }),
-      [ALERT_DEPTH]: 1,
-      [ALERT_URL]: expectedAlertUrl,
-      [ALERT_UUID]: alertUuid,
-      [ALERT_WORKFLOW_TAGS]: [],
-      [ALERT_WORKFLOW_ASSIGNEE_IDS]: [],
-    };
-    expect(alert).toEqual(expected);
-  });
-
-  test('it builds an alert as expected with original_event if present', () => {
+  test('it creates the expected alert fields', () => {
     const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
-    const doc = {
-      ...sampleDoc,
-      _source: {
-        ...sampleDoc._source,
-        [EVENT_ACTION]: 'socket_opened',
-        [EVENT_DATASET]: 'socket',
-        [EVENT_KIND]: 'event',
-        [EVENT_MODULE]: 'system',
-      },
-    };
     const completeRule = getCompleteRuleMock(getQueryRuleParams());
-    const alert = {
-      ...buildAlert(
-        [doc],
-        completeRule,
-        SPACE_ID,
-        reason,
-        completeRule.ruleParams.index as string[],
-        alertUuid,
-        publicBaseUrl,
-        undefined
-      ),
-      ...additionalAlertFields(doc),
-    };
-    const timestamp = alert[TIMESTAMP];
-    const expectedAlertUrl = `${publicBaseUrl}/s/${SPACE_ID}/app/security/alerts/redirect/${alertUuid}?index=${DEFAULT_ALERTS_INDEX}-${SPACE_ID}&timestamp=${timestamp}`;
-    const expected = {
-      [TIMESTAMP]: timestamp,
-      [EVENT_KIND]: 'signal',
-      [SPACE_IDS]: [SPACE_ID],
-      [ALERT_RULE_CONSUMER]: SERVER_APP_ID,
-      [ALERT_ANCESTORS]: [
-        {
-          id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71',
-          type: 'event',
-          index: 'myFakeSignalIndex',
-          depth: 0,
-        },
-      ],
-      [ALERT_ORIGINAL_TIME]: '2020-04-20T21:27:45.000Z',
-      [ALERT_RULE_INDICES]: completeRule.ruleParams.index,
-      ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, {
-        action: 'socket_opened',
-        dataset: 'socket',
-        kind: 'event',
-        module: 'system',
-      }),
-      [ALERT_REASON]: 'alert reasonable reason',
-      [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
-      [ALERT_WORKFLOW_STATUS]: 'open',
-      [ALERT_BUILDING_BLOCK_TYPE]: 'default',
-      [ALERT_SEVERITY]: 'high',
-      [ALERT_RISK_SCORE]: 50,
-      [ALERT_RULE_PARAMETERS]: {
-        description: 'Detecting root and admin users',
-        risk_score: 50,
-        severity: 'high',
-        building_block_type: 'default',
-        note: '# Investigative notes',
-        license: 'Elastic License',
-        timeline_id: 'some-timeline-id',
-        timeline_title: 'some-timeline-title',
-        meta: { someMeta: 'someField' },
-        author: ['Elastic'],
-        false_positives: [],
-        from: 'now-6m',
-        rule_id: 'rule-1',
-        max_signals: 10000,
-        risk_score_mapping: [],
-        severity_mapping: [],
-        threat: [
-          {
-            framework: 'MITRE ATT&CK',
-            tactic: {
-              id: 'TA0000',
-              name: 'test tactic',
-              reference: 'https://attack.mitre.org/tactics/TA0000/',
-            },
-            technique: [
-              {
-                id: 'T0000',
-                name: 'test technique',
-                reference: 'https://attack.mitre.org/techniques/T0000/',
-                subtechnique: [
-                  {
-                    id: 'T0000.000',
-                    name: 'test subtechnique',
-                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
-                  },
-                ],
-              },
-            ],
-          },
-        ],
-        to: 'now',
-        references: ['http://example.com', 'https://example.com'],
-        related_integrations: [],
-        required_fields: [],
-        setup: '',
-        version: 1,
-        exceptions_list: [
-          {
-            id: 'some_uuid',
-            list_id: 'list_id_single',
-            namespace_type: 'single',
-            type: 'detection',
-          },
-          {
-            id: 'endpoint_list',
-            list_id: 'endpoint_list',
-            namespace_type: 'agnostic',
-            type: 'endpoint',
-          },
-        ],
-        immutable: false,
-        rule_source: {
-          type: 'internal',
-        },
-        type: 'query',
-        language: 'kuery',
-        index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
-        query: 'user.name: root or user.name: admin',
-        filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
-        investigation_fields: undefined,
-      },
-      ...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
-        actions: [],
-        author: ['Elastic'],
-        uuid: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
-        building_block_type: 'default',
-        created_at: '2020-03-27T22:55:59.577Z',
-        updated_at: '2020-03-27T22:55:59.577Z',
-        created_by: 'sample user',
-        description: 'Detecting root and admin users',
-        enabled: true,
-        false_positives: [],
-        from: 'now-6m',
-        immutable: false,
-        license: 'Elastic License',
-        meta: {
-          someMeta: 'someField',
-        },
-        name: 'rule-name',
-        note: '# Investigative notes',
-        references: ['http://example.com', 'https://example.com'],
-        severity: 'high',
-        severity_mapping: [],
-        updated_by: 'sample user',
-        tags: ['some fake tag 1', 'some fake tag 2'],
-        to: 'now',
-        type: 'query',
-        threat: [
-          {
-            framework: 'MITRE ATT&CK',
-            tactic: {
-              id: 'TA0000',
-              name: 'test tactic',
-              reference: 'https://attack.mitre.org/tactics/TA0000/',
-            },
-            technique: [
-              {
-                id: 'T0000',
-                name: 'test technique',
-                reference: 'https://attack.mitre.org/techniques/T0000/',
-                subtechnique: [
-                  {
-                    id: 'T0000.000',
-                    name: 'test subtechnique',
-                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
-                  },
-                ],
-              },
-            ],
-          },
-        ],
-        version: 1,
-        max_signals: 10000,
-        risk_score: 50,
-        risk_score_mapping: [],
-        rule_id: 'rule-1',
-        interval: '5m',
-        exceptions_list: getListArrayMock(),
-        throttle: 'no_actions',
-        timeline_id: 'some-timeline-id',
-        timeline_title: 'some-timeline-title',
-      }),
-      [ALERT_DEPTH]: 1,
-      [ALERT_URL]: expectedAlertUrl,
-      [ALERT_UUID]: alertUuid,
-      [ALERT_WORKFLOW_TAGS]: [],
-      [ALERT_WORKFLOW_ASSIGNEE_IDS]: [],
-    };
-    expect(alert).toEqual(expected);
+    const alertFields = buildAlertFields({
+      docs: [sampleDoc],
+      completeRule,
+      spaceId: 'default',
+      reason: 'test reason',
+      indicesToQuery: [],
+      alertUuid: 'test-uuid',
+      publicBaseUrl: 'test/url',
+      alertTimestampOverride: new Date('2020-01-01T00:00:00.000Z'),
+    });
+    expect(alertFields).toMatchSnapshot();
   });
 
   test('it builds a parent correctly if the parent does not exist', () => {
     const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
-    const doc = {
-      ...sampleDoc,
-      _source: {
-        ...sampleDoc._source,
-        [EVENT_ACTION]: 'socket_opened',
-        [EVENT_DATASET]: 'socket',
-        [EVENT_KIND]: 'event',
-        [EVENT_MODULE]: 'system',
-      },
-    };
-    const parent = buildParent(doc);
+    const parent = buildParent(sampleDoc);
     const expected: Ancestor = {
       id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71',
       type: 'event',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts
index d81fe7d020282..036ba8c9a644a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts
@@ -49,7 +49,7 @@ import { requiredOptional } from '@kbn/zod-helpers';
 import { createHash } from 'crypto';
 
 import { getAlertDetailsUrl } from '../../../../../../common/utils/alert_detail_path';
-import type { BaseSignalHit, SimpleHit } from '../../types';
+import type { SimpleHit } from '../../types';
 import type { ThresholdResult } from '../../threshold/types';
 import {
   getField,
@@ -63,8 +63,6 @@ import {
   ALERT_ANCESTORS,
   ALERT_DEPTH,
   ALERT_ORIGINAL_TIME,
-  ALERT_THRESHOLD_RESULT,
-  ALERT_ORIGINAL_EVENT,
   ALERT_BUILDING_BLOCK_TYPE,
   ALERT_RULE_ACTIONS,
   ALERT_RULE_INDICES,
@@ -97,6 +95,22 @@ import type {
   BaseFieldsLatest,
 } from '../../../../../../common/api/detection_engine/model/alerts';
 
+export interface BuildAlertFieldsProps {
+  docs: SimpleHit[];
+  completeRule: CompleteRule<RuleParams>;
+  spaceId: string | null | undefined;
+  reason: string;
+  indicesToQuery: string[];
+  alertUuid: string;
+  publicBaseUrl: string | undefined;
+  alertTimestampOverride: Date | undefined;
+  overrides?: {
+    nameOverride: string;
+    severityOverride: string;
+    riskScoreOverride: number;
+  };
+}
+
 export const generateAlertId = (alert: BaseFieldsLatest) => {
   return createHash('sha256')
     .update(
@@ -145,21 +159,17 @@ export const buildAncestors = (doc: SimpleHit): AncestorLatest[] => {
  * @param reason Human readable string summarizing alert.
  * @param indicesToQuery Array of index patterns searched by the rule.
  */
-export const buildAlert = (
-  docs: SimpleHit[],
-  completeRule: CompleteRule<RuleParams>,
-  spaceId: string | null | undefined,
-  reason: string,
-  indicesToQuery: string[],
-  alertUuid: string,
-  publicBaseUrl: string | undefined,
-  alertTimestampOverride: Date | undefined,
-  overrides?: {
-    nameOverride: string;
-    severityOverride: string;
-    riskScoreOverride: number;
-  }
-): BaseFieldsLatest => {
+export const buildAlertFields = ({
+  docs,
+  completeRule,
+  spaceId,
+  reason,
+  indicesToQuery,
+  alertUuid,
+  publicBaseUrl,
+  alertTimestampOverride,
+  overrides,
+}: BuildAlertFieldsProps): BaseFieldsLatest => {
   const parents = docs.map(buildParent);
   const depth = parents.reduce((acc, parent) => Math.max(parent.depth, acc), 0) + 1;
   const ancestors = docs.reduce(
@@ -276,28 +286,8 @@ export const buildAlert = (
   };
 };
 
-const isThresholdResult = (thresholdResult: SearchTypes): thresholdResult is ThresholdResult => {
+export const isThresholdResult = (
+  thresholdResult: SearchTypes
+): thresholdResult is ThresholdResult => {
   return typeof thresholdResult === 'object';
 };
-
-/**
- * Creates signal fields that are only available in the special case where a signal has only 1 parent signal/event.
- * We copy the original time from the document as "original_time" since we override the timestamp with the current date time.
- * @param doc The parent signal/event of the new signal to be built.
- */
-export const additionalAlertFields = (doc: BaseSignalHit) => {
-  const thresholdResult = doc._source?.threshold_result;
-  if (thresholdResult != null && !isThresholdResult(thresholdResult)) {
-    throw new Error(`threshold_result failed to validate: ${thresholdResult}`);
-  }
-  const additionalFields: Record<string, SearchTypes> = {
-    ...(thresholdResult != null ? { [ALERT_THRESHOLD_RESULT]: thresholdResult } : {}),
-  };
-
-  for (const [key, val] of Object.entries(doc._source ?? {})) {
-    if (key.startsWith('event.')) {
-      additionalFields[`${ALERT_ORIGINAL_EVENT}.${key.replace('event.', '')}`] = val;
-    }
-  }
-  return additionalFields;
-};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.test.ts
deleted file mode 100644
index b2426ceda9767..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { sampleDocWithNonEcsCompliantFields } from '../../__mocks__/es_results';
-import { buildBulkBody } from './build_bulk_body';
-import { getCompleteRuleMock, getEsqlRuleParams } from '../../../rule_schema/mocks';
-import { ruleExecutionLogMock } from '../../../rule_monitoring/mocks';
-
-const SPACE_ID = 'space';
-const publicBaseUrl = 'testKibanaBasePath.com';
-const alertUuid = 'test-uuid';
-const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
-const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
-
-describe('buildBulkBody', () => {
-  test('should strip non-ECS compliant sub-fields of `event.action` field', () => {
-    const doc = sampleDocWithNonEcsCompliantFields(docId, {
-      'event.action': 'process',
-      'event.action.keyword': 'process',
-    });
-    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
-    const buildReasonMessageStub = jest.fn();
-    const alert = buildBulkBody(
-      SPACE_ID,
-      completeRule,
-      doc,
-      'missingFields',
-      [],
-      true,
-      buildReasonMessageStub,
-      [],
-      undefined,
-      ruleExecutionLogger,
-      alertUuid,
-      publicBaseUrl
-    );
-
-    expect(alert['kibana.alert.original_event.action']).toEqual('process');
-    expect(alert['kibana.alert.original_event.action.keyword']).toBeUndefined();
-  });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts
deleted file mode 100644
index 9294cc7159c12..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { flattenWithPrefix } from '@kbn/securitysolution-rules';
-import type * as estypes from '@elastic/elasticsearch/lib/api/types';
-import { requiredOptional } from '@kbn/zod-helpers';
-
-import type { BaseHit, SearchTypes } from '../../../../../../common/detection_engine/types';
-import type { ConfigType } from '../../../../../config';
-import type { BuildReasonMessage } from '../../utils/reason_formatters';
-import { getMergeStrategy } from '../../utils/source_fields_merging/strategies';
-import type { BaseSignalHit, SignalSource, SignalSourceHit } from '../../types';
-import { additionalAlertFields, buildAlert } from './build_alert';
-import { filterSource } from './filter_source';
-import type { CompleteRule, RuleParams } from '../../../rule_schema';
-import type { IRuleExecutionLogForExecutors } from '../../../rule_monitoring';
-import { buildRuleNameFromMapping } from '../../utils/mappings/build_rule_name_from_mapping';
-import { buildSeverityFromMapping } from '../../utils/mappings/build_severity_from_mapping';
-import { buildRiskScoreFromMapping } from '../../utils/mappings/build_risk_score_from_mapping';
-import type { BaseFieldsLatest } from '../../../../../../common/api/detection_engine/model/alerts';
-import { stripNonEcsFields } from './strip_non_ecs_fields';
-
-const isSourceDoc = (
-  hit: SignalSourceHit
-): hit is BaseHit<{ '@timestamp': string; _source: SignalSource }> => {
-  return hit._source != null;
-};
-
-const buildEventTypeAlert = (doc: BaseSignalHit): Record<string, SearchTypes> => {
-  if (doc._source?.event != null && doc._source?.event instanceof Object) {
-    return flattenWithPrefix('event', doc._source?.event ?? {});
-  }
-  return {};
-};
-
-/**
- * Formats the search_after result for insertion into the signals index. We first create a
- * "best effort" merged "fields" with the "_source" object, then build the signal object,
- * then the event object, and finally we strip away any additional temporary data that was added
- * such as the "threshold_result".
- * @param completeRule The rule saved object to build overrides
- * @param doc The SignalSourceHit with "_source", "fields", and additional data such as "threshold_result"
- * @returns The body that can be added to a bulk call for inserting the signal.
- */
-export const buildBulkBody = (
-  spaceId: string | null | undefined,
-  completeRule: CompleteRule<RuleParams>,
-  doc: estypes.SearchHit<SignalSource>,
-  mergeStrategy: ConfigType['alertMergeStrategy'],
-  ignoreFields: ConfigType['alertIgnoreFields'],
-  applyOverrides: boolean,
-  buildReasonMessage: BuildReasonMessage,
-  indicesToQuery: string[],
-  alertTimestampOverride: Date | undefined,
-  ruleExecutionLogger: IRuleExecutionLogForExecutors,
-  alertUuid: string,
-  publicBaseUrl?: string
-): BaseFieldsLatest => {
-  const mergedDoc = getMergeStrategy(mergeStrategy)({ doc, ignoreFields });
-
-  const eventFields = buildEventTypeAlert(mergedDoc);
-  const { result: validatedEventFields, removed: removedEventFields } =
-    stripNonEcsFields(eventFields);
-
-  const filteredSource = filterSource(mergedDoc);
-  const { result: validatedSource, removed: removedSourceFields } =
-    stripNonEcsFields(filteredSource);
-
-  if (removedEventFields.length || removedSourceFields.length) {
-    ruleExecutionLogger?.debug(
-      'Following fields were removed from alert source as ECS non-compliant:',
-      JSON.stringify(removedSourceFields),
-      JSON.stringify(removedEventFields)
-    );
-  }
-
-  const overrides = applyOverrides
-    ? {
-        nameOverride: buildRuleNameFromMapping({
-          eventSource: mergedDoc._source ?? {},
-          ruleName: completeRule.ruleConfig.name,
-          ruleNameMapping: completeRule.ruleParams.ruleNameOverride,
-        }).ruleName,
-        severityOverride: buildSeverityFromMapping({
-          eventSource: mergedDoc._source ?? {},
-          severity: completeRule.ruleParams.severity,
-          severityMapping: completeRule.ruleParams.severityMapping,
-        }).severity,
-        riskScoreOverride: buildRiskScoreFromMapping({
-          eventSource: mergedDoc._source ?? {},
-          riskScore: completeRule.ruleParams.riskScore,
-          riskScoreMapping: requiredOptional(completeRule.ruleParams.riskScoreMapping),
-        }).riskScore,
-      }
-    : undefined;
-
-  const reason = buildReasonMessage({
-    name: overrides?.nameOverride ?? completeRule.ruleConfig.name,
-    severity: overrides?.severityOverride ?? completeRule.ruleParams.severity,
-    mergedDoc,
-  });
-
-  const thresholdResult = mergedDoc._source?.threshold_result;
-  if (isSourceDoc(mergedDoc)) {
-    return {
-      ...validatedSource,
-      ...validatedEventFields,
-      ...buildAlert(
-        [mergedDoc],
-        completeRule,
-        spaceId,
-        reason,
-        indicesToQuery,
-        alertUuid,
-        publicBaseUrl,
-        alertTimestampOverride,
-        overrides
-      ),
-      ...additionalAlertFields({
-        ...mergedDoc,
-        _source: {
-          ...validatedSource,
-          ...validatedEventFields,
-          threshold_result: thresholdResult,
-        },
-      }),
-    };
-  }
-
-  throw Error('Error building alert from source document.');
-};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.test.ts
deleted file mode 100644
index 73717a7c0a5ec..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { filterSource } from './filter_source';
-
-describe('filterSource', () => {
-  test('should remove keys starting with kibana without modifying the original doc', () => {
-    const testDoc = {
-      _index: '',
-      _id: '',
-      _source: {
-        'kibana.alert.suppression.docs_count': 5,
-        'host.name': 'test-host',
-      },
-    };
-    const filtered = filterSource(testDoc);
-    expect(filtered).toEqual({
-      'host.name': 'test-host',
-    });
-    expect(testDoc).toEqual({
-      _index: '',
-      _id: '',
-      _source: {
-        'kibana.alert.suppression.docs_count': 5,
-        'host.name': 'test-host',
-      },
-    });
-  });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts
deleted file mode 100644
index c0172207c7a60..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ALERT_THRESHOLD_RESULT } from '../../../../../../common/field_maps/field_names';
-import type { SignalSourceHit } from '../../types';
-
-export const filterSource = (doc: SignalSourceHit) => {
-  const docSource = doc._source ?? {};
-  const {
-    event,
-    kibana,
-    signal,
-    threshold_result: siemSignalsThresholdResult,
-    [ALERT_THRESHOLD_RESULT]: alertThresholdResult,
-    ...filteredSource
-  } = docSource || {
-    event: null,
-    kibana: null,
-    signal: null,
-    threshold_result: null,
-    [ALERT_THRESHOLD_RESULT]: null,
-  };
-
-  Object.keys(filteredSource).forEach((key) => {
-    if (key.startsWith('kibana')) {
-      delete filteredSource[key];
-    }
-  });
-
-  return filteredSource;
-};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.test.ts
new file mode 100644
index 0000000000000..92d02e1b3ac4a
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.test.ts
@@ -0,0 +1,438 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import {
+  ALERT_REASON,
+  ALERT_RISK_SCORE,
+  ALERT_RULE_CONSUMER,
+  ALERT_RULE_NAMESPACE,
+  ALERT_RULE_PARAMETERS,
+  ALERT_SEVERITY,
+  ALERT_STATUS,
+  ALERT_STATUS_ACTIVE,
+  ALERT_URL,
+  ALERT_UUID,
+  ALERT_WORKFLOW_ASSIGNEE_IDS,
+  ALERT_WORKFLOW_STATUS,
+  ALERT_WORKFLOW_TAGS,
+  EVENT_ACTION,
+  EVENT_KIND,
+  EVENT_MODULE,
+  SPACE_IDS,
+  TIMESTAMP,
+} from '@kbn/rule-data-utils';
+import { flattenWithPrefix } from '@kbn/securitysolution-rules';
+
+import {
+  sampleDocNoSortIdWithTimestamp,
+  sampleDocWithNonEcsCompliantFields,
+} from '../../__mocks__/es_results';
+import { getListArrayMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock';
+import { DEFAULT_ALERTS_INDEX, SERVER_APP_ID } from '../../../../../../common/constants';
+import { EVENT_DATASET } from '../../../../../../common/cti/constants';
+import {
+  ALERT_ANCESTORS,
+  ALERT_ORIGINAL_TIME,
+  ALERT_DEPTH,
+  ALERT_ORIGINAL_EVENT,
+  ALERT_BUILDING_BLOCK_TYPE,
+  ALERT_RULE_INDICES,
+} from '../../../../../../common/field_maps/field_names';
+
+import { transformHitToAlert } from './transform_hit_to_alert';
+import {
+  getCompleteRuleMock,
+  getEsqlRuleParams,
+  getQueryRuleParams,
+} from '../../../rule_schema/mocks';
+import { ruleExecutionLogMock } from '../../../rule_monitoring/mocks';
+import { get } from 'lodash';
+
+const SPACE_ID = 'space';
+const publicBaseUrl = 'testKibanaBasePath.com';
+const alertUuid = 'test-uuid';
+const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
+const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
+const buildReasonMessageStub = jest.fn();
+
+describe('transformHitToAlert', () => {
+  it('should strip non-ECS compliant sub-fields of `event.action` field', () => {
+    const doc = sampleDocWithNonEcsCompliantFields(docId, {
+      'event.action': 'process',
+      'event.action.keyword': 'process',
+    });
+    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
+
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+
+    expect(alert['kibana.alert.original_event.action']).toEqual('process');
+    expect(alert['kibana.alert.original_event.action.keyword']).toBeUndefined();
+  });
+
+  it('should unset an existing event.kind field in nested notation', () => {
+    const doc = {
+      _index: 'testindex',
+      _id: 'myId',
+      _source: {
+        event: {
+          kind: 'test-value',
+        },
+      },
+    };
+    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
+
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+
+    expect(get(alert.event, 'kind')).toEqual(undefined);
+    expect(alert['event.kind']).toEqual('signal');
+  });
+
+  it('should replace an existing event.kind in dot notation', () => {
+    const doc = {
+      _index: 'testindex',
+      _id: 'myId',
+      _source: {
+        'event.kind': 'test-value',
+      },
+    };
+    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
+
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+
+    expect(get(alert.event, 'kind')).toEqual(undefined);
+    expect(alert['event.kind']).toEqual('signal');
+  });
+
+  it('should not add an empty event object if event.kind does not exist', () => {
+    const doc = {
+      _index: 'testindex',
+      _id: 'myId',
+      _source: {
+        testField: 'testValue',
+      },
+    };
+    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
+
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+
+    expect(alert.event).toEqual(undefined);
+    expect(alert['event.kind']).toEqual('signal');
+  });
+
+  it('should only copy ECS compatible array elements from event subfields to kibana.alert.original_event', () => {
+    const doc = {
+      _index: 'testindex',
+      _id: 'myId',
+      _source: {
+        'event.action': ['process', { objectSubfield: 'test' }],
+      },
+    };
+    const completeRule = getCompleteRuleMock(getEsqlRuleParams());
+
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+
+    expect(alert['kibana.alert.original_event.action']).toEqual(['process']);
+  });
+
+  it('builds an alert as expected without original_event if event does not exist', () => {
+    const doc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
+    const completeRule = getCompleteRuleMock(getQueryRuleParams());
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+    delete doc._source.event;
+
+    const timestamp = alert[TIMESTAMP];
+    const expectedAlertUrl = `${publicBaseUrl}/s/${SPACE_ID}/app/security/alerts/redirect/${alertUuid}?index=${DEFAULT_ALERTS_INDEX}-${SPACE_ID}&timestamp=${timestamp}`;
+    const expected = {
+      [TIMESTAMP]: timestamp,
+      [EVENT_KIND]: 'signal',
+      [SPACE_IDS]: [SPACE_ID],
+      [ALERT_RULE_CONSUMER]: SERVER_APP_ID,
+      [ALERT_ANCESTORS]: [
+        {
+          id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71',
+          type: 'event',
+          index: 'myFakeSignalIndex',
+          depth: 0,
+        },
+      ],
+      [ALERT_ORIGINAL_TIME]: '2020-04-20T21:27:45.000Z',
+      [ALERT_REASON]: undefined,
+      [ALERT_STATUS]: ALERT_STATUS_ACTIVE,
+      [ALERT_WORKFLOW_STATUS]: 'open',
+      [ALERT_BUILDING_BLOCK_TYPE]: 'default',
+      [ALERT_SEVERITY]: 'high',
+      [ALERT_RISK_SCORE]: 50,
+      [ALERT_RULE_PARAMETERS]: {
+        description: 'Detecting root and admin users',
+        risk_score: 50,
+        severity: 'high',
+        building_block_type: 'default',
+        note: '# Investigative notes',
+        license: 'Elastic License',
+        timeline_id: 'some-timeline-id',
+        timeline_title: 'some-timeline-title',
+        meta: { someMeta: 'someField' },
+        author: ['Elastic'],
+        false_positives: [],
+        from: 'now-6m',
+        rule_id: 'rule-1',
+        max_signals: 10000,
+        risk_score_mapping: [],
+        severity_mapping: [],
+        threat: [
+          {
+            framework: 'MITRE ATT&CK',
+            tactic: {
+              id: 'TA0000',
+              name: 'test tactic',
+              reference: 'https://attack.mitre.org/tactics/TA0000/',
+            },
+            technique: [
+              {
+                id: 'T0000',
+                name: 'test technique',
+                reference: 'https://attack.mitre.org/techniques/T0000/',
+                subtechnique: [
+                  {
+                    id: 'T0000.000',
+                    name: 'test subtechnique',
+                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
+                  },
+                ],
+              },
+            ],
+          },
+        ],
+        to: 'now',
+        references: ['http://example.com', 'https://example.com'],
+        related_integrations: [],
+        required_fields: [],
+        setup: '',
+        version: 1,
+        exceptions_list: [
+          {
+            id: 'some_uuid',
+            list_id: 'list_id_single',
+            namespace_type: 'single',
+            type: 'detection',
+          },
+          {
+            id: 'endpoint_list',
+            list_id: 'endpoint_list',
+            namespace_type: 'agnostic',
+            type: 'endpoint',
+          },
+        ],
+        immutable: false,
+        rule_source: {
+          type: 'internal',
+        },
+        type: 'query',
+        language: 'kuery',
+        index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
+        query: 'user.name: root or user.name: admin',
+        filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
+        investigation_fields: undefined,
+      },
+      [ALERT_RULE_INDICES]: completeRule.ruleParams.index,
+      ...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
+        actions: [],
+        author: ['Elastic'],
+        uuid: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
+        building_block_type: 'default',
+        created_at: '2020-03-27T22:55:59.577Z',
+        updated_at: '2020-03-27T22:55:59.577Z',
+        created_by: 'sample user',
+        description: 'Detecting root and admin users',
+        enabled: true,
+        false_positives: [],
+        from: 'now-6m',
+        immutable: false,
+        license: 'Elastic License',
+        meta: {
+          someMeta: 'someField',
+        },
+        name: 'rule-name',
+        note: '# Investigative notes',
+        references: ['http://example.com', 'https://example.com'],
+        severity: 'high',
+        severity_mapping: [],
+        updated_by: 'sample user',
+        tags: ['some fake tag 1', 'some fake tag 2'],
+        to: 'now',
+        type: 'query',
+        threat: [
+          {
+            framework: 'MITRE ATT&CK',
+            tactic: {
+              id: 'TA0000',
+              name: 'test tactic',
+              reference: 'https://attack.mitre.org/tactics/TA0000/',
+            },
+            technique: [
+              {
+                id: 'T0000',
+                name: 'test technique',
+                reference: 'https://attack.mitre.org/techniques/T0000/',
+                subtechnique: [
+                  {
+                    id: 'T0000.000',
+                    name: 'test subtechnique',
+                    reference: 'https://attack.mitre.org/techniques/T0000/000/',
+                  },
+                ],
+              },
+            ],
+          },
+        ],
+        version: 1,
+        max_signals: 10000,
+        risk_score: 50,
+        risk_score_mapping: [],
+        rule_id: 'rule-1',
+        interval: '5m',
+        exceptions_list: getListArrayMock(),
+        throttle: 'no_actions',
+        timeline_id: 'some-timeline-id',
+        timeline_title: 'some-timeline-title',
+      }),
+      [ALERT_DEPTH]: 1,
+      [ALERT_URL]: expectedAlertUrl,
+      [ALERT_UUID]: alertUuid,
+      [ALERT_WORKFLOW_TAGS]: [],
+      [ALERT_WORKFLOW_ASSIGNEE_IDS]: [],
+      someKey: 'someValue',
+      source: {
+        ip: '127.0.0.1',
+      },
+    };
+    expect(alert).toEqual(expected);
+  });
+
+  it('builds an alert as expected with original_event if present', () => {
+    const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
+    const doc = {
+      ...sampleDoc,
+      _source: {
+        ...sampleDoc._source,
+        [EVENT_ACTION]: 'socket_opened',
+        [EVENT_DATASET]: 'socket',
+        [EVENT_KIND]: 'event',
+        [EVENT_MODULE]: 'system',
+      },
+    };
+    const completeRule = getCompleteRuleMock(getQueryRuleParams());
+    const alert = transformHitToAlert({
+      spaceId: SPACE_ID,
+      completeRule,
+      doc,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageStub,
+      indicesToQuery: [],
+      alertTimestampOverride: undefined,
+      ruleExecutionLogger,
+      alertUuid,
+      publicBaseUrl,
+    });
+    const expected = {
+      ...alert,
+      ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, {
+        action: 'socket_opened',
+        dataset: 'socket',
+        kind: 'event',
+        module: 'system',
+      }),
+    };
+    expect(alert).toEqual(expected);
+  });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.ts
new file mode 100644
index 0000000000000..c5112006ae251
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/transform_hit_to_alert.ts
@@ -0,0 +1,140 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { merge } from 'lodash';
+import type * as estypes from '@elastic/elasticsearch/lib/api/types';
+import { requiredOptional } from '@kbn/zod-helpers';
+import { EVENT_KIND } from '@kbn/rule-data-utils';
+
+import type { BaseHit } from '../../../../../../common/detection_engine/types';
+import type { ConfigType } from '../../../../../config';
+import type { BuildReasonMessage } from '../../utils/reason_formatters';
+import { getMergeStrategy } from '../../utils/source_fields_merging/strategies';
+import type { SignalSource, SignalSourceHit } from '../../types';
+import { buildAlertFields, isThresholdResult } from './build_alert';
+import type { CompleteRule, RuleParams } from '../../../rule_schema';
+import type { IRuleExecutionLogForExecutors } from '../../../rule_monitoring';
+import { buildRuleNameFromMapping } from '../../utils/mappings/build_rule_name_from_mapping';
+import { buildSeverityFromMapping } from '../../utils/mappings/build_severity_from_mapping';
+import { buildRiskScoreFromMapping } from '../../utils/mappings/build_risk_score_from_mapping';
+import type { BaseFieldsLatest } from '../../../../../../common/api/detection_engine/model/alerts';
+import { traverseAndMutateDoc } from './traverse_and_mutate_doc';
+import { ALERT_THRESHOLD_RESULT } from '../../../../../../common/field_maps/field_names';
+import { robustGet, robustSet } from '../../utils/source_fields_merging/utils/robust_field_access';
+
+const isSourceDoc = (hit: SignalSourceHit): hit is BaseHit<SignalSource> => {
+  return hit._source != null && hit._id != null;
+};
+
+export interface TransformHitToAlertProps {
+  spaceId: string | null | undefined;
+  completeRule: CompleteRule<RuleParams>;
+  doc: estypes.SearchHit<SignalSource>;
+  mergeStrategy: ConfigType['alertMergeStrategy'];
+  ignoreFields: Record<string, boolean>;
+  ignoreFieldsRegexes: string[];
+  applyOverrides: boolean;
+  buildReasonMessage: BuildReasonMessage;
+  indicesToQuery: string[];
+  alertTimestampOverride: Date | undefined;
+  ruleExecutionLogger: IRuleExecutionLogForExecutors;
+  alertUuid: string;
+  publicBaseUrl?: string;
+}
+
+/**
+ * Formats the search_after result for insertion into the signals index. We first create a
+ * "best effort" merged "fields" with the "_source" object, then build the signal object,
+ * then the event object, and finally we strip away any additional temporary data that was added
+ * such as the "threshold_result".
+ * @param completeRule The rule saved object to build overrides
+ * @param doc The SignalSourceHit with "_source", "fields", and additional data such as "threshold_result"
+ * @returns The body that can be added to a bulk call for inserting the signal.
+ */
+export const transformHitToAlert = ({
+  spaceId,
+  completeRule,
+  doc,
+  mergeStrategy,
+  ignoreFields,
+  ignoreFieldsRegexes,
+  applyOverrides,
+  buildReasonMessage,
+  indicesToQuery,
+  alertTimestampOverride,
+  ruleExecutionLogger,
+  alertUuid,
+  publicBaseUrl,
+}: TransformHitToAlertProps): BaseFieldsLatest => {
+  const mergedDoc = getMergeStrategy(mergeStrategy)({ doc, ignoreFields, ignoreFieldsRegexes });
+  const thresholdResult = mergedDoc._source?.threshold_result;
+
+  if (isSourceDoc(mergedDoc)) {
+    const overrides = applyOverrides
+      ? {
+          nameOverride: buildRuleNameFromMapping({
+            eventSource: mergedDoc._source ?? {},
+            ruleName: completeRule.ruleConfig.name,
+            ruleNameMapping: completeRule.ruleParams.ruleNameOverride,
+          }).ruleName,
+          severityOverride: buildSeverityFromMapping({
+            eventSource: mergedDoc._source ?? {},
+            severity: completeRule.ruleParams.severity,
+            severityMapping: completeRule.ruleParams.severityMapping,
+          }).severity,
+          riskScoreOverride: buildRiskScoreFromMapping({
+            eventSource: mergedDoc._source ?? {},
+            riskScore: completeRule.ruleParams.riskScore,
+            riskScoreMapping: requiredOptional(completeRule.ruleParams.riskScoreMapping),
+          }).riskScore,
+        }
+      : undefined;
+
+    const reason = buildReasonMessage({
+      name: overrides?.nameOverride ?? completeRule.ruleConfig.name,
+      severity: overrides?.severityOverride ?? completeRule.ruleParams.severity,
+      mergedDoc,
+    });
+
+    const alertFields = buildAlertFields({
+      docs: [mergedDoc],
+      completeRule,
+      spaceId,
+      reason,
+      indicesToQuery,
+      alertUuid,
+      publicBaseUrl,
+      alertTimestampOverride,
+      overrides,
+    });
+
+    const { result: validatedSource, removed: removedSourceFields } = traverseAndMutateDoc(
+      mergedDoc._source
+    );
+
+    // The `alertFields` we add to alerts contain `event.kind: 'signal'` in dot notation. To avoid duplicating `event.kind`,
+    // we remove any existing `event.kind` field here before we merge `alertFields` into `validatedSource` later on
+    if (robustGet({ key: EVENT_KIND, document: validatedSource }) != null) {
+      robustSet({ key: EVENT_KIND, document: validatedSource, valueToSet: undefined });
+    }
+
+    if (removedSourceFields.length) {
+      ruleExecutionLogger?.debug(
+        'Following fields were removed from alert source as ECS non-compliant:',
+        JSON.stringify(removedSourceFields)
+      );
+    }
+
+    merge(validatedSource, alertFields);
+    if (thresholdResult != null && isThresholdResult(thresholdResult)) {
+      validatedSource[ALERT_THRESHOLD_RESULT] = thresholdResult;
+    }
+    return validatedSource as BaseFieldsLatest;
+  }
+
+  throw Error('Error building alert from source document.');
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.test.ts
similarity index 63%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.test.ts
rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.test.ts
index 21f9adc96bd60..0ec57843c83da 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.test.ts
@@ -5,9 +5,9 @@
  * 2.0.
  */
 
-import { stripNonEcsFields } from './strip_non_ecs_fields';
+import { traverseAndMutateDoc } from './traverse_and_mutate_doc';
 
-describe('stripNonEcsFields', () => {
+describe('traverseAndMutateDoc', () => {
   it('should not strip ECS compliant fields', () => {
     const document = {
       client: {
@@ -19,14 +19,14 @@ describe('stripNonEcsFields', () => {
       },
     };
 
-    const { result, removed } = stripNonEcsFields(document);
+    const { result, removed } = traverseAndMutateDoc(document);
 
     expect(result).toEqual(document);
     expect(removed).toEqual([]);
   });
 
   it('should strip source object field if ECS mapping is not object', () => {
-    const { result, removed } = stripNonEcsFields({
+    const { result, removed } = traverseAndMutateDoc({
       agent: {
         name: {
           first: 'test-1',
@@ -54,7 +54,7 @@ describe('stripNonEcsFields', () => {
   });
 
   it('should strip source keyword field if ECS mapping is object', () => {
-    const { result, removed } = stripNonEcsFields({
+    const { result, removed } = traverseAndMutateDoc({
       agent: 'test',
       message: 'test message',
     });
@@ -74,7 +74,7 @@ describe('stripNonEcsFields', () => {
   // https://github.com/elastic/sdh-security-team/issues/736
   describe('fields that exists in the alerts mapping but not in local ECS(ruleRegistry) definition', () => {
     it('should strip object type "device" field if it is supplied as a keyword', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         device: 'test',
         message: 'test message',
       });
@@ -94,7 +94,7 @@ describe('stripNonEcsFields', () => {
 
   describe('array fields', () => {
     it('should not strip arrays of objects when an object is expected', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         agent: [{ name: 'agent-1' }, { name: 'agent-2' }],
         message: 'test message',
       });
@@ -107,7 +107,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip conflicting fields in array of objects', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         agent: [
           {
             name: 'agent-1',
@@ -135,7 +135,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip conflicting array of keyword fields', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         agent: ['agent-1', 'agent-2'],
         message: 'test message',
       });
@@ -156,7 +156,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip conflicting array of object fields', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         agent: { name: [{ conflict: 'agent-1' }, { conflict: 'agent-2' }], type: 'filebeat' },
         message: 'test message',
       });
@@ -176,11 +176,29 @@ describe('stripNonEcsFields', () => {
         },
       ]);
     });
+
+    it('should entirely strip objects that end up empty in arrays', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        agent: [{ name: { conflict: 'agent-1' } }, { name: 'test' }],
+        message: 'test message',
+      });
+
+      expect(result).toEqual({
+        agent: [{ name: 'test' }],
+        message: 'test message',
+      });
+      expect(removed).toEqual([
+        {
+          key: 'agent.name',
+          value: { conflict: 'agent-1' },
+        },
+      ]);
+    });
   });
 
   describe('dot notation', () => {
     it('should strip conflicting fields that use dot notation', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'agent.name.conflict': 'some-value',
         message: 'test message',
       });
@@ -198,7 +216,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip conflicting fields that use dot notation and is an array', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'agent.name.text': ['1'],
         message: 'test message',
       });
@@ -215,8 +233,8 @@ describe('stripNonEcsFields', () => {
       ]);
     });
 
-    it('should strip conflicting fields that use dot notation and is an empty array', () => {
-      const { result, removed } = stripNonEcsFields({
+    it('should strip conflicting fields that use dot notation and is an empty array but not report the empty array as removed', () => {
+      const { result, removed } = traverseAndMutateDoc({
         'agent.name.text': [],
         message: 'test message',
       });
@@ -225,16 +243,11 @@ describe('stripNonEcsFields', () => {
         message: 'test message',
       });
 
-      expect(removed).toEqual([
-        {
-          key: 'agent.name.text',
-          value: [],
-        },
-      ]);
+      expect(removed).toEqual([]);
     });
 
     it('should not strip valid ECS fields that use dot notation', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'agent.name': 'some name',
         'agent.build.original': 'v10',
         message: 'test message',
@@ -253,7 +266,7 @@ describe('stripNonEcsFields', () => {
   describe('non-ECS fields', () => {
     it('should not strip non-ECS fields that don`t conflict', () => {
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           non_ecs_object: {
             field1: 'value1',
           },
@@ -272,7 +285,7 @@ describe('stripNonEcsFields', () => {
 
     it('should not strip non-ECS fields that don`t conflict even when nested inside ECS fieldsets', () => {
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           agent: {
             non_ecs_object: {
               field1: 'value1',
@@ -298,7 +311,7 @@ describe('stripNonEcsFields', () => {
 
   describe('ip field', () => {
     it('should not strip valid CIDR', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         source: {
           ip: '192.168.0.0',
           name: 'test source',
@@ -315,7 +328,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip invalid ip', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         source: {
           ip: 'invalid-ip',
           name: 'test source',
@@ -336,7 +349,7 @@ describe('stripNonEcsFields', () => {
 
   describe('nested field', () => {
     it('should strip invalid nested', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         threat: {
           enrichments: ['non-valid-threat-1', 'non-valid-threat-2'],
           'indicator.port': 443,
@@ -361,7 +374,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip valid values', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         threat: {
           enrichments: [
             {
@@ -386,7 +399,7 @@ describe('stripNonEcsFields', () => {
 
   describe('date field', () => {
     it('should strip invalid date', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         event: {
           created: true,
           category: 'start',
@@ -397,6 +410,7 @@ describe('stripNonEcsFields', () => {
         event: {
           category: 'start',
         },
+        'kibana.alert.original_event.category': 'start',
       });
       expect(removed).toEqual([
         {
@@ -407,7 +421,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip string or number date field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         event: {
           created: '2020-12-12',
           end: [2345562, '2022-10-12'],
@@ -419,6 +433,8 @@ describe('stripNonEcsFields', () => {
           created: '2020-12-12',
           end: [2345562, '2022-10-12'],
         },
+        'kibana.alert.original_event.created': '2020-12-12',
+        'kibana.alert.original_event.end': [2345562, '2022-10-12'],
       });
       expect(removed).toEqual([]);
     });
@@ -426,13 +442,13 @@ describe('stripNonEcsFields', () => {
 
   describe('long field', () => {
     it('should strip invalid long field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         client: {
           bytes: 'non-valid',
         },
       });
 
-      expect(result).toEqual({ client: {} });
+      expect(result).toEqual({});
       expect(removed).toEqual([
         {
           key: 'client.bytes',
@@ -442,13 +458,13 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip invalid long field with space in it', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         client: {
           bytes: '24 ',
         },
       });
 
-      expect(result).toEqual({ client: {} });
+      expect(result).toEqual({});
       expect(removed).toEqual([
         {
           key: 'client.bytes',
@@ -459,7 +475,7 @@ describe('stripNonEcsFields', () => {
   });
   describe('numeric field', () => {
     it('should strip invalid float field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'user.risk.calculated_score': 'non-valid',
       });
 
@@ -473,13 +489,13 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip invalid scaled_float field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         host: {
           'cpu.usage': 'non-valid',
         },
       });
 
-      expect(result).toEqual({ host: {} });
+      expect(result).toEqual({});
       expect(removed).toEqual([
         {
           key: 'host.cpu.usage',
@@ -489,7 +505,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip string float field with space', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'user.risk.calculated_score': '24 ',
       });
 
@@ -500,7 +516,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip string scaled_float field with space', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'host.cpu.usage': '24 ',
       });
 
@@ -511,7 +527,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip valid number in string field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         host: {
           'cpu.usage': '1234',
         },
@@ -526,7 +542,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip array of valid numeric fields', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'user.risk.calculated_score': [458.3333, '45.3', 10, 0, -667.23],
       });
 
@@ -539,7 +555,7 @@ describe('stripNonEcsFields', () => {
 
   describe('boolean field', () => {
     it('should strip invalid boolean fields', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'dll.code_signature.trusted': ['conflict', 'true', 5, 'False', 'ee', 'True'],
       });
 
@@ -571,7 +587,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should strip invalid boolean True', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'dll.code_signature.trusted': 'True',
       });
 
@@ -585,7 +601,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip valid boolean fields', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'dll.code_signature.trusted': ['true', 'false', true, false, ''],
       });
 
@@ -596,7 +612,7 @@ describe('stripNonEcsFields', () => {
     });
 
     it('should not strip valid boolean fields nested in array', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'dll.code_signature.trusted': [[true, false], ''],
       });
 
@@ -610,7 +626,7 @@ describe('stripNonEcsFields', () => {
   // geo_point is too complex so we going to skip its validation
   describe('geo_point field', () => {
     it('should not strip invalid geo_point field', () => {
-      const { result, removed } = stripNonEcsFields({
+      const { result, removed } = traverseAndMutateDoc({
         'client.location.geo': 'invalid geo_point',
       });
 
@@ -622,7 +638,7 @@ describe('stripNonEcsFields', () => {
 
     it('should not strip valid geo_point fields', () => {
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           'client.geo.location': [0, 90],
         }).result
       ).toEqual({
@@ -630,7 +646,7 @@ describe('stripNonEcsFields', () => {
       });
 
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           'client.geo.location': {
             type: 'Point',
             coordinates: [-88.34, 20.12],
@@ -644,7 +660,7 @@ describe('stripNonEcsFields', () => {
       });
 
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           'client.geo.location': 'POINT (-71.34 41.12)',
         }).result
       ).toEqual({
@@ -652,7 +668,7 @@ describe('stripNonEcsFields', () => {
       });
 
       expect(
-        stripNonEcsFields({
+        traverseAndMutateDoc({
           client: {
             geo: {
               location: {
@@ -674,4 +690,152 @@ describe('stripNonEcsFields', () => {
       });
     });
   });
+
+  describe('globally ignored fields', () => {
+    it('should strip out globally ignored top level fields', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        kibana: 'test-value',
+        non_ecs_field: 'value',
+      });
+
+      expect(result).toEqual({
+        non_ecs_field: 'value',
+      });
+      expect(removed).toEqual([
+        {
+          key: 'kibana',
+          value: 'test-value',
+        },
+      ]);
+    });
+
+    it('should strip out globally ignored nested fields', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        'kibana.test': 'test-value',
+        non_ecs_field: 'value',
+      });
+
+      expect(result).toEqual({
+        non_ecs_field: 'value',
+      });
+      expect(removed).toEqual([
+        {
+          key: 'kibana.test',
+          value: 'test-value',
+        },
+      ]);
+    });
+
+    it('should not strip out fields that use ignored field names as a prefix', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        kibana_test_prefix: 'test-value',
+        non_ecs_field: 'value',
+      });
+
+      expect(result).toEqual({
+        kibana_test_prefix: 'test-value',
+        non_ecs_field: 'value',
+      });
+      expect(removed).toEqual([]);
+    });
+  });
+
+  describe('fieldsToAdd', () => {
+    it('should extract a nested event field to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({ event: { action: 'test' } });
+      expect(result).toEqual({
+        event: { action: 'test' },
+        'kibana.alert.original_event.action': 'test',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should extract multiple nested event fields to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        event: { action: 'test', field2: 'test2' },
+      });
+      expect(result).toEqual({
+        event: { action: 'test', field2: 'test2' },
+        'kibana.alert.original_event.action': 'test',
+        'kibana.alert.original_event.field2': 'test2',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should extract a dot notation event field to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({ 'event.action': 'test' });
+      expect(result).toEqual({
+        'event.action': 'test',
+        'kibana.alert.original_event.action': 'test',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should extract multiple dot notation event fields to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        'event.action': 'test',
+        'event.field2': 'test2',
+      });
+      expect(result).toEqual({
+        'event.action': 'test',
+        'event.field2': 'test2',
+        'kibana.alert.original_event.action': 'test',
+        'kibana.alert.original_event.field2': 'test2',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should extract mixed notation fields to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        event: { 'field.subfield': 'test', 'field2.subfield': 'test2' },
+      });
+      expect(result).toEqual({
+        event: { 'field.subfield': 'test', 'field2.subfield': 'test2' },
+        'kibana.alert.original_event.field.subfield': 'test',
+        'kibana.alert.original_event.field2.subfield': 'test2',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should extract mixed notation with dot notation first to kibana.alert.original_event', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        'event.field': { subfield: 'test', subfield2: 'test2' },
+      });
+      expect(result).toEqual({
+        'event.field': { subfield: 'test', subfield2: 'test2' },
+        'kibana.alert.original_event.field.subfield': 'test',
+        'kibana.alert.original_event.field.subfield2': 'test2',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should not extract original event fields if they are not top level', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        'top_field.event.action': 'test',
+      });
+      expect(result).toEqual({ 'top_field.event.action': 'test' });
+      expect(removed).toEqual([]);
+    });
+
+    it('should not duplicate added fields', () => {
+      const { result, removed } = traverseAndMutateDoc({ event: { event: 'test' } });
+      expect(result).toEqual({
+        event: { event: 'test' },
+        'kibana.alert.original_event.event': 'test',
+      });
+      expect(removed).toEqual([]);
+    });
+
+    it('should work on multiple levels of nesting', () => {
+      const { result, removed } = traverseAndMutateDoc({
+        event: { field: { subfield: 'test', subfield2: 'test2' } },
+      });
+      expect(result).toEqual({
+        event: { field: { subfield: 'test', subfield2: 'test2' } },
+        'kibana.alert.original_event.field.subfield': 'test',
+        'kibana.alert.original_event.field.subfield2': 'test2',
+      });
+      expect(removed).toEqual([]);
+    });
+  });
 });
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts
similarity index 50%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.ts
rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts
index 08e5fa5fd879d..c9720a139ae7d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/strip_non_ecs_fields.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/traverse_and_mutate_doc.ts
@@ -6,8 +6,9 @@
  */
 
 import { ecsFieldMap } from '@kbn/alerts-as-data-utils';
+import { flattenWithPrefix } from '@kbn/securitysolution-rules';
 
-import { isPlainObject, cloneDeep, isArray } from 'lodash';
+import { isPlainObject, isArray, set } from 'lodash';
 
 import type { SearchTypes } from '../../../../../../common/detection_engine/types';
 import { isValidIpType } from './ecs_types_validators/is_valid_ip_type';
@@ -15,6 +16,10 @@ import { isValidDateType } from './ecs_types_validators/is_valid_date_type';
 import { isValidNumericType } from './ecs_types_validators/is_valid_numeric_type';
 import { isValidBooleanType } from './ecs_types_validators/is_valid_boolean_type';
 import { isValidLongType } from './ecs_types_validators/is_valid_long_type';
+import {
+  ALERT_ORIGINAL_EVENT,
+  ALERT_THRESHOLD_RESULT,
+} from '../../../../../../common/field_maps/field_names';
 
 type SourceFieldRecord = Record<string, SearchTypes>;
 type SourceField = SearchTypes | SourceFieldRecord;
@@ -153,67 +158,149 @@ const computeIsEcsCompliant = (value: SourceField, path: string) => {
   return isEcsFieldObject ? isPlainObject(value) : !isPlainObject(value);
 };
 
-interface StripNonEcsFieldsReturn {
-  result: SourceFieldRecord;
-  removed: Array<{ key: string; value: SearchTypes }>;
-}
+const bannedFields = ['kibana', 'signal', 'threshold_result', ALERT_THRESHOLD_RESULT];
 
 /**
- * strips alert source object from ECS non compliant fields
+ * Traverse an entire source document and mutate it to prepare for indexing into the alerts index. Traversing the document
+ * is computationally expensive so we only want to traverse it once, therefore a few distinct cases are handled in this function:
+ * 1. Fields that we must explicitly remove, like `kibana` and `signal`, fields, are removed from the document.
+ * 2. Fields that are incompatible with ECS are removed.
+ * 3. All `event.*` fields are collected and copied to `kibana.alert.original_event.*` using `fieldsToAdd`
+ * @param document The document to traverse
+ * @returns The mutated document, a list of removed fields
  */
-export const stripNonEcsFields = (doc: SourceFieldRecord): StripNonEcsFieldsReturn => {
-  const result = cloneDeep(doc);
-  const removed: Array<{ key: string; value: SearchTypes }> = [];
-
-  /**
-   * traverses through object and deletes ECS non compliant fields
-   * @param document - document to traverse
-   * @param documentKey - document key in parent document, if exists
-   * @param parent - parent of traversing document
-   * @param parentPath - path of parent in initial source document
-   */
-  const traverseAndDeleteInObj = (
-    document: SourceField,
-    documentKey: string,
-    parent?: SourceFieldRecord,
-    parentPath?: string
-  ) => {
-    const fullPath = [parentPath, documentKey].filter(Boolean).join('.');
-    // if document array, traverse through each item w/o changing documentKey, parent, parentPath
-    if (isArray(document) && document.length > 0) {
-      document.slice().forEach((value) => {
-        traverseAndDeleteInObj(value, documentKey, parent, parentPath);
+export const traverseAndMutateDoc = (document: SourceFieldRecord) => {
+  const { result, removed, fieldsToAdd } = internalTraverseAndMutateDoc({
+    document,
+    path: [],
+    topLevel: true,
+    removed: [],
+    fieldsToAdd: [],
+  });
+
+  fieldsToAdd.forEach(({ key, value }) => {
+    result[key] = value;
+  });
+
+  return { result, removed };
+};
+
+const internalTraverseAndMutateDoc = <T extends SourceFieldRecord>({
+  document,
+  path,
+  topLevel,
+  removed,
+  fieldsToAdd,
+}: {
+  document: T;
+  path: string[];
+  topLevel: boolean;
+  removed: Array<{ key: string; value: SearchTypes }>;
+  fieldsToAdd: Array<{ key: string; value: SearchTypes }>;
+}) => {
+  Object.keys(document).forEach((key) => {
+    // Using Object.keys and fetching the value for each key separately performs better in profiling than using Object.entries
+    const value = document[key];
+    const fullPathArray = [...path, key];
+    const fullPath = fullPathArray.join('.');
+    // Insert checks that don't care about the value - only depend on the key - up here
+    let deleted = false;
+    if (topLevel) {
+      const firstKeyString = key.split('.')[0];
+      bannedFields.forEach((bannedField) => {
+        if (firstKeyString === bannedField) {
+          delete document[key];
+          deleted = true;
+          removed.push({ key: fullPath, value });
+        }
       });
-      return;
     }
 
-    if (parent && !computeIsEcsCompliant(document, fullPath)) {
-      const documentReference = parent[documentKey];
-      // if document reference in parent is array, remove only this item from array
-      // e.g. a boolean mapped field with values ['not-boolean', 'true'] should strip 'not-boolean' and leave 'true'
-      if (isArray(documentReference)) {
-        const indexToDelete = documentReference.findIndex((item) => item === document);
-        documentReference.splice(indexToDelete, 1);
-        if (documentReference.length === 0) {
-          delete parent[documentKey];
+    // If we passed the key check, additional checks based on key and value are done below. Items in arrays are treated independently from each other.
+    if (!deleted) {
+      if (isArray(value)) {
+        const newValue = traverseArray({ array: value, path: fullPathArray, removed, fieldsToAdd });
+        if (newValue.length > 0) {
+          set(document, key, newValue);
+        } else {
+          delete document[key];
+          deleted = true;
+        }
+      } else if (!computeIsEcsCompliant(value, fullPath)) {
+        delete document[key];
+        deleted = true;
+        removed.push({ key: fullPath, value });
+      } else if (isSearchTypesRecord(value)) {
+        internalTraverseAndMutateDoc({
+          document: value,
+          path: fullPathArray,
+          topLevel: false,
+          removed,
+          fieldsToAdd,
+        });
+        if (Object.keys(value).length === 0) {
+          delete document[key];
+          deleted = true;
+        }
+      }
+    }
+
+    // We're keeping the field, but maybe we want to copy it to a different field as well
+    if (!deleted && fullPath.split('.')[0] === 'event' && topLevel) {
+      // The value might have changed above when we `set` after traversing an array
+      const valueRefetch = document[key];
+      const newKey = `${ALERT_ORIGINAL_EVENT}${fullPath.replace('event', '')}`;
+      if (isPlainObject(valueRefetch)) {
+        const flattenedObject = flattenWithPrefix(newKey, valueRefetch);
+        for (const [k, v] of Object.entries(flattenedObject)) {
+          fieldsToAdd.push({ key: k, value: v });
         }
       } else {
-        delete parent[documentKey];
+        fieldsToAdd.push({
+          key: `${ALERT_ORIGINAL_EVENT}${fullPath.replace('event', '')}`,
+          value: valueRefetch,
+        });
       }
-      removed.push({ key: fullPath, value: document });
-      return;
     }
+  });
+  return { result: document, removed, fieldsToAdd };
+};
 
-    if (isSearchTypesRecord(document)) {
-      Object.entries(document).forEach(([key, value]) => {
-        traverseAndDeleteInObj(value, key, document, fullPath);
+const traverseArray = ({
+  array,
+  path,
+  removed,
+  fieldsToAdd,
+}: {
+  array: SearchTypes[];
+  path: string[];
+  removed: Array<{ key: string; value: SearchTypes }>;
+  fieldsToAdd: Array<{ key: string; value: SearchTypes }>;
+}): SearchTypes[] => {
+  const pathString = path.join('.');
+  for (let i = 0; i < array.length; i++) {
+    const value = array[i];
+    if (isArray(value)) {
+      array[i] = traverseArray({ array: value, path, removed, fieldsToAdd });
+    }
+  }
+  return array.filter((value) => {
+    if (isArray(value)) {
+      return value.length > 0;
+    } else if (!computeIsEcsCompliant(value, pathString)) {
+      removed.push({ key: pathString, value });
+      return false;
+    } else if (isSearchTypesRecord(value)) {
+      internalTraverseAndMutateDoc({
+        document: value,
+        path,
+        topLevel: false,
+        removed,
+        fieldsToAdd,
       });
+      return Object.keys(value).length > 0;
+    } else {
+      return true;
     }
-  };
-
-  traverseAndDeleteInObj(result, '');
-  return {
-    result,
-    removed,
-  };
+  });
 };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts
index 6b21ed226c165..2495e48c1cc81 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts
@@ -10,7 +10,7 @@ import type { ConfigType } from '../../../../config';
 import type { SignalSource, SimpleHit } from '../types';
 import type { CompleteRule, RuleParams } from '../../rule_schema';
 import { generateId } from '../utils/utils';
-import { buildBulkBody } from './utils/build_bulk_body';
+import { transformHitToAlert } from './utils/transform_hit_to_alert';
 import type { BuildReasonMessage } from '../utils/reason_formatters';
 import type {
   BaseFieldsLatest,
@@ -22,6 +22,7 @@ export const wrapHitsFactory =
   ({
     completeRule,
     ignoreFields,
+    ignoreFieldsRegexes,
     mergeStrategy,
     spaceId,
     indicesToQuery,
@@ -30,7 +31,8 @@ export const wrapHitsFactory =
     ruleExecutionLogger,
   }: {
     completeRule: CompleteRule<RuleParams>;
-    ignoreFields: ConfigType['alertIgnoreFields'];
+    ignoreFields: Record<string, boolean>;
+    ignoreFieldsRegexes: string[];
     mergeStrategy: ConfigType['alertMergeStrategy'];
     spaceId: string | null | undefined;
     indicesToQuery: string[];
@@ -51,20 +53,21 @@ export const wrapHitsFactory =
         `${spaceId}:${completeRule.alertId}`
       );
 
-      const baseAlert = buildBulkBody(
+      const baseAlert = transformHitToAlert({
         spaceId,
         completeRule,
-        event as SimpleHit,
+        doc: event as SimpleHit,
         mergeStrategy,
         ignoreFields,
-        true,
+        ignoreFieldsRegexes,
+        applyOverrides: true,
         buildReasonMessage,
         indicesToQuery,
         alertTimestampOverride,
         ruleExecutionLogger,
-        id,
-        publicBaseUrl
-      );
+        alertUuid: id,
+        publicBaseUrl,
+      });
 
       return {
         _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_new_terms_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_new_terms_alerts.ts
index 5028c15c2c8d1..efd33c5442bf3 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_new_terms_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_new_terms_alerts.ts
@@ -18,7 +18,7 @@ import type { CompleteRule, RuleParams } from '../../rule_schema';
 import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
 import type { SignalSource } from '../types';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 
 export interface EventsAndTerms {
   event: estypes.SearchHit<SignalSource>;
@@ -52,20 +52,21 @@ export const wrapNewTermsAlerts = ({
       `${spaceId}:${completeRule.alertId}`,
       eventAndTerms.newTerms,
     ]);
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      eventAndTerms.event,
+      doc: eventAndTerms.event,
       mergeStrategy,
-      [],
-      true,
-      buildReasonMessageForNewTermsAlert,
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageForNewTermsAlert,
       indicesToQuery,
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts
index 274a11fc4ffcc..ad34feb81eab1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts
@@ -21,7 +21,7 @@ import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
 import { getSuppressionAlertFields, getSuppressionTerms } from '../utils';
 import type { SignalSource } from '../types';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 
 export interface EventsAndTerms {
   event: estypes.SearchHit<SignalSource>;
@@ -69,20 +69,21 @@ export const wrapSuppressedNewTermsAlerts = ({
       eventAndTerms.newTerms,
     ]);
 
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      event,
+      doc: event,
       mergeStrategy,
-      [],
-      true,
-      buildReasonMessageForNewTermsAlert,
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: buildReasonMessageForNewTermsAlert,
       indicesToQuery,
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/wrap_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/wrap_suppressed_alerts.ts
index e7bfe3f7eaacd..abff900a5dcb4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/wrap_suppressed_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/wrap_suppressed_alerts.ts
@@ -23,7 +23,7 @@ import type { ConfigType } from '../../../../../config';
 import type { CompleteRule, RuleParams } from '../../../rule_schema';
 import type { IRuleExecutionLogForExecutors } from '../../../rule_monitoring';
 import type { SignalSource } from '../../types';
-import { buildBulkBody } from '../../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../../factories/utils/transform_hit_to_alert';
 import type { BuildReasonMessage } from '../../utils/reason_formatters';
 
 export interface SuppressionBucket {
@@ -82,20 +82,21 @@ export const wrapSuppressedAlerts = ({
       ruleId: completeRule.alertId,
       spaceId,
     });
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      bucket.event,
+      doc: bucket.event,
       mergeStrategy,
-      [],
-      true,
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
       buildReasonMessage,
       indicesToQuery,
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts
index 492d27fba091f..e9acb8284b740 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/wrap_suppressed_threshold_alerts.ts
@@ -25,7 +25,7 @@ import type {
 import type { ConfigType } from '../../../../config';
 import type { CompleteRule, ThresholdRuleParams } from '../../rule_schema';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 
 import type { ThresholdBucket } from './types';
 import type { BuildReasonMessage } from '../utils/reason_formatters';
@@ -86,20 +86,21 @@ export const wrapSuppressedThresholdALerts = ({
 
     const instanceId = objectHash([suppressedValues, completeRule.alertId, spaceId]);
 
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      hit,
+      doc: hit,
       mergeStrategy,
-      [],
-      true,
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
       buildReasonMessage,
       indicesToQuery,
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts
index 4d414d71cfadf..60b7e3edacedf 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts
@@ -10,7 +10,6 @@ import {
   repeatedSearchResultsWithSortId,
   repeatedSearchResultsWithNoSortId,
   sampleDocSearchResultsNoSortIdNoHits,
-  sampleDocWithSortId,
 } from '../__mocks__/es_results';
 import { searchAfterAndBulkCreate } from './search_after_bulk_create';
 import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks';
@@ -114,7 +113,8 @@ describe('searchAfterAndBulkCreate', () => {
     wrapHits = wrapHitsFactory({
       completeRule: queryCompleteRule,
       mergeStrategy: 'missingFields',
-      ignoreFields: [],
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
       spaceId: 'default',
       indicesToQuery: inputIndexPattern,
       alertTimestampOverride: undefined,
@@ -1025,8 +1025,22 @@ describe('searchAfterAndBulkCreate', () => {
     expect(mockEnrichment).toHaveBeenCalledWith(
       expect.objectContaining([
         expect.objectContaining({
-          ...sampleDocWithSortId(),
           _id: expect.any(String),
+          _index: 'myFakeSignalIndex',
+          _score: 100,
+          _source: expect.objectContaining({
+            destination: { ip: '127.0.0.1' },
+            someKey: 'someValue',
+            source: { ip: '127.0.0.1' },
+          }),
+          _version: 1,
+          fields: {
+            '@timestamp': ['2020-04-20T21:27:45+0000'],
+            'destination.ip': ['127.0.0.1'],
+            someKey: ['someValue'],
+            'source.ip': ['127.0.0.1'],
+          },
+          sort: ['1234567891111', '2233447556677'],
         }),
       ])
     );
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts
index 2b69f4fe980f2..6f4f15ad942be 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.test.ts
@@ -46,7 +46,11 @@ describe('merge_all_fields_with_source', () => {
       test('when source is "undefined", merged doc is "undefined"', () => {
         const _source: SignalSourceHit['_source'] = {};
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -55,7 +59,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -64,7 +72,11 @@ describe('merge_all_fields_with_source', () => {
           foo: 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -73,7 +85,11 @@ describe('merge_all_fields_with_source', () => {
           foo: ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -82,7 +98,11 @@ describe('merge_all_fields_with_source', () => {
           foo: ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -91,7 +111,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -100,7 +124,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -109,7 +137,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -135,7 +167,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -144,7 +180,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -153,7 +193,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -162,7 +206,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -171,7 +219,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -180,7 +232,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -189,7 +245,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -219,7 +279,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: [] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -228,7 +292,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: 'value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -237,7 +305,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: ['value'] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -246,7 +318,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: ['value_1', 'value_2'] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -255,7 +331,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: { mars: 'some value' } },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -264,7 +344,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: [{ mars: 'some value' }] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -273,7 +357,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: [{ mars: 'some value' }, { mars: 'some other value' }] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -301,7 +389,11 @@ describe('merge_all_fields_with_source', () => {
           'bar.foo': [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -310,7 +402,11 @@ describe('merge_all_fields_with_source', () => {
           'bar.foo': 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -319,7 +415,11 @@ describe('merge_all_fields_with_source', () => {
           'bar.foo': ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -328,7 +428,11 @@ describe('merge_all_fields_with_source', () => {
           'bar.foo': ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -337,7 +441,11 @@ describe('merge_all_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -346,7 +454,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -355,7 +467,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -378,7 +494,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': ['other_value_1'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
           bar: 'other_value_1',
@@ -391,7 +511,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': ['other_value_1', 'other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
           bar: ['other_value_1', 'other_value_2'],
@@ -404,7 +528,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': [{ zed: 'other_value_1' }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: { bar: { zed: 'other_value_1' } },
       });
@@ -415,7 +543,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
       });
@@ -442,7 +574,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: 'other_value_1',
@@ -455,7 +591,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: ['other_value_1', 'other_value_2'] },
         });
@@ -466,7 +606,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: 'other_value_1',
@@ -479,7 +623,11 @@ describe('merge_all_fields_with_source', () => {
           foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }],
         });
@@ -505,7 +653,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'foo.bar': 'other_value_1',
         });
@@ -516,7 +668,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -525,7 +681,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'foo.bar': { zed: 'other_value_1' },
         });
@@ -536,7 +696,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -562,7 +726,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: ['other_value_1'] },
         });
@@ -573,7 +741,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: ['other_value_1', 'other_value_2'] },
         });
@@ -584,7 +756,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }] },
         });
@@ -595,7 +771,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
         });
@@ -621,7 +801,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -630,7 +814,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -639,7 +827,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -648,7 +840,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -672,7 +868,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: ['other_value_1'] },
         });
@@ -683,7 +883,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: ['other_value_1', 'other_value_2'] },
         });
@@ -694,7 +898,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }] },
         });
@@ -705,7 +913,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
         });
@@ -731,7 +943,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -740,7 +956,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -749,7 +969,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -758,7 +982,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -784,7 +1012,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: ['other_value_1'],
@@ -797,7 +1029,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: ['other_value_1', 'other_value_2'],
@@ -810,7 +1046,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: [{ zed: 'other_value_1' }],
@@ -823,7 +1063,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
@@ -851,7 +1095,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -860,7 +1108,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -869,7 +1121,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -878,7 +1134,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -904,7 +1164,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -913,7 +1177,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -922,7 +1190,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: { zed: 'other_value_1' } },
         });
@@ -933,7 +1205,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
         });
@@ -959,7 +1235,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -968,7 +1248,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -977,7 +1261,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'foo.bar': { mars: 'other_value_1' },
         });
@@ -988,7 +1276,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         });
@@ -1016,7 +1308,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1025,7 +1321,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1034,7 +1334,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }] },
         });
@@ -1045,7 +1349,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
         });
@@ -1071,7 +1379,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1080,7 +1392,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1089,7 +1405,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }] },
         });
@@ -1100,7 +1420,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] },
         });
@@ -1126,7 +1450,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1135,7 +1463,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1144,7 +1476,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -1153,7 +1489,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -1177,8 +1517,12 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
-        expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
+        expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(cloneDeep(_source));
       });
 
       test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_[{}1, ...1])"', () => {
@@ -1186,8 +1530,12 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
-        expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
+        expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(cloneDeep(_source));
       });
 
       test('fields has a single nested object (f_[{}2]), merged doc is array value (f_[{}2])"', () => {
@@ -1195,7 +1543,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
 
@@ -1204,7 +1556,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(fields);
       });
     });
@@ -1230,10 +1586,14 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': ['other_value_1'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
-        foo: { bar: 'value_1' },
-        'foo.bar': 'other_value_1',
+        foo: { bar: 'other_value_1' },
+        'foo.bar': 'value_2',
       });
     });
 
@@ -1245,10 +1605,14 @@ describe('merge_all_fields_with_source', () => {
         'foo.bar': ['value_1', 'value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
-        foo: { bar: 'value_1' }, // <--- We have duplicated value_1 twice which is a bug
-        'foo.bar': ['value_1', 'value_2'], // <-- We have merged the array value because we do not understand if we should or not
+        foo: { bar: ['value_1', 'value_2'] }, // <-- We have merged the array value because we do not understand if we should or not
+        'foo.bar': 'value_2', // <--- We have duplicated value_2 twice which is a bug
       });
     });
   });
@@ -1272,7 +1636,11 @@ describe('merge_all_fields_with_source', () => {
           'bar.keyword': ['bar_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: 'foo_other_value_1',
           bar: 'bar_other_value_1',
@@ -1293,7 +1661,11 @@ describe('merge_all_fields_with_source', () => {
           'host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           host: {
             hostname: 'hostname_other_value_1',
@@ -1318,7 +1690,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: {
             host: {
@@ -1339,7 +1715,11 @@ describe('merge_all_fields_with_source', () => {
           'process.command_line.text': ['string longer than 10 characters'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1353,7 +1733,11 @@ describe('merge_all_fields_with_source', () => {
           'process.command_line.text': ['string longer than 10 characters'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
 
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
@@ -1365,7 +1749,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar': ['other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: 'other_value_1',
         });
@@ -1385,7 +1773,11 @@ describe('merge_all_fields_with_source', () => {
           'host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'host.name': 'host_name_other_value_1',
           'host.hostname': 'hostname_other_value_1',
@@ -1404,7 +1796,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'foo.host.name': 'host_name_other_value_1',
           'foo.host.hostname': 'hostname_other_value_1',
@@ -1419,7 +1815,11 @@ describe('merge_all_fields_with_source', () => {
           'foo.bar.zed': ['zed_other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: 'other_value_1',
         });
@@ -1434,7 +1834,11 @@ describe('merge_all_fields_with_source', () => {
           'process.command_line.text': ['string longer than 10 characters'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1447,7 +1851,11 @@ describe('merge_all_fields_with_source', () => {
           'process.command_line.text': ['string longer than 10 characters'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeAllFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1472,7 +1880,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.mars': ['other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
 
@@ -1492,7 +1904,11 @@ describe('merge_all_fields_with_source', () => {
         'foo.zed.mars': ['other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
   });
@@ -1507,7 +1923,11 @@ describe('merge_all_fields_with_source', () => {
         foo: [{ bar: ['single_value'], zed: ['single_value'] }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: { bar: 'single_value', zed: 'single_value' },
       });
@@ -1526,7 +1946,11 @@ describe('merge_all_fields_with_source', () => {
         foo: [{ bar: ['single_value'], zed: ['single_value'] }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: [{ bar: ['single_value'], zed: ['single_value'] }],
       });
@@ -1543,15 +1967,15 @@ describe('merge_all_fields_with_source', () => {
         'email.headers.x-test': ['from fields'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
-        email: {
-          headers: {
-            'x-test': 'from fields',
-          },
+        'email.headers': {
+          'x-test': 'from fields',
         },
-        // preserves conflicting keys if values contain empty objects
-        'email.headers': {},
       });
     });
 
@@ -1564,7 +1988,11 @@ describe('merge_all_fields_with_source', () => {
         'email.headers.x-test': ['from fields'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         'email.headers': {
           'x-test': 'from fields',
@@ -1581,7 +2009,11 @@ describe('merge_all_fields_with_source', () => {
         'email.headers.x-test': ['from fields'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         'email.headers': { 'x-test': 'from fields' },
       });
@@ -1596,7 +2028,11 @@ describe('merge_all_fields_with_source', () => {
         'email.headers.x-test': ['b1', 'b2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         'email.headers': { 'x-test': ['b1', 'b2'] },
       });
@@ -1611,7 +2047,11 @@ describe('merge_all_fields_with_source', () => {
         'a.b.c.d': ['5', '6'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
 
@@ -1624,7 +2064,11 @@ describe('merge_all_fields_with_source', () => {
         'a.b.c': [{ d: '3 ' }, { d: '4' }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeAllFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeAllFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         'a.b': { c: [{ d: '3 ' }, { d: '4' }] },
       });
@@ -1645,7 +2089,8 @@ describe('merge_all_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeAllFieldsWithSource({
         doc,
-        ignoreFields: ['value.should.ignore', '/[_]+/'],
+        ignoreFields: { 'value.should.ignore': true },
+        ignoreFieldsRegexes: ['/[_]+/'],
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1664,7 +2109,8 @@ describe('merge_all_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeAllFieldsWithSource({
         doc,
-        ignoreFields: ['other.string', '/[z]+/'], // Neither of these two should match anything
+        ignoreFields: { 'other.string': true },
+        ignoreFieldsRegexes: ['/[z]+/'], // Neither of these two should match anything
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1694,7 +2140,8 @@ describe('merge_all_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeAllFieldsWithSource({
         doc,
-        ignoreFields: ['value.should.ignore', '/[_]+/'],
+        ignoreFields: { 'value.should.ignore': true },
+        ignoreFieldsRegexes: ['/[_]+/'],
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1718,7 +2165,8 @@ describe('merge_all_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeAllFieldsWithSource({
         doc,
-        ignoreFields: ['nothing.to.match', '/[z]+/'], // these match nothing
+        ignoreFields: { 'nothing.to.match': true },
+        ignoreFieldsRegexes: ['/[z]+/'], // these match nothing
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1743,7 +2191,8 @@ describe('merge_all_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeAllFieldsWithSource({
         doc,
-        ignoreFields: [],
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts
index 762542d2b93dc..7b75899fe60f7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_all_fields_with_source.ts
@@ -5,10 +5,9 @@
  * 2.0.
  */
 
-import { get } from 'lodash/fp';
-import { set } from '@kbn/safer-lodash-set/fp';
+import { robustGet, robustSet } from '../utils/robust_field_access';
 import type { SignalSource } from '../../../types';
-import { filterFieldEntries } from '../utils/filter_field_entries';
+import { filterFieldEntry } from '../utils/filter_field_entry';
 import type { FieldsType, MergeStrategyFunction } from '../types';
 import { isObjectLikeOrArrayOfObjectLikes } from '../utils/is_objectlike_or_array_of_objectlikes';
 import { isNestedObject } from '../utils/is_nested_object';
@@ -16,8 +15,7 @@ import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields';
 import { isPrimitive } from '../utils/is_primitive';
 import { isArrayOfPrimitives } from '../utils/is_array_of_primitives';
 import { isTypeObject } from '../utils/is_type_object';
-import { isPathValid } from '../utils/is_path_valid';
-import { buildFieldsKeyAsArrayMap } from '../utils/build_fields_key_as_array_map';
+import { robustIsPathValid } from '../utils/is_path_valid';
 
 /**
  * Merges all of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information
@@ -29,63 +27,56 @@ import { buildFieldsKeyAsArrayMap } from '../utils/build_fields_key_as_array_map
  * it will not be added from fields.
  * @returns The two merged together in one object where we can
  */
-export const mergeAllFieldsWithSource: MergeStrategyFunction = ({ doc, ignoreFields }) => {
+export const mergeAllFieldsWithSource: MergeStrategyFunction = ({
+  doc,
+  ignoreFields,
+  ignoreFieldsRegexes,
+}) => {
   const source = doc._source ?? {};
   const fields = doc.fields ?? {};
-  const fieldEntries = Object.entries(fields);
-  const filteredEntries = filterFieldEntries(fieldEntries, ignoreFields);
-  const fieldsKeyMap = buildFieldsKeyAsArrayMap(source);
-
-  const transformedSource = filteredEntries.reduce(
-    (merged, [fieldsKeyAsString, fieldsValue]: [string, FieldsType]) => {
-      const fieldsKey = fieldsKeyMap[fieldsKeyAsString] ?? fieldsKeyAsString;
-
-      if (
-        hasEarlyReturnConditions({
-          fieldsValue,
-          fieldsKey,
-          merged,
-        })
-      ) {
-        return merged;
-      }
+  const fieldsKeys = Object.keys(fields);
 
-      const valueInMergedDocument = get(fieldsKey, merged);
+  fieldsKeys.forEach((fieldsKey) => {
+    const valueInMergedDocument = robustGet({ key: fieldsKey, document: source });
+    const fieldsValue = fields[fieldsKey];
+    if (
+      !hasEarlyReturnConditions({
+        fieldsValue,
+        fieldsKey,
+        merged: source,
+      }) &&
+      filterFieldEntry([fieldsKey, fieldsValue], fieldsKeys, ignoreFields, ignoreFieldsRegexes)
+    ) {
       if (valueInMergedDocument === undefined) {
         const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-        return set(fieldsKey, valueToMerge, merged);
+        robustSet({ key: fieldsKey, valueToSet: valueToMerge, document: source });
       } else if (isPrimitive(valueInMergedDocument)) {
         const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-        return set(fieldsKey, valueToMerge, merged);
+        robustSet({ key: fieldsKey, valueToSet: valueToMerge, document: source });
       } else if (isArrayOfPrimitives(valueInMergedDocument)) {
         const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-        return set(fieldsKey, valueToMerge, merged);
+        robustSet({ key: fieldsKey, valueToSet: valueToMerge, document: source });
       } else if (
         isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) &&
         isNestedObject(fieldsValue) &&
         !Array.isArray(valueInMergedDocument)
       ) {
         const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-        return set(fieldsKey, valueToMerge, merged);
+        robustSet({ key: fieldsKey, valueToSet: valueToMerge, document: source });
       } else if (
         isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) &&
         isNestedObject(fieldsValue) &&
         Array.isArray(valueInMergedDocument)
       ) {
         const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-        return set(fieldsKey, valueToMerge, merged);
-      } else {
-        // fail safe catch all condition for production, but we shouldn't try to reach here and
-        // instead write tests if we encounter this situation.
-        return merged;
+        robustSet({ key: fieldsKey, valueToSet: valueToMerge, document: source });
       }
-    },
-    { ...source }
-  );
+    }
+  });
 
   return {
     ...doc,
-    _source: transformedSource,
+    _source: source,
   };
 };
 
@@ -105,13 +96,13 @@ const hasEarlyReturnConditions = ({
   merged,
 }: {
   fieldsValue: FieldsType;
-  fieldsKey: string[] | string;
+  fieldsKey: string;
   merged: SignalSource;
 }) => {
-  const valueInMergedDocument = get(fieldsKey, merged);
+  const valueInMergedDocument = robustGet({ key: fieldsKey, document: merged });
   return (
     fieldsValue.length === 0 ||
-    (valueInMergedDocument === undefined && !isPathValid(fieldsKey, merged)) ||
+    (valueInMergedDocument === undefined && !robustIsPathValid(fieldsKey, merged)) ||
     (isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) &&
       !isNestedObject(fieldsValue) &&
       !isTypeObject(fieldsValue))
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts
index 80b9360541563..04089f449c368 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts
@@ -45,7 +45,11 @@ describe('merge_missing_fields_with_source', () => {
       test('when source is "undefined", merged doc is "undefined"', () => {
         const _source: SignalSourceHit['_source'] = {};
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -54,7 +58,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -63,7 +71,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -72,7 +84,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -81,7 +97,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -90,7 +110,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -99,7 +123,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -108,7 +136,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -134,7 +166,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -143,7 +179,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -152,7 +192,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -161,7 +205,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -170,7 +218,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -179,7 +231,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -188,7 +244,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -218,7 +278,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: [] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -227,7 +291,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: 'value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -236,7 +304,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: ['value'] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -245,7 +317,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: ['value_1', 'value_2'] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -254,7 +330,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: { mars: 'some value' } },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -263,7 +343,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: [{ mars: 'some value' }] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -272,7 +356,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: [{ mars: 'some value' }, { mars: 'some other value' }] },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -310,7 +398,11 @@ describe('merge_missing_fields_with_source', () => {
           'bar.foo': [],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -319,7 +411,11 @@ describe('merge_missing_fields_with_source', () => {
           'bar.foo': 'value',
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -328,7 +424,11 @@ describe('merge_missing_fields_with_source', () => {
           'bar.foo': ['value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -337,7 +437,11 @@ describe('merge_missing_fields_with_source', () => {
           'bar.foo': ['value_1', 'value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -346,7 +450,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: { bar: 'some value' },
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -355,7 +463,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -364,7 +476,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'some value' }, { foo: 'some other value' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -390,7 +506,7 @@ describe('merge_missing_fields_with_source', () => {
         const start = performance.now();
         // we don't care about the response just determining performance
         // eslint-disable-next-line @typescript-eslint/no-unused-expressions
-        mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        mergeMissingFieldsWithSource({ doc, ignoreFields: {}, ignoreFieldsRegexes: [] })._source;
         const end = performance.now();
         expect(end - start).toBeLessThan(500);
       });
@@ -414,7 +530,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': ['other_value_1'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
           bar: 'other_value_1',
@@ -427,7 +547,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': ['other_value_1', 'other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
           bar: ['other_value_1', 'other_value_2'],
@@ -440,7 +564,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': [{ zed: 'other_value_1' }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({});
     });
 
@@ -449,7 +577,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({});
     });
   });
@@ -474,7 +606,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -483,7 +619,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -492,7 +632,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -501,7 +645,11 @@ describe('merge_missing_fields_with_source', () => {
           foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -525,7 +673,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -534,7 +686,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -543,7 +699,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -552,7 +712,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -578,7 +742,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -587,7 +755,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -596,7 +768,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -605,7 +781,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -629,7 +809,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -638,7 +822,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -647,7 +835,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -656,7 +848,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -680,7 +876,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -689,7 +889,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -698,7 +902,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -707,7 +915,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -731,7 +943,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -740,7 +956,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -749,7 +969,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -758,7 +982,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -784,7 +1012,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -793,7 +1025,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -802,7 +1038,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -811,7 +1051,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -835,7 +1079,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -844,7 +1092,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -853,7 +1105,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -862,7 +1118,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -888,7 +1148,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -897,7 +1161,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -906,7 +1174,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -915,7 +1187,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -939,7 +1215,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -948,7 +1228,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -957,7 +1241,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -966,7 +1254,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -992,7 +1284,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1001,7 +1297,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1010,7 +1310,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1019,7 +1323,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1043,7 +1351,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1052,7 +1364,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1061,7 +1377,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1070,7 +1390,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1094,7 +1418,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1103,7 +1431,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1112,7 +1444,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1121,7 +1457,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1145,7 +1485,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1154,7 +1498,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_1', 'other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1163,7 +1511,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1172,7 +1524,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1198,7 +1554,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': ['other_value_1'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
 
@@ -1210,7 +1570,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.bar': ['value_1', 'value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
   });
@@ -1234,7 +1598,11 @@ describe('merge_missing_fields_with_source', () => {
           'bar.keyword': ['bar_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1252,7 +1620,11 @@ describe('merge_missing_fields_with_source', () => {
           'host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1272,7 +1644,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1283,7 +1659,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar': ['other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: 'other_value_1',
         });
@@ -1301,7 +1681,11 @@ describe('merge_missing_fields_with_source', () => {
           '@timestamp': ['2023-02-10T10:15:50.000Z'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1317,7 +1701,11 @@ describe('merge_missing_fields_with_source', () => {
           '@timestamp': ['2023-02-10T10:15:50.000Z'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1335,7 +1723,11 @@ describe('merge_missing_fields_with_source', () => {
           'host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1351,7 +1743,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1363,7 +1759,11 @@ describe('merge_missing_fields_with_source', () => {
           'foo.bar.zed': ['zed_other_value_2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           foo: 'other_value_1',
         });
@@ -1380,7 +1780,11 @@ describe('merge_missing_fields_with_source', () => {
           '@timestamp': ['2023-02-10T10:15:50.000Z'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1395,7 +1799,11 @@ describe('merge_missing_fields_with_source', () => {
           '@timestamp': ['2023-02-10T10:15:50.000Z'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1410,15 +1818,15 @@ describe('merge_missing_fields_with_source', () => {
           'email.headers.x-test': ['from fields'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
-          email: {
-            headers: {
-              'x-test': 'from fields',
-            },
+          'email.headers': {
+            'x-test': 'from fields',
           },
-          // preserves conflicting keys if values contain empty objects
-          'email.headers': {},
         });
       });
 
@@ -1431,7 +1839,11 @@ describe('merge_missing_fields_with_source', () => {
           'email.headers.x-test': ['from fields'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
           'email.headers': {
             'x-test': 'from fields',
@@ -1448,7 +1860,11 @@ describe('merge_missing_fields_with_source', () => {
           'email.headers.x-test': ['from fields'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1461,7 +1877,11 @@ describe('merge_missing_fields_with_source', () => {
           'email.headers.x-test': ['from fields'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1474,7 +1894,11 @@ describe('merge_missing_fields_with_source', () => {
           'a.b.c.d': ['1', '2'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1487,7 +1911,11 @@ describe('merge_missing_fields_with_source', () => {
           'a.b.c': [{ d: '3 ' }, { d: '4' }],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
 
@@ -1500,7 +1928,11 @@ describe('merge_missing_fields_with_source', () => {
           'email.headers.x-test': ['a'],
         };
         const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-        const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+        const merged = mergeMissingFieldsWithSource({
+          doc,
+          ignoreFields: {},
+          ignoreFieldsRegexes: [],
+        })._source;
         expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
       });
     });
@@ -1525,7 +1957,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.mars': ['other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
 
@@ -1545,7 +1981,11 @@ describe('merge_missing_fields_with_source', () => {
         'foo.zed.mars': ['other_value_2'],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
   });
@@ -1560,7 +2000,11 @@ describe('merge_missing_fields_with_source', () => {
         foo: [{ bar: ['single_value'], zed: ['single_value'] }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({});
     });
 
@@ -1577,7 +2021,11 @@ describe('merge_missing_fields_with_source', () => {
         foo: [{ bar: ['single_value'], zed: ['single_value'] }],
       };
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
-      const merged = mergeMissingFieldsWithSource({ doc, ignoreFields: [] })._source;
+      const merged = mergeMissingFieldsWithSource({
+        doc,
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
+      })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>(_source);
     });
   });
@@ -1596,7 +2044,8 @@ describe('merge_missing_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeMissingFieldsWithSource({
         doc,
-        ignoreFields: ['value.should.ignore', '/[_]+/'],
+        ignoreFields: { 'value.should.ignore': true },
+        ignoreFieldsRegexes: ['/[_]+/'],
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1615,7 +2064,8 @@ describe('merge_missing_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeMissingFieldsWithSource({
         doc,
-        ignoreFields: ['other.string', '/[z]+/'], // Neither of these two should match anything
+        ignoreFields: { 'other.string': true },
+        ignoreFieldsRegexes: ['/[z]+/'], // Neither of these two should match anything
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
@@ -1646,7 +2096,8 @@ describe('merge_missing_fields_with_source', () => {
       const doc: SignalSourceHit = { ...emptyEsResult(), _source: cloneDeep(_source), fields };
       const merged = mergeMissingFieldsWithSource({
         doc,
-        ignoreFields: [],
+        ignoreFields: {},
+        ignoreFieldsRegexes: [],
       })._source;
       expect(merged).toEqual<ReturnTypeMergeFieldsWithSource>({
         foo: {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts
index b4de7a9d4bb1a..ad9d8bd34705b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/strategies/merge_missing_fields_with_source.ts
@@ -5,17 +5,13 @@
  * 2.0.
  */
 
-import { get } from 'lodash/fp';
-import { set } from '@kbn/safer-lodash-set';
-
-import type { SignalSource } from '../../../types';
-import { filterFieldEntries } from '../utils/filter_field_entries';
+import { filterFieldEntry } from '../utils/filter_field_entry';
 import type { FieldsType, MergeStrategyFunction } from '../types';
 import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields';
 import { isTypeObject } from '../utils/is_type_object';
 import { isNestedObject } from '../utils/is_nested_object';
-import { isPathValid } from '../utils/is_path_valid';
-import { buildFieldsKeyAsArrayMap } from '../utils/build_fields_key_as_array_map';
+import { robustGet, robustSet } from '../utils/robust_field_access';
+import { robustIsPathValid } from '../utils/is_path_valid';
 
 /**
  * Merges only missing sections of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information
@@ -26,36 +22,33 @@ import { buildFieldsKeyAsArrayMap } from '../utils/build_fields_key_as_array_map
  * it will not be added from fields.
  * @returns The two merged together in one object where we can
  */
-export const mergeMissingFieldsWithSource: MergeStrategyFunction = ({ doc, ignoreFields }) => {
+export const mergeMissingFieldsWithSource: MergeStrategyFunction = ({
+  doc,
+  ignoreFields,
+  ignoreFieldsRegexes,
+}) => {
   const source = doc._source ?? {};
   const fields = doc.fields ?? {};
-  const fieldEntries = Object.entries(fields);
-  const filteredEntries = filterFieldEntries(fieldEntries, ignoreFields);
-  const fieldsKeyMap = buildFieldsKeyAsArrayMap(source);
+  const fieldKeys = Object.keys(fields);
+
+  fieldKeys.forEach((fieldKey) => {
+    const valueInMergedDocument = robustGet({ key: fieldKey, document: source });
+    if (valueInMergedDocument == null && robustIsPathValid(fieldKey, source)) {
+      const fieldsValue = fields[fieldKey];
 
-  const transformedSource = filteredEntries.reduce(
-    (merged, [fieldsKeyAsString, fieldsValue]: [string, FieldsType]) => {
-      const fieldsKey = fieldsKeyMap[fieldsKeyAsString] ?? fieldsKeyAsString;
       if (
-        hasEarlyReturnConditions({
-          fieldsValue,
-          fieldsKey,
-          merged,
-        })
+        !hasEarlyReturnConditions(fieldsValue) &&
+        filterFieldEntry([fieldKey, fieldsValue], fieldKeys, ignoreFields, ignoreFieldsRegexes)
       ) {
-        return merged;
+        const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
+        return robustSet({ document: source, key: fieldKey, valueToSet: valueToMerge });
       }
-
-      const valueInMergedDocument = get(fieldsKey, merged);
-      const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument);
-      return set(merged, fieldsKey, valueToMerge);
-    },
-    { ...source }
-  );
+    }
+  });
 
   return {
     ...doc,
-    _source: transformedSource,
+    _source: source,
   };
 };
 
@@ -70,21 +63,6 @@ export const mergeMissingFieldsWithSource: MergeStrategyFunction = ({ doc, ignor
  * @param merged The merge document which is what we are testing conditions against
  * @returns true if we should return early, otherwise false
  */
-const hasEarlyReturnConditions = ({
-  fieldsValue,
-  fieldsKey,
-  merged,
-}: {
-  fieldsValue: FieldsType;
-  fieldsKey: string[] | string;
-  merged: SignalSource;
-}) => {
-  const valueInMergedDocument = get(fieldsKey, merged);
-  return (
-    fieldsValue.length === 0 ||
-    valueInMergedDocument !== undefined ||
-    !isPathValid(fieldsKey, merged) ||
-    isNestedObject(fieldsValue) ||
-    isTypeObject(fieldsValue)
-  );
+const hasEarlyReturnConditions = (fieldsValue: FieldsType) => {
+  return fieldsValue.length === 0 || isNestedObject(fieldsValue) || isTypeObject(fieldsValue);
 };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/types.ts
index 68d0c2f047727..670fba02b2aec 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/types.ts
@@ -20,7 +20,9 @@ export type FieldsType = string[] | number[] | boolean[] | object[];
 export type MergeStrategyFunction = ({
   doc,
   ignoreFields,
+  ignoreFieldsRegexes,
 }: {
   doc: SignalSourceHit;
-  ignoreFields: string[];
+  ignoreFields: Record<string, boolean>;
+  ignoreFieldsRegexes: string[];
 }) => SignalSourceHit;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.test.ts
deleted file mode 100644
index 1495fd4b9aaca..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.test.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { buildFieldsKeyAsArrayMap } from './build_fields_key_as_array_map';
-
-describe('buildFieldsKeyAsArrayMap()', () => {
-  it('returns primitive type if it passed as source', () => {
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(1)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(Infinity)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(NaN)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(false)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(null)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap(undefined)).toEqual({});
-    // @ts-expect-error
-    expect(buildFieldsKeyAsArrayMap([])).toEqual({});
-  });
-  it('builds map for nested source', () => {
-    expect(buildFieldsKeyAsArrayMap({ a: 'b' })).toEqual({ a: ['a'] });
-    expect(buildFieldsKeyAsArrayMap({ a: ['b'] })).toEqual({ a: ['a'] });
-    expect(buildFieldsKeyAsArrayMap({ a: { b: { c: 1 } } })).toEqual({
-      a: ['a'],
-      'a.b': ['a', 'b'],
-      'a.b.c': ['a', 'b', 'c'],
-    });
-    expect(buildFieldsKeyAsArrayMap({ a: { b: 'c' }, d: { e: 'f' } })).toEqual({
-      a: ['a'],
-      'a.b': ['a', 'b'],
-      d: ['d'],
-      'd.e': ['d', 'e'],
-    });
-  });
-
-  it('builds map for flattened source', () => {
-    expect(buildFieldsKeyAsArrayMap({ a: 'b' })).toEqual({ a: ['a'] });
-    expect(buildFieldsKeyAsArrayMap({ 'a.b.c': 1 })).toEqual({ 'a.b.c': ['a.b.c'] });
-    expect(buildFieldsKeyAsArrayMap({ 'a.b': 'c', 'd.e': 'f' })).toEqual({
-      'a.b': ['a.b'],
-      'd.e': ['d.e'],
-    });
-  });
-
-  it('builds map for arrays in a path', () => {
-    expect(buildFieldsKeyAsArrayMap({ a: { b: [{ c: 1 }, { c: 2 }] } })).toEqual({
-      a: ['a'],
-      'a.b': ['a', 'b'],
-      'a.b.c': ['a', 'b', 'c'],
-    });
-  });
-
-  it('builds map for mixed nested and flattened in a path', () => {
-    expect(
-      buildFieldsKeyAsArrayMap({
-        'a.b': { c: { d: 1 } },
-      })
-    ).toEqual({
-      'a.b': ['a.b'],
-      'a.b.c': ['a.b', 'c'],
-      'a.b.c.d': ['a.b', 'c', 'd'],
-    });
-  });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.ts
deleted file mode 100644
index 33e3814104921..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/build_fields_key_as_array_map.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { isPlainObject, isArray } from 'lodash';
-
-import type { SearchTypes } from '../../../../../../../common/detection_engine/types';
-
-const isObjectTypeGuard = (value: SearchTypes): value is Record<string, SearchTypes> => {
-  return isPlainObject(value);
-};
-
-function traverseSource(
-  document: SearchTypes,
-  result: Record<string, string[]> = {},
-  prefix: string[] = []
-): Record<string, string[]> {
-  if (prefix.length) {
-    result[prefix.join('.')] = prefix;
-  }
-
-  if (isObjectTypeGuard(document)) {
-    for (const [key, value] of Object.entries(document)) {
-      const path = [...prefix, key];
-
-      traverseSource(value, result, path);
-    }
-  } else if (isArray(document)) {
-    // for array of primitive values we can call traverseSource once
-    if (isPlainObject(document[0])) {
-      traverseSource(document[0], result, prefix);
-    } else {
-      document.forEach((doc) => {
-        traverseSource(doc, result, prefix);
-      });
-    }
-  }
-
-  return result;
-}
-
-/**
- * takes object document and creates map of string field keys to array field keys
- * source  `{ 'a.b': { c: { d: 1 } } }`
- * will result in map: `{
- *     'a.b': ['a.b'],
- *     'a.b.c': ['a.b', 'c'],
- *     'a.b.c.d': ['a.b', 'c', 'd'],
- *   }`
- * @param document - Record<string, SearchTypes>
- **/
-export function buildFieldsKeyAsArrayMap(
-  document: Record<string, SearchTypes>
-): Record<string, string[]> {
-  if (!isPlainObject(document)) {
-    return {};
-  }
-
-  return traverseSource(document);
-}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.test.ts
deleted file mode 100644
index 7288e7d1e9b80..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.test.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { filterFieldEntries } from './filter_field_entries';
-import type { FieldsType } from '../types';
-
-describe('filter_field_entries', () => {
-  beforeAll(() => {
-    jest.resetAllMocks();
-  });
-
-  afterEach(() => {
-    jest.resetAllMocks();
-  });
-
-  /** Dummy test value */
-  const dummyValue = ['value'];
-
-  /**
-   * Get the return type of the mergeFieldsWithSource for TypeScript checks against expected
-   */
-  type ReturnTypeFilterFieldEntries = ReturnType<typeof filterFieldEntries>;
-
-  test('returns a single valid fieldEntries as expected', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [['foo.bar', dummyValue]];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>(
-      fieldEntries
-    );
-  });
-
-  test('removes invalid dotted entries', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['.', dummyValue],
-      ['foo.bar', dummyValue],
-      ['..', dummyValue],
-      ['foo..bar', dummyValue],
-    ];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>([
-      ['foo.bar', dummyValue],
-    ]);
-  });
-
-  test('removes multi-field values such "foo.keyword" mixed with "foo" and prefers just "foo" for 1st level', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['foo', dummyValue],
-      ['foo.keyword', dummyValue], // <-- "foo.keyword" multi-field should be removed
-      ['bar.keyword', dummyValue], // <-- "bar.keyword" multi-field should be removed
-      ['bar', dummyValue],
-    ];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>([
-      ['foo', dummyValue],
-      ['bar', dummyValue],
-    ]);
-  });
-
-  test('removes multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['host.name', dummyValue],
-      ['host.name.keyword', dummyValue], // <-- multi-field should be removed
-      ['host.hostname', dummyValue],
-      ['host.hostname.keyword', dummyValue], // <-- multi-field should be removed
-    ];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>([
-      ['host.name', dummyValue],
-      ['host.hostname', dummyValue],
-    ]);
-  });
-
-  test('removes multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['foo.host.name', dummyValue],
-      ['foo.host.name.keyword', dummyValue], // <-- multi-field should be removed
-      ['foo.host.hostname', dummyValue],
-      ['foo.host.hostname.keyword', dummyValue], // <-- multi-field should be removed
-    ];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>([
-      ['foo.host.name', dummyValue],
-      ['foo.host.hostname', dummyValue],
-    ]);
-  });
-
-  test('ignores fields of "_ignore", for eql bug https://github.com/elastic/elasticsearch/issues/77152', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['_ignored', dummyValue],
-      ['foo.host.hostname', dummyValue],
-    ];
-    expect(filterFieldEntries(fieldEntries, [])).toEqual<ReturnTypeFilterFieldEntries>([
-      ['foo.host.hostname', dummyValue],
-    ]);
-  });
-
-  test('ignores fields given strings and regular expressions in the ignoreFields list', () => {
-    const fieldEntries: Array<[string, FieldsType]> = [
-      ['host.name', dummyValue],
-      ['user.name', dummyValue], // <-- string from ignoreFields should ignore this
-      ['host.hostname', dummyValue],
-      ['_odd.value', dummyValue], // <-- regular expression from ignoreFields should ignore this
-    ];
-    expect(
-      filterFieldEntries(fieldEntries, ['user.name', '/[_]+/'])
-    ).toEqual<ReturnTypeFilterFieldEntries>([
-      ['host.name', dummyValue],
-      ['host.hostname', dummyValue],
-    ]);
-  });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.test.ts
new file mode 100644
index 0000000000000..b9ae033f425fa
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.test.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { filterFieldEntry } from './filter_field_entry';
+
+describe('filter_field_entry', () => {
+  beforeAll(() => {
+    jest.resetAllMocks();
+  });
+
+  afterEach(() => {
+    jest.resetAllMocks();
+  });
+
+  /** Dummy test value */
+  const dummyValue = ['value'];
+
+  test('returns true for a valid field entry', () => {
+    const fieldsKeys: string[] = ['foo.bar'];
+    expect(filterFieldEntry(['foo.bar', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+  });
+
+  test('returns false for invalid dotted entries', () => {
+    const fieldsKeys: string[] = ['.', 'foo.bar', '..', 'foo..bar'];
+    expect(filterFieldEntry(['.', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['foo.bar', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['..', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['foo..bar', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+  });
+
+  test('removes multi-field values such "foo.keyword" mixed with "foo" and prefers just "foo" for 1st level', () => {
+    const fieldsKeys: string[] = [
+      'foo',
+      'foo.keyword', // <-- "foo.keyword" multi-field should be removed
+      'bar.keyword', // <-- "bar.keyword" multi-field should be removed
+      'bar',
+    ];
+    expect(filterFieldEntry(['foo', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['foo.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['bar.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['bar', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+  });
+
+  test('removes multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => {
+    const fieldsKeys: string[] = [
+      'host.name',
+      'host.name.keyword', // <-- multi-field should be removed
+      'host.hostname',
+      'host.hostname.keyword', // <-- multi-field should be removed
+    ];
+    expect(filterFieldEntry(['host.name', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['host.name.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['host.hostname', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['host.hostname.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(
+      false
+    );
+  });
+
+  test('removes multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => {
+    const fieldsKeys: string[] = [
+      'foo.host.name',
+      'foo.host.name.keyword', // <-- multi-field should be removed
+      'foo.host.hostname',
+      'foo.host.hostname.keyword', // <-- multi-field should be removed
+    ];
+    expect(filterFieldEntry(['foo.host.name', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['foo.host.name.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(
+      false
+    );
+    expect(filterFieldEntry(['foo.host.hostname', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+    expect(filterFieldEntry(['foo.host.hostname.keyword', dummyValue], fieldsKeys, {}, [])).toEqual(
+      false
+    );
+  });
+
+  test('ignores fields of "_ignore", for eql bug https://github.com/elastic/elasticsearch/issues/77152', () => {
+    const fieldsKeys: string[] = ['_ignored', 'foo.host.hostname'];
+    expect(filterFieldEntry(['_ignored', dummyValue], fieldsKeys, {}, [])).toEqual(false);
+    expect(filterFieldEntry(['foo.host.hostname', dummyValue], fieldsKeys, {}, [])).toEqual(true);
+  });
+
+  test('ignores fields given strings and regular expressions in the ignoreFields list', () => {
+    const fieldsKeys: string[] = [
+      'host.name',
+      'user.name', // <-- string from ignoreFields should ignore this
+      'host.hostname',
+      '_odd.value', // <-- regular expression from ignoreFields should ignore this
+    ];
+    expect(
+      filterFieldEntry(['host.name', dummyValue], fieldsKeys, { 'user.name': true }, ['/[_]+/'])
+    ).toEqual(true);
+    expect(
+      filterFieldEntry(['user.name', dummyValue], fieldsKeys, { 'user.name': true }, ['/[_]+/'])
+    ).toEqual(false);
+    expect(
+      filterFieldEntry(['host.hostname', dummyValue], fieldsKeys, { 'user.name': true }, ['/[_]+/'])
+    ).toEqual(true);
+    expect(
+      filterFieldEntry(['_odd.value', dummyValue], fieldsKeys, { 'user.name': true }, ['/[_]+/'])
+    ).toEqual(false);
+  });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.ts
similarity index 58%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.ts
rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.ts
index 581dd7ffbff1a..3a18d36334070 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entries.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/filter_field_entry.ts
@@ -18,23 +18,25 @@ import { isEqlBug77152 } from './is_eql_bug_77152';
  * are invalid runtime field names. Also matches type objects such as geo-points and we ignore
  * those and don't try to merge those.
  *
- * @param fieldEntries The field entries to filter
+ * @param fieldEntry The specific entry to test
+ * @param fieldEntries The full list of field entries, so we can check for multifields
  * @param ignoreFields Array of fields to ignore. If a value starts and ends with "/", such as: "/[_]+/" then the field will be treated as a regular expression.
  * If you have an object structure to ignore such as "{ a: { b: c: {} } } ", then you need to ignore it as the string "a.b.c"
- * @returns The field entries filtered
+ * @returns boolean Whether or not the field entry is valid for merging into _source
  */
-export const filterFieldEntries = (
-  fieldEntries: Array<[string, FieldsType]>,
-  ignoreFields: string[]
-): Array<[string, FieldsType]> => {
-  return fieldEntries.filter(([fieldsKey, fieldsValue]: [string, FieldsType]) => {
-    return (
-      // TODO: Look at not filtering this and instead transform it so it can be inserted correctly in the strategies which does an overwrite of everything from fields
-      !isEqlBug77152(fieldsKey) &&
-      !isIgnored(fieldsKey, ignoreFields) &&
-      !isInvalidKey(fieldsKey) &&
-      !isMultiField(fieldsKey, fieldEntries) &&
-      !isTypeObject(fieldsValue)
-    );
-  });
+export const filterFieldEntry = (
+  fieldEntry: [string, FieldsType],
+  fieldsKeys: string[],
+  ignoreFields: Record<string, boolean>,
+  ignoreFieldRegexes: string[]
+): boolean => {
+  const [fieldsKey, fieldsValue] = fieldEntry;
+  // TODO: Look at not filtering this and instead transform it so it can be inserted correctly in the strategies which does an overwrite of everything from fields
+  return (
+    !isEqlBug77152(fieldsKey) &&
+    !isIgnored(fieldsKey, ignoreFields, ignoreFieldRegexes) &&
+    !isInvalidKey(fieldsKey) &&
+    !isMultiField(fieldsKey, fieldsKeys) &&
+    !isTypeObject(fieldsValue)
+  );
 };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/index.ts
index 87b1097dd9bca..4c48b585167b8 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/index.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/index.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 export * from './array_in_path_exists';
-export * from './filter_field_entries';
+export * from './filter_field_entry';
 export * from './is_array_of_primitives';
 export * from './is_ignored';
 export * from './is_invalid_key';
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.test.ts
index e4a7093ef127c..a4aa55cbabcb1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.test.ts
@@ -17,68 +17,72 @@ describe('is_ignored', () => {
   });
 
   describe('string matching', () => {
-    test('it returns false if given an empty array', () => {
-      expect(isIgnored('simple.value', [])).toEqual(false);
+    test('it returns false if given an empty object and empty array', () => {
+      expect(isIgnored('simple.value', {}, [])).toEqual(false);
     });
 
     test('it returns true if a simple string value matches', () => {
-      expect(isIgnored('simple.value', ['simple.value'])).toEqual(true);
+      expect(isIgnored('simple.value', { 'simple.value': true }, [])).toEqual(true);
     });
 
     test('it returns false if a simple string value does not match', () => {
-      expect(isIgnored('simple', ['simple.value'])).toEqual(false);
+      expect(isIgnored('simple', { 'simple.value': true }, [])).toEqual(false);
     });
 
     test('it returns true if a simple string value matches with two strings', () => {
-      expect(isIgnored('simple.value', ['simple.value', 'simple.second.value'])).toEqual(true);
+      expect(
+        isIgnored('simple.value', { 'simple.value': true, 'simple.second.value': true }, [])
+      ).toEqual(true);
     });
 
     test('it returns true if a simple string value matches the second string', () => {
-      expect(isIgnored('simple.second.value', ['simple.value', 'simple.second.value'])).toEqual(
-        true
-      );
+      expect(
+        isIgnored('simple.second.value', { 'simple.value': true, 'simple.second.value': true }, [])
+      ).toEqual(true);
     });
 
     test('it returns false if a simple string value does not match two strings', () => {
-      expect(isIgnored('simple', ['simple.value', 'simple.second.value'])).toEqual(false);
+      expect(
+        isIgnored('simple', { 'simple.value': true, 'simple.second.value': true }, [])
+      ).toEqual(false);
     });
 
     test('it returns true if mixed with a regular expression in the list', () => {
-      expect(isIgnored('simple', ['simple', '/[_]+/'])).toEqual(true);
+      expect(isIgnored('simple', { simple: true }, ['/[_]+/'])).toEqual(true);
     });
   });
 
   describe('regular expression matching', () => {
     test('it returns true if a simple regular expression matches', () => {
-      expect(isIgnored('_ignored', ['/[_]+/'])).toEqual(true);
+      expect(isIgnored('_ignored', {}, ['/[_]+/'])).toEqual(true);
     });
 
     test('it returns false if a simple regular expression does not match', () => {
-      expect(isIgnored('simple', ['/[_]+/'])).toEqual(false);
+      expect(isIgnored('simple', {}, ['/[_]+/'])).toEqual(false);
     });
 
     test('it returns true if a simple regular expression matches a longer string', () => {
-      expect(isIgnored('___ignored', ['/[_]+/'])).toEqual(true);
+      expect(isIgnored('___ignored', {}, ['/[_]+/'])).toEqual(true);
     });
 
     test('it returns true if mixed with regular stings', () => {
-      expect(isIgnored('___ignored', ['simple', '/[_]+/'])).toEqual(true);
+      expect(isIgnored('___ignored', { simple: true }, ['/[_]+/'])).toEqual(true);
     });
 
     test('it returns true with start anchor', () => {
-      expect(isIgnored('_ignored', ['simple', '/^[_]+/'])).toEqual(true);
+      expect(isIgnored('_ignored', { simple: true }, ['/^[_]+/'])).toEqual(true);
     });
 
     test('it returns false with start anchor', () => {
-      expect(isIgnored('simple.something_', ['simple', '/^[_]+/'])).toEqual(false);
+      expect(isIgnored('simple.something_', { simple: true }, ['/^[_]+/'])).toEqual(false);
     });
 
     test('it returns true with end anchor', () => {
-      expect(isIgnored('something_', ['simple', '/[_]+$/'])).toEqual(true);
+      expect(isIgnored('something_', { simple: true }, ['/[_]+$/'])).toEqual(true);
     });
 
     test('it returns false with end anchor', () => {
-      expect(isIgnored('_something', ['simple', '/[_]+$/'])).toEqual(false);
+      expect(isIgnored('_something', { simple: true }, ['/[_]+$/'])).toEqual(false);
     });
   });
 });
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.ts
index a418ce735626d..46638293bce17 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_ignored.ts
@@ -12,12 +12,13 @@
  * If you have an object structure to ignore such as "{ a: { b: c: {} } } ", then you need to ignore it as the string "a.b.c"
  * @returns true if it is a field to ignore, otherwise false
  */
-export const isIgnored = (fieldsKey: string, ignoreFields: string[]): boolean => {
-  return ignoreFields.some((ignoreField) => {
-    if (ignoreField.startsWith('/') && ignoreField.endsWith('/')) {
-      return new RegExp(ignoreField.slice(1, -1)).test(fieldsKey);
-    } else {
-      return fieldsKey === ignoreField;
-    }
-  });
+export const isIgnored = (
+  fieldsKey: string,
+  ignoreFields: Record<string, boolean>,
+  ignoreFieldRegexes: string[]
+): boolean => {
+  return (
+    (ignoreFields[fieldsKey] ? true : false) ||
+    ignoreFieldRegexes.some((regex) => new RegExp(regex.slice(1, -1)).test(fieldsKey))
+  );
 };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.test.ts
index a8050b600b464..8b85557ee521a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.test.ts
@@ -16,25 +16,23 @@ describe('is_multifield', () => {
     jest.resetAllMocks();
   });
 
-  const dummyValue = ['value'];
-
   test('it returns true if the string "foo.bar" is a multiField', () => {
-    expect(isMultiField('foo.bar', [['foo', dummyValue]])).toEqual(true);
+    expect(isMultiField('foo.bar', ['foo'])).toEqual(true);
   });
 
   test('it returns false if the string "foo" is not a multiField', () => {
-    expect(isMultiField('foo', [['foo', dummyValue]])).toEqual(false);
+    expect(isMultiField('foo', ['foo'])).toEqual(false);
   });
 
   test('it returns false if we have a sibling string and are not a multiField', () => {
-    expect(isMultiField('foo.bar', [['foo.mar', dummyValue]])).toEqual(false);
+    expect(isMultiField('foo.bar', ['foo.mar'])).toEqual(false);
   });
 
   test('it returns true for a 3rd level match of being a sub-object. Runtime fields can have multiple layers of multiFields', () => {
-    expect(isMultiField('foo.mars.bar', [['foo', dummyValue]])).toEqual(true);
+    expect(isMultiField('foo.mars.bar', ['foo'])).toEqual(true);
   });
 
   test('it returns true for a 3rd level match against a 2nd level sub-object. Runtime fields can have multiple layers of multiFields', () => {
-    expect(isMultiField('foo.mars.bar', [['foo.mars', dummyValue]])).toEqual(true);
+    expect(isMultiField('foo.mars.bar', ['foo.mars'])).toEqual(true);
   });
 });
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.ts
index e9e51f9f50389..e19ff40a32e90 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_multifield.ts
@@ -5,8 +5,6 @@
  * 2.0.
  */
 
-import type { FieldsType } from '../types';
-
 /**
  * Returns true if we are a multiField when passed in a fields entry and a fields key,
  * otherwise false. Notice that runtime fields can have multiple levels of multiFields which is kind a problem
@@ -16,18 +14,15 @@ import type { FieldsType } from '../types';
  * @param fieldEntries The entries to check against.
  * @returns True if we are a subObject, otherwise false.
  */
-export const isMultiField = (
-  fieldsKey: string,
-  fieldEntries: Array<[string, FieldsType]>
-): boolean => {
+export const isMultiField = (fieldsKey: string, fieldsKeys: string[]): boolean => {
   const splitPath = fieldsKey.split('.');
   return splitPath.some((_, index, array) => {
     if (index + 1 === array.length) {
       return false;
     } else {
       const newPath = [...array].splice(0, index + 1).join('.');
-      return fieldEntries.some(([fieldKeyToCheck]) => {
-        return fieldKeyToCheck === newPath;
+      return fieldsKeys.some((key) => {
+        return key === newPath;
       });
     }
   });
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts
index 39d215d4c5062..be4f35f1948f9 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts
@@ -6,6 +6,7 @@
  */
 
 import { isObjectLike } from 'lodash/fp';
+import { isPlainObject } from 'lodash';
 import type { SearchTypes } from '../../../../../../../common/detection_engine/types';
 
 /**
@@ -23,3 +24,7 @@ export const isObjectLikeOrArrayOfObjectLikes = (
     return isObjectLike(valueInMergedDocument);
   }
 };
+
+export const isObjectTypeGuard = (value: SearchTypes): value is Record<string, SearchTypes> => {
+  return isPlainObject(value);
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts
index 896adb2326a8f..6cdac6c1662e1 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.test.ts
@@ -5,138 +5,97 @@
  * 2.0.
  */
 
-import { isPathValid } from './is_path_valid';
-
-describe('isPathValid', () => {
-  test('not valid when empty array is key', () => {
-    expect(isPathValid([], {})).toEqual(false);
-  });
+import { robustIsPathValid } from './is_path_valid';
 
+describe('robustIsPathValid', () => {
   test('valid when empty string is key', () => {
-    expect(isPathValid('', {})).toEqual(true);
-    expect(isPathValid([''], {})).toEqual(true);
+    expect(robustIsPathValid('', {})).toEqual(true);
   });
 
   test('valid when a path and empty object', () => {
-    expect(isPathValid(['a', 'b', 'c'], {})).toEqual(true);
-    expect(isPathValid('a.b.c', {})).toEqual(true);
+    expect(robustIsPathValid('a.b.c', {})).toEqual(true);
   });
 
   test('not valid when a path and an array exists', () => {
-    expect(isPathValid(['a'], { a: [] })).toEqual(false);
-    expect(isPathValid('a', { a: [] })).toEqual(false);
+    expect(robustIsPathValid('a', { a: [] })).toEqual(false);
   });
 
   test('not valid when a path and primitive value exists', () => {
-    expect(isPathValid(['a'], { a: 'test' })).toEqual(false);
-    expect(isPathValid(['a'], { a: 1 })).toEqual(false);
-    expect(isPathValid(['a'], { a: true })).toEqual(false);
-
-    expect(isPathValid('a', { a: 'test' })).toEqual(false);
-    expect(isPathValid('a', { a: 1 })).toEqual(false);
-    expect(isPathValid('a', { a: true })).toEqual(false);
+    expect(robustIsPathValid('a', { a: 'test' })).toEqual(false);
+    expect(robustIsPathValid('a', { a: 1 })).toEqual(false);
+    expect(robustIsPathValid('a', { a: true })).toEqual(false);
   });
 
   test('valid when a path and object value exists', () => {
-    expect(isPathValid(['a'], { a: {} })).toEqual(true);
-
-    expect(isPathValid('a', { a: {} })).toEqual(true);
+    expect(robustIsPathValid('a', { a: {} })).toEqual(true);
   });
 
   test('not valid when a path and an array exists within the parent path at level 1', () => {
-    expect(isPathValid(['a', 'b'], { a: [] })).toEqual(false);
-
-    expect(isPathValid('a.b', { a: [] })).toEqual(false);
+    expect(robustIsPathValid('a.b', { a: [] })).toEqual(false);
   });
 
   test('not valid when a path and primitive value exists within the parent path at level 1', () => {
-    expect(isPathValid(['a', 'b'], { a: 'test' })).toEqual(false);
-    expect(isPathValid(['a', 'b'], { a: 1 })).toEqual(false);
-    expect(isPathValid(['a', 'b'], { a: true })).toEqual(false);
-
-    expect(isPathValid('a.b', { a: 'test' })).toEqual(false);
-    expect(isPathValid('a.b', { a: 1 })).toEqual(false);
-    expect(isPathValid('a.b', { a: true })).toEqual(false);
+    expect(robustIsPathValid('a.b', { a: 'test' })).toEqual(false);
+    expect(robustIsPathValid('a.b', { a: 1 })).toEqual(false);
+    expect(robustIsPathValid('a.b', { a: true })).toEqual(false);
   });
 
   test('valid when a path and object value exists within the parent path at level 1', () => {
-    expect(isPathValid(['a', 'b'], { a: {} })).toEqual(true);
-
-    expect(isPathValid('a.b', { a: {} })).toEqual(true);
+    expect(robustIsPathValid('a.b', { a: {} })).toEqual(true);
   });
 
   test('not valid when a path and an array exists within the parent path at level 2', () => {
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: [] } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { 'a.b': [] })).toEqual(false);
-
-    expect(isPathValid('a.b.c', { a: { b: [] } })).toEqual(false);
-    expect(isPathValid('a.b.c', { 'a.b': [] })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: [] } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { 'a.b': [] })).toEqual(false);
   });
 
   test('not valid when a path and primitive value exists within the parent path at level 2', () => {
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: 'test' } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: 1 } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: true } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { 'a.b': true })).toEqual(false);
-
-    expect(isPathValid('a.b.c', { a: { b: 'test' } })).toEqual(false);
-    expect(isPathValid('a.b.c', { a: { b: 1 } })).toEqual(false);
-    expect(isPathValid('a.b.c', { a: { b: true } })).toEqual(false);
-    expect(isPathValid('a.b.c', { 'a.b': true })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: 'test' } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: 1 } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: true } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { 'a.b': true })).toEqual(false);
   });
 
   test('valid when a path and object value exists within the parent path at the last level 2', () => {
-    expect(isPathValid(['a', 'b'], { a: { b: {} } })).toEqual(true);
-
-    expect(isPathValid('a.b', { a: { b: {} } })).toEqual(true);
+    expect(robustIsPathValid('a.b', { a: { b: {} } })).toEqual(true);
   });
 
   test('not valid when a path and an array exists within the parent path at the last level 3', () => {
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: { c: [] } } })).toEqual(false);
-
-    expect(isPathValid('a.b.c', { a: { b: { c: [] } } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: { c: [] } } })).toEqual(false);
   });
 
   test('not valid when a path and primitive value exists within the parent path at the last level 3', () => {
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: { c: 'test' } } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: { c: 1 } } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: { c: true } } })).toEqual(false);
-    expect(isPathValid(['a', 'b', 'c'], { 'a.b.c': true })).toEqual(false);
-
-    expect(isPathValid('a.b.c', { a: { b: { c: 'test' } } })).toEqual(false);
-    expect(isPathValid('a.b.c', { a: { b: { c: 1 } } })).toEqual(false);
-    expect(isPathValid('a.b.c', { a: { b: { c: true } } })).toEqual(false);
-    expect(isPathValid('a.b.c', { 'a.b.c': true })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: { c: 'test' } } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: { c: 1 } } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { b: { c: true } } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { 'a.b.c': true })).toEqual(false);
   });
 
   test('valid when a path and object value exists within the parent path at the last level 3', () => {
-    expect(isPathValid(['a', 'b', 'c'], { a: { b: { c: {} } } })).toEqual(true);
-    expect(isPathValid(['a', 'b', 'c'], { 'a.b.c': {} })).toEqual(true);
-
-    expect(isPathValid('a.b.c', { a: { b: { c: {} } } })).toEqual(true);
-    expect(isPathValid('a.b.c', { 'a.b.c': {} })).toEqual(true);
+    expect(robustIsPathValid('a.b.c', { a: { b: { c: {} } } })).toEqual(true);
+    expect(robustIsPathValid('a.b.c', { 'a.b.c': {} })).toEqual(true);
   });
 
-  test('valid when any key has dot notation', () => {
-    expect(isPathValid(['a', 'b.c'], { a: { 'b.c': {} } })).toEqual(true);
-    expect(isPathValid(['a.b', 'c'], { 'a.b': { c: {} } })).toEqual(true);
-    expect(isPathValid(['a', 'b.c', 'd'], { a: { 'b.c': { d: {} } } })).toEqual(true);
+  test('valid when using dot notation', () => {
+    expect(robustIsPathValid('a.b.c', { a: { 'b.c': {} } })).toEqual(true);
+    expect(robustIsPathValid('a.b.c', { 'a.b': { c: {} } })).toEqual(true);
+    expect(robustIsPathValid('a.b.c.d', { a: { 'b.c': { d: {} } } })).toEqual(true);
   });
 
-  test('not valid when any key has dot notation and array is present in source on the last level', () => {
-    expect(isPathValid(['a', 'b.c'], { a: { 'b.c': [] } })).toEqual(false);
-    expect(isPathValid(['a.b', 'c'], { 'a.b': { c: [] } })).toEqual(false);
-    expect(isPathValid(['a', 'b.c', 'd'], { a: { 'b.c': { d: [] } } })).toEqual(false);
+  test('not valid when using dot notation and array is present in source on the last level', () => {
+    expect(robustIsPathValid('a.b.c', { a: { 'b.c': [] } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { 'a.b': { c: [] } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c.d', { a: { 'b.c': { d: [] } } })).toEqual(false);
   });
 
   test('not valid when any key has dot notation and primitive value is present in source on the last level', () => {
-    expect(isPathValid(['a', 'b.c'], { a: { 'b.c': 1 } })).toEqual(false);
-    expect(isPathValid(['a.b', 'c'], { 'a.b': { c: 1 } })).toEqual(false);
-    expect(isPathValid(['a', 'b.c', 'd'], { a: { 'b.c': { d: 1 } } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { a: { 'b.c': 1 } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c', { 'a.b': { c: 1 } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c.d', { a: { 'b.c': { d: 1 } } })).toEqual(false);
   });
 
   test('not valid when any key has dot notation and array is present in source on level 2', () => {
-    expect(isPathValid(['a', 'b.c', 'd'], { a: { 'b.c': [] } })).toEqual(false);
-    expect(isPathValid(['a.b', 'c', 'd'], { 'a.b': { c: [] } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c.d', { a: { 'b.c': [] } })).toEqual(false);
+    expect(robustIsPathValid('a.b.c.d', { 'a.b': { c: [] } })).toEqual(false);
   });
 });
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts
index 6e193e237696d..f9c87088bd25b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/is_path_valid.ts
@@ -5,32 +5,32 @@
  * 2.0.
  */
 
-import { get, isPlainObject } from 'lodash/fp';
-import type { SignalSource } from '../../../types';
+import { isObjectTypeGuard } from './is_objectlike_or_array_of_objectlikes';
 
 /**
  * Returns true if path in SignalSource object is valid
  * Path is valid if each field in hierarchy is object or undefined
  * Path is not valid if ANY of field in hierarchy is not object or undefined
- * @param path in source to check within source
- * @param source The source document
+ * The function is robust in that it can handle any mix of dot and nested notation in the document
+ * @param key Path (dot-notation) to check for validity
+ * @param document Document to search
  * @returns boolean
  */
-export const isPathValid = (path: string[] | string, source: SignalSource): boolean => {
-  if (path == null) {
-    return false;
+export const robustIsPathValid = (key: string, document: Record<string, unknown>): boolean => {
+  const splitKey = key.split('.');
+  let tempKey = splitKey[0];
+  for (let i = 0; i < splitKey.length; i++) {
+    if (i > 0) {
+      tempKey += `.${splitKey[i]}`;
+    }
+    const value = document[tempKey];
+    if (value != null) {
+      if (!isObjectTypeGuard(value)) {
+        return false;
+      } else {
+        return robustIsPathValid(splitKey.slice(i + 1).join('.'), value);
+      }
+    }
   }
-  const pathAsArray = typeof path === 'string' ? path.split('.') : path;
-
-  if (pathAsArray.length === 0) {
-    return false;
-  }
-
-  return pathAsArray.every((_, index, array) => {
-    const newPath = [...array].splice(0, index + 1);
-    // _.get won't retrieve value of flattened key 'a.b' when receives path ['a', 'b'].
-    // so we would try to call _.get with dot-notation path if array path results in undefined
-    const valueToCheck = get(newPath, source) ?? get(newPath.join('.'), source);
-    return valueToCheck === undefined || isPlainObject(valueToCheck);
-  });
+  return true;
 };
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.test.ts
new file mode 100644
index 0000000000000..646068b59eec7
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.test.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { robustGet, robustSet } from './robust_field_access';
+
+describe('robust field access', () => {
+  describe('get', () => {
+    it('fetches a value with basic key', () => {
+      expect(robustGet({ key: 'a.b.c', document: { a: { b: { c: 'my-value' } } } })).toEqual(
+        'my-value'
+      );
+    });
+
+    it('fetches a value with mixed notation', () => {
+      expect(robustGet({ key: 'a.b.c', document: { 'a.b': { c: 'my-value' } } })).toEqual(
+        'my-value'
+      );
+    });
+
+    it('fetches a value with different mixed notation', () => {
+      expect(robustGet({ key: 'a.b.c', document: { a: { 'b.c': 'my-value' } } })).toEqual(
+        'my-value'
+      );
+    });
+
+    it('fetches a value using only dot notation', () => {
+      expect(robustGet({ key: 'a.b.c', document: { 'a.b.c': 'my-value' } })).toEqual('my-value');
+    });
+
+    it('returns undefined if the key does not exist', () => {
+      expect(robustGet({ key: 'a.b.c', document: { a: { b: 'my-value' } } })).toEqual(undefined);
+    });
+  });
+
+  describe('set', () => {
+    it('sets a value with a basic key', () => {
+      expect(robustSet({ key: 'a.b.c', valueToSet: 'test-value', document: {} })).toEqual({
+        a: { b: { c: 'test-value' } },
+      });
+    });
+
+    it('sets a value inside an object at a dot notation path', () => {
+      expect(
+        robustSet({ key: 'a.b.c', valueToSet: 'test-value', document: { 'a.b': {} } })
+      ).toEqual({
+        'a.b': { c: 'test-value' },
+      });
+    });
+
+    it('sets a value inside an object at a nested notation path', () => {
+      expect(
+        robustSet({ key: 'a.b.c', valueToSet: 'test-value', document: { a: { b: {} } } })
+      ).toEqual({
+        a: { b: { c: 'test-value' } },
+      });
+    });
+
+    it('sets a value and overwrites the existing value with dot notation', () => {
+      expect(
+        robustSet({ key: 'a.b.c', valueToSet: 'test-new', document: { 'a.b.c': 'test-original' } })
+      ).toEqual({
+        'a.b.c': 'test-new',
+      });
+    });
+
+    it('sets a value and overwrites the existing value with nested notation', () => {
+      expect(
+        robustSet({
+          key: 'a.b.c',
+          valueToSet: 'test-new',
+          document: { a: { b: { c: 'test-original' } } },
+        })
+      ).toEqual({
+        a: { b: { c: 'test-new' } },
+      });
+    });
+
+    it('sets a value and overwrites the existing value with mixed notation', () => {
+      expect(
+        robustSet({
+          key: 'a.b.c',
+          valueToSet: 'test-new',
+          document: { 'a.b': { c: 'test-original' } },
+        })
+      ).toEqual({
+        'a.b': { c: 'test-new' },
+      });
+    });
+
+    it('sets a value and ignores non-object values on the path', () => {
+      expect(
+        robustSet({
+          key: 'a.b.c',
+          valueToSet: 'test-new',
+          document: { 'a.b': 'test-ignore' },
+        })
+      ).toEqual({
+        'a.b': 'test-ignore',
+        a: { b: { c: 'test-new' } },
+      });
+    });
+
+    it('sets the value correctly if an object already exists at the path', () => {
+      expect(
+        robustSet({
+          key: 'a.b',
+          valueToSet: 'test-new',
+          document: { 'a.b': {} },
+        })
+      ).toEqual({ 'a.b': 'test-new' });
+    });
+  });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.ts
new file mode 100644
index 0000000000000..30d716896056d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/source_fields_merging/utils/robust_field_access.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { set } from '@kbn/safer-lodash-set';
+
+import type { SearchTypes } from '../../../../../../../common/detection_engine/types';
+import { isObjectTypeGuard } from './is_objectlike_or_array_of_objectlikes';
+
+/**
+ * Similar to lodash `get`, but instead of handling only pure dot or nested notation this function handles any mix of dot and nested notation
+ *
+ * Note: this function makes no attempt to handle arrays in the middle of the path because it's only used to fetch values based on paths from
+ * the `fields` option on search requests, which can never have arrays in the middle of the path
+ * @param key Path to field, in dot notation
+ * @param document Object to fetch value from
+ * @returns Fetched value or undefined
+ */
+export const robustGet = ({
+  key,
+  document,
+}: {
+  key: string;
+  document: Record<string, unknown>;
+}): SearchTypes => {
+  const fastPathValue = document[key];
+  if (fastPathValue != null) {
+    return fastPathValue;
+  }
+  const splitKey = key.split('.');
+  let tempKey = splitKey[0];
+  for (let i = 0; i < splitKey.length - 1; i++) {
+    if (i > 0) {
+      tempKey += `.${splitKey[i]}`;
+    }
+    const value = document[tempKey];
+    if (value != null) {
+      if (isObjectTypeGuard(value)) {
+        return robustGet({ key: splitKey.slice(i + 1).join('.'), document: value });
+      } else {
+        return undefined;
+      }
+    }
+  }
+  return undefined;
+};
+
+/**
+ * Similar to lodash set, but instead of handling only pure dot or nested notation this function handles any mix of dot and nested notation
+ * @param key Path to field, in dot notation
+ * @param valueToSet Value to insert into document
+ * @param document Object to insert value into
+ * @returns Updated document
+ */
+export const robustSet = <T extends Record<string, unknown>>({
+  key,
+  valueToSet,
+  document,
+}: {
+  key: string;
+  valueToSet: SearchTypes;
+  document: T;
+}) => {
+  const splitKey = key.split('.');
+  let tempKey = splitKey[0];
+  for (let i = 0; i < splitKey.length - 1; i++) {
+    if (i > 0) {
+      tempKey += `.${splitKey[i]}`;
+    }
+    const value = document[tempKey];
+    if (value != null) {
+      if (isObjectTypeGuard(value)) {
+        robustSet({ key: splitKey.slice(i + 1).join('.'), valueToSet, document: value });
+        return document;
+      }
+    }
+  }
+  return set(document, key, valueToSet);
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.test.ts
index 5baa84ff913b2..4d47c279f1495 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.test.ts
@@ -16,13 +16,13 @@ import {
 } from '@kbn/rule-data-utils';
 
 import type { CompleteRule, ThreatRuleParams } from '../../rule_schema';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 
 import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
 
-jest.mock('../factories/utils/build_bulk_body', () => ({ buildBulkBody: jest.fn() }));
+jest.mock('../factories/utils/transform_hit_to_alert', () => ({ transformHitToAlert: jest.fn() }));
 
-const buildBulkBodyMock = buildBulkBody as jest.Mock;
+const transformHitToAlertMock = transformHitToAlert as jest.Mock;
 
 const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
 
@@ -118,7 +118,7 @@ const wrappedParams = {
 };
 
 describe('wrapSuppressedAlerts', () => {
-  buildBulkBodyMock.mockReturnValue({ 'mock-props': true });
+  transformHitToAlertMock.mockReturnValue({ 'mock-props': true });
 
   it('should wrap event with alert fields and correctly set suppression fields', () => {
     const expectedTimestamp = '2020-10-28T06:30:00.000Z';
@@ -137,10 +137,10 @@ describe('wrapSuppressedAlerts', () => {
       ...wrappedParams,
     });
 
-    expect(buildBulkBodyMock).toHaveBeenCalledWith(
-      'default',
-      wrappedParams.completeRule,
-      {
+    expect(transformHitToAlertMock).toHaveBeenCalledWith({
+      spaceId: 'default',
+      completeRule: wrappedParams.completeRule,
+      doc: {
         fields: {
           '@timestamp': [expectedTimestamp],
           'agent.name': ['agent-0'],
@@ -149,16 +149,17 @@ describe('wrapSuppressedAlerts', () => {
         _id: '1',
         _index: 'test*',
       },
-      'missingFields',
-      [],
-      true,
-      wrappedParams.buildReasonMessage,
-      ['test*'],
-      undefined,
+      mergeStrategy: 'missingFields',
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
+      buildReasonMessage: wrappedParams.buildReasonMessage,
+      indicesToQuery: ['test*'],
+      alertTimestampOverride: undefined,
       ruleExecutionLogger,
-      expect.any(String),
-      'public-url-mock'
-    );
+      alertUuid: expect.any(String),
+      publicBaseUrl: 'public-url-mock',
+    });
     expect(wrappedAlerts[0]._source).toEqual(
       expect.objectContaining({
         [ALERT_SUPPRESSION_TERMS]: [
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts
index 70fee20116fc4..87d1e64d8ece9 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts
@@ -23,7 +23,7 @@ import type {
   ThreatRuleParams,
 } from '../../rule_schema';
 import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
-import { buildBulkBody } from '../factories/utils/build_bulk_body';
+import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
 import { getSuppressionAlertFields, getSuppressionTerms } from './suppression_utils';
 import { generateId } from './utils';
 
@@ -77,20 +77,21 @@ export const wrapSuppressedAlerts = ({
 
     const instanceId = objectHash([suppressionTerms, completeRule.alertId, spaceId]);
 
-    const baseAlert: BaseFieldsLatest = buildBulkBody(
+    const baseAlert: BaseFieldsLatest = transformHitToAlert({
       spaceId,
       completeRule,
-      event,
+      doc: event,
       mergeStrategy,
-      [],
-      true,
+      ignoreFields: {},
+      ignoreFieldsRegexes: [],
+      applyOverrides: true,
       buildReasonMessage,
       indicesToQuery,
       alertTimestampOverride,
       ruleExecutionLogger,
-      id,
-      publicBaseUrl
-    );
+      alertUuid: id,
+      publicBaseUrl,
+    });
 
     return {
       _id: id,
diff --git a/x-pack/plugins/spaces/public/config.ts b/x-pack/plugins/spaces/public/config.ts
index dcd203eb696e3..3dd2d3bc89f92 100644
--- a/x-pack/plugins/spaces/public/config.ts
+++ b/x-pack/plugins/spaces/public/config.ts
@@ -9,4 +9,7 @@ export interface ConfigType {
   maxSpaces: number;
   allowFeatureVisibility: boolean;
   allowSolutionVisibility: boolean;
+  experimental: {
+    forceSolutionVisibility: boolean;
+  };
 }
diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts
index 5f97515171518..e5438c0cf5e9c 100644
--- a/x-pack/plugins/spaces/public/management/management_service.test.ts
+++ b/x-pack/plugins/spaces/public/management/management_service.test.ts
@@ -24,6 +24,9 @@ describe('ManagementService', () => {
     maxSpaces: 1000,
     allowFeatureVisibility: true,
     allowSolutionVisibility: true,
+    experimental: {
+      forceSolutionVisibility: false,
+    },
   };
 
   describe('#setup', () => {
diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
index 61619c499181c..d9852a82f8259 100644
--- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
+++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx
@@ -31,6 +31,9 @@ const config: ConfigType = {
   maxSpaces: 1000,
   allowFeatureVisibility: true,
   allowSolutionVisibility: true,
+  experimental: {
+    forceSolutionVisibility: false,
+  },
 };
 
 const eventTracker = new EventTracker({ reportEvent: jest.fn() });
diff --git a/x-pack/plugins/spaces/public/mocks.ts b/x-pack/plugins/spaces/public/mocks.ts
index 1499384f4ed72..8478ed010cd98 100644
--- a/x-pack/plugins/spaces/public/mocks.ts
+++ b/x-pack/plugins/spaces/public/mocks.ts
@@ -16,6 +16,7 @@ const createApiMock = (hasOnlyDefaultSpace: boolean): jest.Mocked<SpacesApi> =>
   getActiveSpace: jest.fn(),
   ui: createApiUiMock(),
   hasOnlyDefaultSpace,
+  isSolutionViewEnabled: true,
 });
 
 type SpacesApiUiMock = Omit<jest.Mocked<SpacesApiUi>, 'components'> & {
diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts
index 9afe7d27e9a04..33565748a99e3 100644
--- a/x-pack/plugins/spaces/public/plugin.test.ts
+++ b/x-pack/plugins/spaces/public/plugin.test.ts
@@ -5,6 +5,7 @@
  * 2.0.
  */
 
+import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
 import { coreMock } from '@kbn/core/public/mocks';
 import { homePluginMock } from '@kbn/home-plugin/public/mocks';
 import {
@@ -13,7 +14,6 @@ import {
 } from '@kbn/management-plugin/public/mocks';
 
 import { SpacesPlugin } from './plugin';
-// import { ConfigSchema } from './config';
 
 describe('Spaces plugin', () => {
   describe('#setup', () => {
@@ -209,27 +209,156 @@ describe('Spaces plugin', () => {
     });
   });
 
-  it('determines hasOnlyDefaultSpace correctly when maxSpaces=1', () => {
-    const coreSetup = coreMock.createSetup();
-    const coreStart = coreMock.createStart();
+  describe('hasOnlyDefaultSpace', () => {
+    it('determines hasOnlyDefaultSpace correctly when maxSpaces=1', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
 
-    const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1 }));
-    const spacesSetup = plugin.setup(coreSetup, {});
-    const spacesStart = plugin.start(coreStart);
+      const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1 }));
+      const spacesSetup = plugin.setup(coreSetup, {});
+      const spacesStart = plugin.start(coreStart);
 
-    expect(spacesSetup.hasOnlyDefaultSpace).toBe(true);
-    expect(spacesStart.hasOnlyDefaultSpace).toBe(true);
+      expect(spacesSetup.hasOnlyDefaultSpace).toBe(true);
+      expect(spacesStart.hasOnlyDefaultSpace).toBe(true);
+    });
+
+    it('determines hasOnlyDefaultSpace correctly when maxSpaces=1000', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
+
+      const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1000 }));
+      const spacesSetup = plugin.setup(coreSetup, {});
+      const spacesStart = plugin.start(coreStart);
+
+      expect(spacesSetup.hasOnlyDefaultSpace).toBe(false);
+      expect(spacesStart.hasOnlyDefaultSpace).toBe(false);
+    });
   });
 
-  it('determines hasOnlyDefaultSpace correctly when maxSpaces=1000', () => {
-    const coreSetup = coreMock.createSetup();
-    const coreStart = coreMock.createStart();
+  describe('isSolutionViewEnabled', () => {
+    it('when onCloud, not serverless and allowSolutionVisibility is "true"', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
+      const cloud = cloudMock.createSetup();
+      cloud.isCloudEnabled = true;
+
+      const plugin = new SpacesPlugin(
+        coreMock.createPluginInitializerContext(
+          { allowSolutionVisibility: true },
+          { buildFlavor: 'traditional' }
+        )
+      );
+      const spacesSetup = plugin.setup(coreSetup, { cloud });
+      const spacesStart = plugin.start(coreStart);
+
+      expect(spacesSetup.isSolutionViewEnabled).toBe(true);
+      expect(spacesStart.isSolutionViewEnabled).toBe(true);
+    });
 
-    const plugin = new SpacesPlugin(coreMock.createPluginInitializerContext({ maxSpaces: 1000 }));
-    const spacesSetup = plugin.setup(coreSetup, {});
-    const spacesStart = plugin.start(coreStart);
+    it('when not onCloud and allowSolutionVisibility is "true"', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
 
-    expect(spacesSetup.hasOnlyDefaultSpace).toBe(false);
-    expect(spacesStart.hasOnlyDefaultSpace).toBe(false);
+      {
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: true }, // it is true but we are not onCloud
+            { buildFlavor: 'traditional' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, {});
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(false); // so it should be false
+        expect(spacesStart.isSolutionViewEnabled).toBe(false);
+      }
+
+      {
+        // unless the forceSolutionVisibility flag is set
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: false, experimental: { forceSolutionVisibility: true } },
+            { buildFlavor: 'traditional' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, {}); // we are not onCloud but forceSolutionVisibility is true
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(true);
+        expect(spacesStart.isSolutionViewEnabled).toBe(true);
+      }
+    });
+
+    it('when onCloud, not serverless and allowSolutionVisibility is "false"', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
+      const cloud = cloudMock.createSetup();
+      cloud.isCloudEnabled = true;
+
+      {
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: false },
+            { buildFlavor: 'traditional' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, { cloud });
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(false);
+        expect(spacesStart.isSolutionViewEnabled).toBe(false);
+      }
+
+      {
+        // unless the forceSolutionVisibility flag is set
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: false, experimental: { forceSolutionVisibility: true } },
+            { buildFlavor: 'traditional' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, { cloud });
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(true);
+        expect(spacesStart.isSolutionViewEnabled).toBe(true);
+      }
+    });
+
+    it('when onCloud and serverless', () => {
+      const coreSetup = coreMock.createSetup();
+      const coreStart = coreMock.createStart();
+      const cloud = cloudMock.createSetup();
+      cloud.isCloudEnabled = true;
+
+      {
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: true },
+            { buildFlavor: 'serverless' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, { cloud });
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(false);
+        expect(spacesStart.isSolutionViewEnabled).toBe(false);
+      }
+
+      {
+        // unless the forceSolutionVisibility flag is set
+        const plugin = new SpacesPlugin(
+          coreMock.createPluginInitializerContext(
+            { allowSolutionVisibility: true, experimental: { forceSolutionVisibility: true } },
+            { buildFlavor: 'serverless' }
+          )
+        );
+        const spacesSetup = plugin.setup(coreSetup, { cloud });
+        const spacesStart = plugin.start(coreStart);
+
+        expect(spacesSetup.isSolutionViewEnabled).toBe(true);
+        expect(spacesStart.isSolutionViewEnabled).toBe(true);
+      }
+    });
   });
 });
diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx
index 31ba324b926f3..6d545cdb70e61 100644
--- a/x-pack/plugins/spaces/public/plugin.tsx
+++ b/x-pack/plugins/spaces/public/plugin.tsx
@@ -60,6 +60,14 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
 
   public setup(core: CoreSetup<PluginsStart, SpacesPluginStart>, plugins: PluginsSetup) {
     const hasOnlyDefaultSpace = this.config.maxSpaces === 1;
+    const onCloud = plugins.cloud !== undefined && plugins.cloud.isCloudEnabled;
+
+    // We only allow "solution" to be set on cloud environments, not on prem
+    // unless the forceSolutionVisibility flag is set
+    const allowSolutionVisibility =
+      (onCloud && !this.isServerless && this.config.allowSolutionVisibility) ||
+      Boolean(this.config.experimental?.forceSolutionVisibility);
+
     this.spacesManager = new SpacesManager(core.http);
     this.spacesApi = {
       ui: getUiApi({
@@ -69,15 +77,14 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
       getActiveSpace$: () => this.spacesManager.onActiveSpaceChange$,
       getActiveSpace: () => this.spacesManager.getActiveSpace(),
       hasOnlyDefaultSpace,
+      isSolutionViewEnabled: allowSolutionVisibility,
+    };
+
+    this.config = {
+      ...this.config,
+      allowSolutionVisibility,
     };
 
-    const onCloud = plugins.cloud !== undefined && plugins.cloud.isCloudEnabled;
-    if (!onCloud) {
-      this.config = {
-        ...this.config,
-        allowSolutionVisibility: false,
-      };
-    }
     registerSpacesEventTypes(core);
     this.eventTracker = new EventTracker(core.analytics);
 
@@ -133,7 +140,7 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
 
     registerAnalyticsContext(core.analytics, this.spacesManager.onActiveSpaceChange$);
 
-    return { hasOnlyDefaultSpace };
+    return { hasOnlyDefaultSpace, isSolutionViewEnabled: allowSolutionVisibility };
   }
 
   public start(core: CoreStart) {
diff --git a/x-pack/plugins/spaces/public/types.ts b/x-pack/plugins/spaces/public/types.ts
index 1ab253262c3c1..b5540df1fdd5a 100644
--- a/x-pack/plugins/spaces/public/types.ts
+++ b/x-pack/plugins/spaces/public/types.ts
@@ -60,4 +60,9 @@ export interface SpacesApi {
    * UI components and services to add spaces capabilities to an application.
    */
   ui: SpacesApiUi;
+
+  /**
+   * Indicates whether the solution view is enabled.
+   */
+  isSolutionViewEnabled: boolean;
 }
diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts
index 19d4a7cbe7f90..cc210e7e4e5a4 100644
--- a/x-pack/plugins/spaces/server/config.test.ts
+++ b/x-pack/plugins/spaces/server/config.test.ts
@@ -23,6 +23,9 @@ describe('config schema', () => {
         "allowFeatureVisibility": true,
         "allowSolutionVisibility": true,
         "enabled": true,
+        "experimental": Object {
+          "forceSolutionVisibility": false,
+        },
         "maxSpaces": 1000,
       }
     `);
@@ -32,6 +35,9 @@ describe('config schema', () => {
         "allowFeatureVisibility": true,
         "allowSolutionVisibility": true,
         "enabled": true,
+        "experimental": Object {
+          "forceSolutionVisibility": false,
+        },
         "maxSpaces": 1000,
       }
     `);
@@ -41,6 +47,9 @@ describe('config schema', () => {
         "allowFeatureVisibility": true,
         "allowSolutionVisibility": true,
         "enabled": true,
+        "experimental": Object {
+          "forceSolutionVisibility": false,
+        },
         "maxSpaces": 1000,
       }
     `);
diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts
index a7c9606e74543..ef6b300d43965 100644
--- a/x-pack/plugins/spaces/server/config.ts
+++ b/x-pack/plugins/spaces/server/config.ts
@@ -54,6 +54,13 @@ export const ConfigSchema = schema.object({
       defaultValue: true,
     }),
   }),
+  experimental: schema.maybe(
+    offeringBasedSchema({
+      traditional: schema.object({
+        forceSolutionVisibility: schema.boolean({ defaultValue: false }),
+      }),
+    })
+  ),
 });
 
 export function createConfig$(context: PluginInitializerContext) {
diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts
index a568f52c7c29a..297a99a525ec7 100644
--- a/x-pack/plugins/spaces/server/index.ts
+++ b/x-pack/plugins/spaces/server/index.ts
@@ -34,6 +34,9 @@ export const config: PluginConfigDescriptor = {
     maxSpaces: true,
     allowFeatureVisibility: true,
     allowSolutionVisibility: true,
+    experimental: {
+      forceSolutionVisibility: true,
+    },
   },
 };
 
diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts
index 8b91dcac35917..2f8fb2ec30842 100644
--- a/x-pack/plugins/spaces/server/plugin.ts
+++ b/x-pack/plugins/spaces/server/plugin.ts
@@ -130,7 +130,10 @@ export class SpacesPlugin
         ([config, onCloud]): ConfigType => ({
           ...config,
           // We only allow "solution" to be set on cloud environments, not on prem
-          allowSolutionVisibility: onCloud ? config.allowSolutionVisibility : false,
+          // unless the forceSolutionVisibility flag is set.
+          allowSolutionVisibility:
+            (onCloud && config.allowSolutionVisibility) ||
+            Boolean(config.experimental?.forceSolutionVisibility),
         })
       )
     );
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/__snapshots__/agent_policy.snap b/x-pack/test/fleet_api_integration/apis/agent_policy/__snapshots__/agent_policy.snap
index a4d255613133e..9112ad20ad860 100644
--- a/x-pack/test/fleet_api_integration/apis/agent_policy/__snapshots__/agent_policy.snap
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/__snapshots__/agent_policy.snap
@@ -30,7 +30,6 @@ Object {
       "enabled": true,
       "name": "system-1",
       "namespace": "default",
-      "output_id": null,
       "package": Object {
         "name": "system",
         "requires_root": true,
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/create_standalone_api_key.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/create_standalone_api_key.ts
new file mode 100644
index 0000000000000..dd227d53911c6
--- /dev/null
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/create_standalone_api_key.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
+import { skipIfNoDockerRegistry } from '../../helpers';
+import { SpaceTestApiClient } from '../space_awareness/api_helper';
+import { expectToRejectWithError } from '../space_awareness/helpers';
+import { setupTestUsers, testUsers } from '../test_users';
+
+export default function (providerContext: FtrProviderContext) {
+  const { getService } = providerContext;
+
+  const supertestWithoutAuth = getService('supertestWithoutAuth');
+  const supertest = getService('supertest');
+
+  describe('create standalone api key', function () {
+    skipIfNoDockerRegistry(providerContext);
+
+    before(async () => {
+      await setupTestUsers(getService('security'));
+    });
+
+    describe('POST /internal/fleet/create_standalone_agent_api_key', () => {
+      it('should work with a user with the correct permissions', async () => {
+        const apiClient = new SpaceTestApiClient(supertest);
+        const res = await apiClient.postStandaloneApiKey('test');
+        expect(res.item.name).to.eql('standalone_agent-test');
+      });
+      it('should return a 403 if the user cannot create the api key', async () => {
+        const apiClient = new SpaceTestApiClient(supertestWithoutAuth, {
+          username: testUsers.fleet_all_int_all.username,
+          password: testUsers.fleet_all_int_all.password,
+        });
+        await expectToRejectWithError(
+          () => apiClient.postStandaloneApiKey('tata'),
+          /403 Forbidden Missing permissions to create standalone API key/
+        );
+      });
+    });
+  });
+}
diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/index.js b/x-pack/test/fleet_api_integration/apis/agent_policy/index.js
index 66abbf8d6a5b3..9ae58b0089942 100644
--- a/x-pack/test/fleet_api_integration/apis/agent_policy/index.js
+++ b/x-pack/test/fleet_api_integration/apis/agent_policy/index.js
@@ -12,5 +12,6 @@ export default function loadTests({ loadTestFile }) {
     loadTestFile(require.resolve('./agent_policy_datastream_permissions'));
     loadTestFile(require.resolve('./privileges'));
     loadTestFile(require.resolve('./agent_policy_root_integrations'));
+    loadTestFile(require.resolve('./create_standalone_api_key'));
   });
 }
diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
index 159c40926f4e7..ea50aaaf53eb8 100644
--- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
+++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
@@ -198,7 +198,7 @@ export default function (providerContext: FtrProviderContext) {
           },
         })
         .expect(200);
-      expect(response.body.item.policy_id).to.eql(null);
+      expect(response.body.item.policy_id).to.eql(undefined);
       expect(response.body.item.policy_ids).to.eql([]);
     });
 
diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
index 1de90ae3dcfaa..c8af244ba11b9 100644
--- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
+++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts
@@ -554,4 +554,19 @@ export class SpaceTestApiClient {
 
     return res;
   }
+
+  async postStandaloneApiKey(name: string, spaceId?: string) {
+    const { body: res, statusCode } = await this.supertest
+      .post(`${this.getBaseUrl(spaceId)}/internal/fleet/create_standalone_agent_api_key`)
+      .auth(this.auth.username, this.auth.password)
+      .set('kbn-xsrf', 'xxxx')
+      .set('elastic-api-version', '1')
+      .send({ name });
+
+    if (statusCode !== 200) {
+      throw new Error(`${statusCode} ${res?.error} ${res.message}`);
+    }
+
+    return res;
+  }
 }
diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts
index ffd1ad09c7ccd..e0e67066b4777 100644
--- a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts
+++ b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts
@@ -59,6 +59,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
           messages,
           connectorId: 'does not exist',
           functions: [],
+          scope: 'all',
         })
         .expect(404);
     });
@@ -87,6 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
                 messages,
                 connectorId,
                 functions: [],
+                scope: 'all',
               })
               .pipe(passThrough);
 
@@ -144,6 +146,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
           messages,
           connectorId,
           functions: [],
+          scope: 'all',
         })
         .expect(200)
         .pipe(passThrough);
diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts
index 04d85d6dc282c..aaba5fbc7ba99 100644
--- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts
+++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts
@@ -84,6 +84,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
             connectorId,
             persist: true,
             screenContexts: params.screenContexts || [],
+            scope: 'all',
           })
           .then((response) => resolve(response))
           .catch((err) => reject(err));
@@ -136,6 +137,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
           connectorId,
           persist: false,
           screenContexts: [],
+          scope: 'all',
         })
         .pipe(passThrough);
 
@@ -402,6 +404,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
                 connectorId,
                 persist: true,
                 screenContexts: [],
+                scope: 'observability',
               },
             },
           })
@@ -444,6 +447,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
                 persist: true,
                 screenContexts: [],
                 conversationId,
+                scope: 'observability',
               },
             },
           })
diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/helpers.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/helpers.ts
index a32c22abcf7aa..552b779d2c0aa 100644
--- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/helpers.ts
+++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/helpers.ts
@@ -12,6 +12,7 @@ import {
   StreamingChatResponseEvent,
 } from '@kbn/observability-ai-assistant-plugin/common';
 import { Readable } from 'stream';
+import { AssistantScope } from '@kbn/observability-ai-assistant-plugin/common/types';
 import { CreateTest } from '../../../common/config';
 
 function decodeEvents(body: Readable | string) {
@@ -32,12 +33,14 @@ export async function invokeChatCompleteWithFunctionRequest({
   connectorId,
   observabilityAIAssistantAPIClient,
   functionCall,
+  scope,
 }: {
   connectorId: string;
   observabilityAIAssistantAPIClient: Awaited<
     ReturnType<CreateTest['services']['observabilityAIAssistantAPIClient']>
   >;
   functionCall: Message['message']['function_call'];
+  scope?: AssistantScope;
 }) {
   const { body } = await observabilityAIAssistantAPIClient
     .editorUser({
@@ -57,6 +60,7 @@ export async function invokeChatCompleteWithFunctionRequest({
           connectorId,
           persist: false,
           screenContexts: [],
+          scope: scope || 'observability',
         },
       },
     })
diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts
index 4cad8079dc0b2..bf2eef14db553 100644
--- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts
+++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts
@@ -249,6 +249,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
               connectorId,
               persist: true,
               screenContexts: [],
+              scope: 'observability',
             },
           },
         }).expect(200);
diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts
index 9af51a859befa..4a74e3938467e 100644
--- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts
+++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts
@@ -18,6 +18,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
 
 export default function ({ getService }: FtrProviderContext) {
   const supertest = getService('supertestWithoutAuth');
+  const esSupertest = getService('esSupertest');
   const es = getService('es');
   const esDeleteAllIndices = getService('esDeleteAllIndices');
   const config = getService('config');
@@ -93,9 +94,19 @@ export default function ({ getService }: FtrProviderContext) {
     });
   }
 
+  async function addESDebugLoggingSettings() {
+    const addLogging = {
+      persistent: {
+        'logger.org.elasticsearch.xpack.security.authc': 'debug',
+      },
+    };
+    await esSupertest.put('/_cluster/settings').send(addLogging).expect(200);
+  }
+
   describe('Session Idle cleanup', () => {
     beforeEach(async () => {
       await es.cluster.health({ index: '.kibana_security_session*', wait_for_status: 'green' });
+      await addESDebugLoggingSettings();
       await esDeleteAllIndices('.kibana_security_session*');
     });
 
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/alerts_compatibility.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/alerts_compatibility.ts
index f08893fe4ded4..9c8d50631cc53 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/alerts_compatibility.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/alerts_compatibility.ts
@@ -300,9 +300,11 @@ export default ({ getService }: FtrProviderContext) => {
             type: 'logs',
             dataset: 'elastic_agent.filebeat',
           },
-          'event.agent_id_status': 'verified',
-          'event.ingested': '2022-03-23T16:50:28.994Z',
-          'event.dataset': 'elastic_agent.filebeat',
+          event: {
+            agent_id_status: 'verified',
+            ingested: '2022-03-23T16:50:28.994Z',
+            dataset: 'elastic_agent.filebeat',
+          },
           'event.kind': 'signal',
           'kibana.alert.ancestors': [
             {
@@ -466,9 +468,11 @@ export default ({ getService }: FtrProviderContext) => {
             type: 'logs',
             dataset: 'elastic_agent.filebeat',
           },
-          'event.agent_id_status': 'verified',
-          'event.ingested': '2022-03-23T16:50:28.994Z',
-          'event.dataset': 'elastic_agent.filebeat',
+          event: {
+            agent_id_status: 'verified',
+            ingested: '2022-03-23T16:50:28.994Z',
+            dataset: 'elastic_agent.filebeat',
+          },
           'event.kind': 'signal',
           'kibana.alert.ancestors': [
             {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/ip_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/ip_array.ts
index cd67498c421c9..c0060912d6a52 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/ip_array.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/ips/basic_license_essentials_tier/ip_array.ts
@@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => {
   const log = getService('log');
   const es = getService('es');
 
-  describe('@serverless @serverlessQA @ess Rule exception operators for data type ip', () => {
+  describe('@serverless @serverlessQA @ess Rule exception operators for data type ip array', () => {
     before(async () => {
       await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/ip_as_array');
     });
@@ -62,10 +62,10 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
         expect(ips).to.eql([
-          [],
           ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'],
           ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'],
           ['127.0.0.8', '127.0.0.9', '127.0.0.10'],
+          undefined,
         ]);
       });
 
@@ -86,9 +86,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
         expect(ips).to.eql([
-          [],
           ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'],
           ['127.0.0.8', '127.0.0.9', '127.0.0.10'],
+          undefined,
         ]);
       });
 
@@ -116,7 +116,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
 
       it('should filter 3 ips if all 3 are set as exceptions', async () => {
@@ -151,7 +151,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips.flat(10)).to.eql([]);
+        expect(ips.flat(10)).to.eql([undefined]);
       });
 
       it('should filter a CIDR range of "127.0.0.1/30"', async () => {
@@ -171,9 +171,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
         expect(ips).to.eql([
-          [],
           ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'],
           ['127.0.0.8', '127.0.0.9', '127.0.0.10'],
+          undefined,
         ]);
       });
 
@@ -193,7 +193,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
     });
 
@@ -305,9 +305,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
         expect(ips).to.eql([
-          [],
           ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'],
           ['127.0.0.8', '127.0.0.9', '127.0.0.10'],
+          undefined,
         ]);
       });
 
@@ -327,7 +327,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
 
       it('should filter 3 ips if all 3 are set as exceptions', async () => {
@@ -346,7 +346,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips.flat(10)).to.eql([]);
+        expect(ips.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -408,7 +408,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips.flat(10)).to.eql([]);
+        expect(ips.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -458,9 +458,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
         expect(ips).to.eql([
-          [],
           ['127.0.0.5', null, '127.0.0.6', '127.0.0.7'],
           ['127.0.0.8', '127.0.0.9', '127.0.0.10'],
+          undefined,
         ]);
       });
 
@@ -484,7 +484,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
 
       it('will return 1 result if we have a list that includes all ips', async () => {
@@ -513,7 +513,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips.flat(10)).to.eql([]);
+        expect(ips.flat(10)).to.eql([undefined]);
       });
 
       it('will return 2 results if we have a list which contains the CIDR ranges of "127.0.0.1/32, 127.0.0.2/31, 127.0.0.4/30"', async () => {
@@ -551,7 +551,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
 
       it('will return 2 results if we have a list which contains the range syntax of "127.0.0.1-127.0.0.7', async () => {
@@ -582,7 +582,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const ips = alertsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
-        expect(ips).to.eql([[], ['127.0.0.8', '127.0.0.9', '127.0.0.10']]);
+        expect(ips).to.eql([['127.0.0.8', '127.0.0.9', '127.0.0.10'], undefined]);
       });
     });
 
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/keyword_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/keyword_array.ts
index c4d50860ea5c2..016d83e1587be 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/keyword_array.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/keyword/basic_license_essentials_tier/keyword_array.ts
@@ -33,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => {
   const log = getService('log');
   const es = getService('es');
 
-  describe('@serverles @serverlessQA @ess Rule exception operators for data type keyword', () => {
+  describe('@serverless @serverlessQA @ess Rule exception operators for data type keyword array', () => {
     before(async () => {
       await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/keyword_as_array');
     });
@@ -65,10 +65,10 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
           ['word one', 'word two', 'word three', 'word four'],
+          undefined,
         ]);
       });
 
@@ -89,9 +89,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -119,7 +119,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('should filter 3 keyword if all 3 are set as exceptions', async () => {
@@ -154,7 +154,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -241,9 +241,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -263,7 +263,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('should filter 3 keyword if all 3 are set as exceptions', async () => {
@@ -282,7 +282,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -344,7 +344,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -404,10 +404,10 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
           ['word one', 'word two', 'word three', 'word four'],
+          undefined,
         ]);
       });
 
@@ -442,9 +442,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -469,9 +469,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -495,7 +495,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('will return only the empty array for results if we have a list that includes all keyword', async () => {
@@ -524,7 +524,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -653,9 +653,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -675,7 +675,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits).to.eql([[], ['word five', null, 'word six', 'word seven']]);
+        expect(hits).to.eql([['word five', null, 'word six', 'word seven'], undefined]);
       });
 
       it('should filter 3 keyword if all 3 are set as exceptions', async () => {
@@ -694,7 +694,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.keyword).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/text_array.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/text_array.ts
index bfc528cf6ad6b..e0271b7ddb934 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/text_array.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/operators_data_types/text/basic_license_essentials_tier/text_array.ts
@@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => {
   const log = getService('log');
   const es = getService('es');
 
-  describe('@serverless @serverlessQA @ess Rule exception operators for data type text', () => {
+  describe('@serverless @serverlessQA @ess Rule exception operators for data type text array', () => {
     before(async () => {
       await esArchiver.load('x-pack/test/functional/es_archives/rule_exceptions/text_as_array');
     });
@@ -62,10 +62,10 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
           ['word one', 'word two', 'word three', 'word four'],
+          undefined,
         ]);
       });
 
@@ -86,9 +86,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -116,7 +116,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('should filter 3 text if all 3 are set as exceptions', async () => {
@@ -151,7 +151,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -238,9 +238,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -260,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('should filter 3 text if all 3 are set as exceptions', async () => {
@@ -279,7 +279,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -341,7 +341,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
@@ -401,10 +401,10 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
           ['word one', 'word two', 'word three', 'word four'],
+          undefined,
         ]);
       });
 
@@ -439,9 +439,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -466,9 +466,9 @@ export default ({ getService }: FtrProviderContext) => {
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
         expect(hits).to.eql([
-          [],
           ['word eight', 'word nine', 'word ten'],
           ['word five', null, 'word six', 'word seven'],
+          undefined,
         ]);
       });
 
@@ -492,7 +492,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 2, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits).to.eql([[], ['word eight', 'word nine', 'word ten']]);
+        expect(hits).to.eql([['word eight', 'word nine', 'word ten'], undefined]);
       });
 
       it('will return only the empty array for results if we have a list that includes all text', async () => {
@@ -521,7 +521,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForAlertsToBePresent(supertest, log, 1, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
         const hits = alertsOpen.hits.hits.map((hit) => hit._source?.text).sort();
-        expect(hits.flat(10)).to.eql([]);
+        expect(hits.flat(10)).to.eql([undefined]);
       });
     });
 
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts
index 608b7da833b6c..0b39a7287bacb 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts
@@ -25,7 +25,7 @@ import { flattenWithPrefix } from '@kbn/securitysolution-rules';
 import { Rule } from '@kbn/alerting-plugin/common';
 import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema';
 import moment from 'moment';
-import { orderBy } from 'lodash';
+import { get, orderBy } from 'lodash';
 import { v4 as uuidv4 } from 'uuid';
 
 import {
@@ -2830,7 +2830,7 @@ export default ({ getService }: FtrProviderContext) => {
         expect(previewAlerts[0]?._source?.destination).toEqual(
           expect.objectContaining({ domain: 'aaa.stage.11111111.hello' })
         );
-        expect(previewAlerts[0]?._source?.['event.dataset']).toEqual('network_traffic.tls');
+        expect(get(previewAlerts[0]?._source, 'event.dataset')).toEqual('network_traffic.tls');
       });
     });
   });
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
index 6a59d5244b88c..53b2843399c62 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts
@@ -158,12 +158,12 @@ export default ({ getService }: FtrProviderContext) => {
         ecs: {
           version: '1.0.0-beta2',
         },
-        ...flattenWithPrefix('event', {
+        event: {
           action: 'changed-audit-configuration',
           category: 'configuration',
           module: 'auditd',
-          kind: 'signal',
-        }),
+        },
+        'event.kind': 'signal',
         host: {
           architecture: 'x86_64',
           containerized: false,
@@ -300,12 +300,11 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
         },
-        ...flattenWithPrefix('event', {
+        event: {
           action: 'changed-audit-configuration',
           category: 'configuration',
           module: 'auditd',
-          kind: 'signal',
-        }),
+        },
         service: {
           type: 'auditd',
         },
@@ -427,12 +426,11 @@ export default ({ getService }: FtrProviderContext) => {
         },
         cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' },
         ecs: { version: '1.0.0-beta2' },
-        ...flattenWithPrefix('event', {
+        event: {
           action: 'changed-promiscuous-mode-on-device',
           category: 'anomoly',
           module: 'auditd',
-          kind: 'signal',
-        }),
+        },
         host: {
           architecture: 'x86_64',
           containerized: false,
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts
index b688f53e288b1..0dd5a93bb9e60 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match.ts
@@ -282,12 +282,12 @@ export default ({ getService }: FtrProviderContext) => {
         ecs: {
           version: '1.0.0-beta2',
         },
-        ...flattenWithPrefix('event', {
+        event: {
           action: 'error',
           category: 'user-login',
           module: 'auditd',
-          kind: 'signal',
-        }),
+        },
+        'event.kind': 'signal',
         host: {
           architecture: 'x86_64',
           containerized: false,
@@ -464,12 +464,12 @@ export default ({ getService }: FtrProviderContext) => {
         ecs: {
           version: '1.0.0-beta2',
         },
-        ...flattenWithPrefix('event', {
+        event: {
           action: 'error',
           category: 'user-login',
           module: 'auditd',
-          kind: 'signal',
-        }),
+        },
+        'event.kind': 'signal',
         host: {
           architecture: 'x86_64',
           containerized: false,
@@ -749,7 +749,6 @@ export default ({ getService }: FtrProviderContext) => {
           {
             enrichments: [
               {
-                feed: {},
                 indicator: {
                   description: "domain should match the auditbeat hosts' data's source.ip",
                   domain: '159.89.119.67',
@@ -774,7 +773,6 @@ export default ({ getService }: FtrProviderContext) => {
           {
             enrichments: [
               {
-                feed: {},
                 indicator: {
                   description: "domain should match the auditbeat hosts' data's source.ip",
                   domain: '159.89.119.67',
@@ -826,7 +824,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threat.enrichments, [
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -844,7 +841,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -901,7 +897,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threat.enrichments, [
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -924,7 +919,6 @@ export default ({ getService }: FtrProviderContext) => {
           // threat.indicator.matched data). That's the case with the
           // first and third indicators matched, here.
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -943,7 +937,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1003,7 +996,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threats[0].enrichments, [
           {
-            feed: {},
             indicator: {
               description: "domain should match the auditbeat hosts' data's source.ip",
               domain: '159.89.119.67',
@@ -1024,7 +1016,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1042,7 +1033,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1063,7 +1053,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threats[1].enrichments, [
           {
-            feed: {},
             indicator: {
               description: "domain should match the auditbeat hosts' data's source.ip",
               domain: '159.89.119.67',
@@ -1172,7 +1161,6 @@ export default ({ getService }: FtrProviderContext) => {
           {
             enrichments: [
               {
-                feed: {},
                 indicator: {
                   description: "domain should match the auditbeat hosts' data's source.ip",
                   domain: '159.89.119.67',
@@ -1197,7 +1185,6 @@ export default ({ getService }: FtrProviderContext) => {
           {
             enrichments: [
               {
-                feed: {},
                 indicator: {
                   description: "domain should match the auditbeat hosts' data's source.ip",
                   domain: '159.89.119.67',
@@ -1274,7 +1261,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threat.enrichments, [
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1292,7 +1278,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1390,7 +1375,6 @@ export default ({ getService }: FtrProviderContext) => {
         }>;
         assertContains(threatTerm.enrichments, [
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1413,7 +1397,6 @@ export default ({ getService }: FtrProviderContext) => {
           // threat.indicator.matched data). That's the case with the
           // first and third indicators matched, here.
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1432,7 +1415,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1505,7 +1487,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threats[0].enrichments, [
           {
-            feed: {},
             indicator: {
               description: "domain should match the auditbeat hosts' data's source.ip",
               domain: '159.89.119.67',
@@ -1526,7 +1507,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1544,7 +1524,6 @@ export default ({ getService }: FtrProviderContext) => {
             },
           },
           {
-            feed: {},
             indicator: {
               description: 'this should match auditbeat/hosts on both port and ip',
               first_seen: '2021-01-26T11:06:03.000Z',
@@ -1565,7 +1544,6 @@ export default ({ getService }: FtrProviderContext) => {
 
         assertContains(threats[1].enrichments, [
           {
-            feed: {},
             indicator: {
               description: "domain should match the auditbeat hosts' data's source.ip",
               domain: '159.89.119.67',
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts
index dfb5b3492b17a..e274366e54aa7 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts
@@ -160,14 +160,17 @@ export default ({ getService }: FtrProviderContext) => {
           name: expect.stringMatching(/(root|bob)/),
           terminal: 'pts/0',
         },
-        'event.action': 'user_login',
-        'event.category': 'authentication',
-        'event.dataset': 'login',
+        event: {
+          action: 'user_login',
+          category: 'authentication',
+          dataset: 'login',
+
+          module: 'system',
+          origin: '/var/log/wtmp',
+          outcome: 'success',
+          type: 'authentication_success',
+        },
         'event.kind': 'signal',
-        'event.module': 'system',
-        'event.origin': '/var/log/wtmp',
-        'event.outcome': 'success',
-        'event.type': 'authentication_success',
         'kibana.alert.original_time': '2019-02-19T20:42:08.230Z',
         'kibana.alert.ancestors': [
           {
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts
index 15ea0c02b6bc2..a4c59313389e3 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/non_ecs_fields.ts
@@ -213,8 +213,7 @@ export default ({ getService }: FtrProviderContext) => {
 
       expect(errors).toEqual([]);
 
-      // event properties getting flattened
-      expect(alertSource).toHaveProperty(['event.created'], validDates);
+      expect(alertSource).toHaveProperty(['event', 'created'], validDates);
     });
 
     // source threat.enrichments is keyword, ECS mapping for threat.enrichments is nested
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts
index e97b9436612aa..79b118b3b3d94 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/const_keyword.ts
@@ -6,6 +6,7 @@
  */
 
 import expect from '@kbn/expect';
+import { get } from 'lodash';
 import {
   EqlRuleCreateProps,
   ThresholdRuleCreateProps,
@@ -73,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 4, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
@@ -107,7 +108,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 4, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts
index 5ef047ecd2de8..1be8274079663 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts
@@ -6,6 +6,7 @@
  */
 
 import expect from '@kbn/expect';
+import { get } from 'lodash';
 
 import {
   EqlRuleCreateProps,
@@ -60,7 +61,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 4, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
@@ -81,7 +82,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 4, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts
index c0b42709847be..9d63a84a1cbe5 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword_mixed_with_const.ts
@@ -6,6 +6,7 @@
  */
 
 import expect from '@kbn/expect';
+import { get } from 'lodash';
 import {
   EqlRuleCreateProps,
   ThresholdRuleCreateProps,
@@ -75,7 +76,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 8, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
@@ -113,7 +114,7 @@ export default ({ getService }: FtrProviderContext) => {
         await waitForRuleSuccess({ supertest, log, id });
         await waitForAlertsToBePresent(supertest, log, 8, [id]);
         const alertsOpen = await getAlertsById(supertest, log, id);
-        const hits = alertsOpen.hits.hits.map((hit) => hit._source?.['event.dataset']).sort();
+        const hits = alertsOpen.hits.hits.map((hit) => get(hit, '_source.event.dataset')).sort();
         expect(hits).to.eql([
           'dataset_name_1',
           'dataset_name_1',
diff --git a/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts b/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
index 313dd001c4f4a..91b536c79c5e8 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
@@ -20,7 +20,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
   const testSubjects = getService('testSubjects');
   const synthtrace = getService('svlLogsSynthtraceClient');
 
-  describe('Onboarding Firehose Quickstart Flow', () => {
+  // Failing: See https://github.com/elastic/kibana/issues/193294
+  describe.skip('Onboarding Firehose Quickstart Flow', () => {
     before(async () => {
       await PageObjects.svlCommonPage.loginAsAdmin(); // Onboarding requires admin role
       await PageObjects.common.navigateToUrlWithBrowserHistory(
@@ -38,6 +39,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
     });
 
     beforeEach(async () => {
+      await (await testSubjects.find('createCloudFormationOptionAWSCLI')).click();
       await testSubjects.existOrFail('observabilityOnboardingFirehoseCreateStackCommand');
     });
 
@@ -51,10 +53,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
     it('starts to monitor for incoming data after user leaves the page', async () => {
       await browser.execute(`window.dispatchEvent(new Event("blur"))`);
 
-      await testSubjects.isDisplayed('observabilityOnboardingAWSServiceList');
+      await testSubjects.isDisplayed('observabilityOnboardingFirehoseProgressCallout');
     });
 
-    it('highlights an AWS service when data is detected', async () => {
+    it('shows an AWS service when data is detected', async () => {
       const DATASET = 'aws.vpcflow';
       const AWS_SERVICE_ID = 'vpc-flow';
       await testSubjects.clickWhenNotDisabled('observabilityOnboardingCopyToClipboardButton');
@@ -79,11 +81,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
           })
       );
 
-      // Checking that an AWS service item is enabled after data is detected
-      await testSubjects
-        .find(`observabilityOnboardingAWSService-${AWS_SERVICE_ID}`)
-        .then((el) => el.findByTagName('button'))
-        .then((el) => el.isEnabled());
+      // Checking that an AWS service item is visible after data is detected
+      await testSubjects.isDisplayed(`observabilityOnboardingAWSService-${AWS_SERVICE_ID}`);
     });
   });
 }
diff --git a/yarn.lock b/yarn.lock
index a09aca8eff5ae..13852c94782c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -30422,6 +30422,18 @@ tinyqueue@^2.0.3:
   resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
   integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
 
+tldts-core@^6.1.46:
+  version "6.1.46"
+  resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.46.tgz#062d64981ee83f934f875c178a97e42bcd13bef7"
+  integrity sha512-zA3ai/j4aFcmbqTvTONkSBuWs0Q4X4tJxa0gV9sp6kDbq5dAhQDSg0WUkReEm0fBAKAGNj+wPKCCsR8MYOYmwA==
+
+tldts@^6.1.32:
+  version "6.1.46"
+  resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.46.tgz#0c3c4157efe732caeddd06eee6da891b26bd8a75"
+  integrity sha512-fw81lXV2CijkNrZAZvee7wegs+EOlTyIuVl/z4q6OUzZHQ1jGL2xQzKXq9geYf/1tzo9LZQLrkcko2m8HLh+rg==
+  dependencies:
+    tldts-core "^6.1.46"
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -30546,6 +30558,13 @@ tough-cookie@^4.1.2, tough-cookie@^4.1.3, tough-cookie@^4.1.4:
     universalify "^0.2.0"
     url-parse "^1.5.3"
 
+tough-cookie@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af"
+  integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==
+  dependencies:
+    tldts "^6.1.32"
+
 tr46@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"