From 176cf1140f398701832e3f05d5777c00f170d802 Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Thu, 16 Apr 2026 14:08:37 +0300 Subject: [PATCH 1/7] init on install --- .../public/hooks/useInstallEntityStoreV2.tsx | 3 +- .../server/routes/apis/install/index.ts | 3 +- .../scout/api/tests/install_update.spec.ts | 28 +++++++++++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx index 60ff729c3253e..ec6a744afe434 100644 --- a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx +++ b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx @@ -72,9 +72,8 @@ export const useInstallEntityStoreV2 = (services: Services) => { await services.http.post(initEntityMaintainersRequest); return; } - // Entity store not installed → install entity store, then init entity maintainers. + // Entity store not installed → install entity store (init entity maintainers is already done by the install API). await services.http.post(installAllEntitiesRequest); - await services.http.post(initEntityMaintainersRequest); } catch (e) { services.logger.error('Failed to initialize Entity Store V2'); services.logger.error(e); diff --git a/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts b/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts index ed4b648013511..c8f21aa025c26 100644 --- a/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts +++ b/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts @@ -35,7 +35,7 @@ export function registerInstall(router: EntityStorePluginRouter) { }, wrapMiddlewares(async (ctx, req, res): Promise => { const entityStoreCtx = await ctx.entityStore; - const { logger, assetManagerClient: assetManager } = entityStoreCtx; + const { logger, assetManagerClient: assetManager, entityMaintainersClient } = entityStoreCtx; const { entityTypes, logExtraction, historySnapshot } = req.body; logger.debug('Install api called'); @@ -60,6 +60,7 @@ export function registerInstall(router: EntityStorePluginRouter) { } await assetManager.init(req, toInstall, logExtraction, historySnapshot); + await entityMaintainersClient.init(req); return res.created({ body: { ok: true } }); }) diff --git a/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts b/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts index 5d39a042f24ce..7d8415debeda6 100644 --- a/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts +++ b/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts @@ -7,11 +7,17 @@ import { apiTest } from '@kbn/scout-security'; import { expect } from '@kbn/scout-security/api'; -import { PUBLIC_HEADERS, ENTITY_STORE_ROUTES, ENTITY_STORE_TAGS } from '../fixtures/constants'; -import { FF_ENABLE_ENTITY_STORE_V2 } from '../../../../common'; +import { + PUBLIC_HEADERS, + INTERNAL_HEADERS, + ENTITY_STORE_ROUTES, + ENTITY_STORE_TAGS, +} from '../fixtures/constants'; +import { FF_ENABLE_ENTITY_STORE_V2, type GetEntityMaintainersResponse } from '../../../../common'; apiTest.describe('Entity Store install / update API tests', { tag: ENTITY_STORE_TAGS }, () => { let defaultHeaders: Record; + let internalHeaders: Record; apiTest.beforeAll(async ({ samlAuth }) => { const credentials = await samlAuth.asInteractiveUser('admin'); @@ -19,9 +25,13 @@ apiTest.describe('Entity Store install / update API tests', { tag: ENTITY_STORE_ ...credentials.cookieHeader, ...PUBLIC_HEADERS, }; + internalHeaders = { + ...credentials.cookieHeader, + ...INTERNAL_HEADERS, + }; }); - apiTest( + apiTest.only( 'Should install the entity store happy path with feature flag enabled', async ({ apiClient, kbnClient }) => { await kbnClient.uiSettings.update({ @@ -35,6 +45,18 @@ apiTest.describe('Entity Store install / update API tests', { tag: ENTITY_STORE_ }); expect(install.statusCode).toBe(201); + const maintainersResponse = await apiClient.get( + ENTITY_STORE_ROUTES.internal.ENTITY_MAINTAINERS_GET, + { + headers: internalHeaders, + responseType: 'json', + } + ); + expect(maintainersResponse.statusCode).toBe(200); + const { maintainers } = maintainersResponse.body as GetEntityMaintainersResponse; + expect(maintainers.length).toBeGreaterThan(0); + expect(maintainers.every((m) => m.taskStatus === 'started')).toBe(true); + const uninstall = await apiClient.post(ENTITY_STORE_ROUTES.public.UNINSTALL, { headers: defaultHeaders, responseType: 'json', From 52beff2f221af172c1993f904c3c8934e68a4b05 Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Thu, 16 Apr 2026 14:15:58 +0300 Subject: [PATCH 2/7] remove only --- .../entity_store/test/scout/api/tests/install_update.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts b/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts index 7d8415debeda6..ee6ca66671874 100644 --- a/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts +++ b/x-pack/solutions/security/plugins/entity_store/test/scout/api/tests/install_update.spec.ts @@ -31,7 +31,7 @@ apiTest.describe('Entity Store install / update API tests', { tag: ENTITY_STORE_ }; }); - apiTest.only( + apiTest( 'Should install the entity store happy path with feature flag enabled', async ({ apiClient, kbnClient }) => { await kbnClient.uiSettings.update({ From fb6636c7104091b99d3ab8c76bf54b8eb47ecc83 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:48:00 +0000 Subject: [PATCH 3/7] Changes from node scripts/eslint_all_files --no-cache --fix --- .../entity_store/server/routes/apis/install/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts b/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts index c8f21aa025c26..8c4aa57873c00 100644 --- a/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts +++ b/x-pack/solutions/security/plugins/entity_store/server/routes/apis/install/index.ts @@ -35,7 +35,11 @@ export function registerInstall(router: EntityStorePluginRouter) { }, wrapMiddlewares(async (ctx, req, res): Promise => { const entityStoreCtx = await ctx.entityStore; - const { logger, assetManagerClient: assetManager, entityMaintainersClient } = entityStoreCtx; + const { + logger, + assetManagerClient: assetManager, + entityMaintainersClient, + } = entityStoreCtx; const { entityTypes, logExtraction, historySnapshot } = req.body; logger.debug('Install api called'); From a624f4413b0223b3040f66a4b6d32b73af99a0b3 Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Thu, 16 Apr 2026 14:48:00 +0300 Subject: [PATCH 4/7] fix non default space init maintainers on cv2 installed --- .../hooks/useInstallEntityStoreV2.test.tsx | 53 +++++++++++++++---- .../public/hooks/useInstallEntityStoreV2.tsx | 10 ++-- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.test.tsx b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.test.tsx index 9db669033e1e1..ed9ec39f1bf9f 100644 --- a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.test.tsx +++ b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.test.tsx @@ -53,7 +53,9 @@ describe('useInstallEntityStoreV2', () => { const mockServices = createMockServices(); mockServices.uiSettings.get.mockReturnValue(true); mockServices.spaces.getActiveSpace.mockResolvedValue({ id: 'custom-space' }); - mockServices.http.get.mockResolvedValueOnce({ status: EntityStoreStatus.enum.not_installed }); + mockServices.http.get + .mockResolvedValueOnce({ status: EntityStoreStatus.enum.not_installed }) + .mockResolvedValueOnce({ status: EntityStoreStatus.enum.not_installed }); renderHook(() => useInstallEntityStoreV2(asServices(mockServices))); @@ -63,16 +65,20 @@ describe('useInstallEntityStoreV2', () => { }); }); - expect(mockServices.http.get).toHaveBeenCalledTimes(1); + expect(mockServices.http.get).toHaveBeenNthCalledWith(1, { + path: ENTITY_STORE_ROUTES.public.STATUS, + query: { include_components: false }, + }); + expect(mockServices.http.get).toHaveBeenCalledTimes(2); expect(mockServices.http.post).not.toHaveBeenCalled(); }); - it('should proceed when not in default space and v1 is installed', async () => { + it('should install when not in default space, v1 is installed, and v2 is not installed', async () => { const mockServices = createMockServices(); mockServices.uiSettings.get.mockReturnValue(true); mockServices.spaces.getActiveSpace.mockResolvedValue({ id: 'custom-space' }); mockServices.http.get - .mockResolvedValueOnce({ status: EntityStoreStatus.enum.running }) + .mockResolvedValueOnce({ status: EntityStoreStatus.enum.not_installed }) .mockResolvedValueOnce({ status: EntityStoreStatus.enum.running }); mockServices.http.post.mockResolvedValueOnce({}); @@ -83,12 +89,37 @@ describe('useInstallEntityStoreV2', () => { }); expect(mockServices.http.get).toHaveBeenNthCalledWith(1, { - path: '/api/entity_store/status', - }); - expect(mockServices.http.get).toHaveBeenNthCalledWith(2, { path: ENTITY_STORE_ROUTES.public.STATUS, query: { include_components: false }, }); + expect(mockServices.http.get).toHaveBeenNthCalledWith(2, { + path: '/api/entity_store/status', + }); + expect(mockServices.http.post).toHaveBeenCalledWith({ + path: ENTITY_STORE_ROUTES.public.INSTALL, + body: JSON.stringify({}), + }); + }); + + it('should init entity maintainers when not in default space, v1 is not installed, and v2 is running', async () => { + const mockServices = createMockServices(); + mockServices.uiSettings.get.mockReturnValue(true); + mockServices.spaces.getActiveSpace.mockResolvedValue({ id: 'custom-space' }); + mockServices.http.get.mockResolvedValueOnce({ status: EntityStoreStatus.enum.running }); + mockServices.http.post.mockResolvedValue({}); + + renderHook(() => useInstallEntityStoreV2(asServices(mockServices))); + + await waitFor(() => { + expect(mockServices.http.post).toHaveBeenCalledTimes(1); + }); + + expect(mockServices.http.get).toHaveBeenCalledTimes(1); + expect(mockServices.http.post).toHaveBeenCalledWith({ + path: ENTITY_STORE_ROUTES.internal.ENTITY_MAINTAINERS_INIT, + body: JSON.stringify({}), + query: { apiVersion: '2' }, + }); }); it("should not install when entity store status is 'running'", async () => { @@ -119,7 +150,7 @@ describe('useInstallEntityStoreV2', () => { }); }); - it('when entity store is not installed and space is default, installs entity store then inits entity maintainers', async () => { + it('when entity store is not installed and space is default, installs entity store (install API inits maintainers)', async () => { const mockServices = createMockServices(); mockServices.uiSettings.get.mockReturnValue(true); mockServices.spaces.getActiveSpace.mockResolvedValue({ id: 'default' }); @@ -129,18 +160,18 @@ describe('useInstallEntityStoreV2', () => { renderHook(() => useInstallEntityStoreV2(asServices(mockServices))); await waitFor(() => { - expect(mockServices.http.post).toHaveBeenCalledTimes(2); + expect(mockServices.http.post).toHaveBeenCalledTimes(1); }); expect(mockServices.http.get).toHaveBeenCalledWith({ path: ENTITY_STORE_ROUTES.public.STATUS, query: { include_components: false }, }); - expect(mockServices.http.post).toHaveBeenNthCalledWith(1, { + expect(mockServices.http.post).toHaveBeenCalledWith({ path: ENTITY_STORE_ROUTES.public.INSTALL, body: JSON.stringify({}), }); - expect(mockServices.http.post).toHaveBeenNthCalledWith(2, { + expect(mockServices.http.post).not.toHaveBeenCalledWith({ path: ENTITY_STORE_ROUTES.internal.ENTITY_MAINTAINERS_INIT, body: JSON.stringify({}), query: { apiVersion: '2' }, diff --git a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx index ec6a744afe434..c1a71b94124a2 100644 --- a/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx +++ b/x-pack/solutions/security/plugins/entity_store/public/hooks/useInstallEntityStoreV2.tsx @@ -60,13 +60,17 @@ export const useInstallEntityStoreV2 = (services: Services) => { if (!isEntityStoreV2Enabled) return; const space = await services.spaces.getActiveSpace(); - // Install v2 and remove v1 in default namespace AND every namespace where v1 is currently installed - if (space.id !== 'default' && !(await isEntityStoreV1Installed(services.http))) return; - const statusResponse = await services.http.get<{ status: EntityStoreStatus }>( getStatusRequest ); const isEntityStoreV2Installed = isEntityStoreInstalled(statusResponse.status); + // In non-default spaces, only auto-install v2 where v1 existed. If v2 is already there, + // skip the v1 check and still run (e.g. init entity maintainers for this space). + if (space.id !== 'default' && !isEntityStoreV2Installed) { + if (!(await isEntityStoreV1Installed(services.http))) { + return; + } + } // Entity store already installed → init entity maintainers only. if (isEntityStoreV2Installed) { await services.http.post(initEntityMaintainersRequest); From be18d039ff4b620f3c373c0734e78727c4590b64 Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Sun, 19 Apr 2026 10:19:26 +0300 Subject: [PATCH 5/7] stop resolution maintainer --- .../resolution_csv_upload.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/entity_resolution/trial_license_complete_tier/resolution_csv_upload.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/entity_resolution/trial_license_complete_tier/resolution_csv_upload.ts index 32a6c41c68864..4d872ecece987 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/entity_resolution/trial_license_complete_tier/resolution_csv_upload.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/entity_resolution/trial_license_complete_tier/resolution_csv_upload.ts @@ -12,6 +12,10 @@ import { ENTITY_STORE_ROUTES, API_VERSIONS } from '@kbn/entity-store/common'; import { ENTITY_RESOLUTION_CSV_UPLOAD_URL } from '@kbn/security-solution-plugin/common/entity_analytics/entity_store/constants'; import type { FtrProviderContext } from '../../../../ftr_provider_context'; import { EntityStoreUtils } from '../../utils'; +import { entityMaintainerRouteHelpersFactory } from '../../utils/entity_maintainers'; + +/** Same value as `MAINTAINER_ID` in entity_store `server/maintainers/automated_resolution`. */ +const AUTOMATED_RESOLUTION_MAINTAINER_ID = 'automated-resolution'; const TEST_PREFIX = 'csv-test:'; @@ -116,11 +120,14 @@ export default ({ getService }: FtrProviderContext) => { describe('@ess @serverless @skipInServerlessMKI Entity Resolution CSV Upload', () => { before(async () => { - // Use enableEntityStoreV2 (without maintainer init) to prevent the - // automated resolution maintainer from racing with CSV upload tests. + // Use enableEntityStoreV2 and explicitly stop + // the automated-resolution maintainer so it cannot race with CSV upload tests. // The maintainer would link entities sharing the same user.email, // interfering with the test's own resolution assertions. await entityStoreUtils.enableEntityStoreV2(); + await entityMaintainerRouteHelpersFactory(supertest).stopMaintainer( + AUTOMATED_RESOLUTION_MAINTAINER_ID + ); await cleanEntities(); await seedEntities(); await waitForEntities(); From f3c459f7806b61b9ec175184b4096011888bc2fb Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Sun, 19 Apr 2026 13:56:13 +0300 Subject: [PATCH 6/7] stop risk-score for testing --- .../entity_analytics/utils/risk_score_maintainer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_score_maintainer.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_score_maintainer.ts index 6f1fa42ab2b7f..4138afcffccac 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_score_maintainer.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_score_maintainer.ts @@ -37,7 +37,7 @@ interface RetryServiceLike { type MaintainerRoutesLike = Pick< ReturnType, - 'getMaintainers' | 'runMaintainer' | 'runMaintainerSync' | 'startMaintainer' + 'getMaintainers' | 'runMaintainer' | 'runMaintainerSync' | 'startMaintainer' | 'stopMaintainer' >; interface EntityStoreUtilsLike { @@ -364,6 +364,8 @@ export const riskScoreMaintainerScenarioFactory = ({ dataViewPattern, maintainerAutoStart: false, }); + await routes.stopMaintainer('risk-score'); + if (runMode === 'sync') { await routes.runMaintainerSync('risk-score'); return; From 28c66c6f1ceb44f9f6da05091a6ce8d368924c82 Mon Sep 17 00:00:00 2001 From: chennn1990 Date: Sun, 19 Apr 2026 14:26:04 +0300 Subject: [PATCH 7/7] add stopMaintainer abillity --- .../trial_license_complete_tier/preview_api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/risk_score_maintainer/trial_license_complete_tier/preview_api.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/risk_score_maintainer/trial_license_complete_tier/preview_api.ts index 5fadc1d3a5c40..b8e9beb1aa183 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/risk_score_maintainer/trial_license_complete_tier/preview_api.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/entity_analytics/risk_score_maintainer/trial_license_complete_tier/preview_api.ts @@ -57,6 +57,9 @@ export default ({ getService }: FtrProviderContext): void => { startMaintainer: async () => { throw new Error('Preview API tests do not use maintainer route startMaintainer'); }, + stopMaintainer: async () => { + throw new Error('Preview API tests do not use maintainer route stopMaintainer'); + }, }, }); const setEntityStoreV2Setting = async (enabled: boolean) => {