x-pack/solutions/security/plugins/discoveries/event_logging_implementation.md
This document describes the implementation of event logging for Attack Discovery workflow steps, enabling workflow-generated discoveries to be tracked and displayed in the Attack Discovery UI alongside discoveries generated via the public API.
January 13, 2026
When users invoke the public Attack Discovery API (POST /api/attack_discovery/_generate), it logs events to the Elasticsearch event log. These events are tied together by kibana.alert.rule.execution.uuid and queried by the GET /api/attack_discovery/generations endpoint to display generation status in the UI.
However, workflow-based generation steps did not emit these events, meaning:
Implemented event logging in the @kbn/discoveries orchestration helpers (invoked by executeGenerationWorkflow on the _generate route, the scheduled workflowExecutor, and the security.attack-discovery.run step) to emit the same event structure as the public API, and moved the shared event logging utilities into the @kbn/discoveries package to eliminate code duplication. The security.attack-discovery.generate step itself does not write these events — they are written by the orchestration layer that wraps the step.
elastic_assistant plugin
├── Event logging utilities (duplicated)
└── Public API with event logging
discoveries plugin
└── Workflow steps (no event logging)
@kbn/discoveries package
├── impl/attack_discovery/persistence/event_logging/ (shared)
│ ├── constants.ts (ATTACK_DISCOVERY_EVENT_ACTIONS + action constants)
│ ├── write_attack_discovery_event.ts
│ └── index.ts
└── impl/lib/persistence/
└── get_duration_nanoseconds.ts
elastic_assistant plugin
├── Public API (uses shared utilities)
└── Re-export shims that forward to @kbn/discoveries
discoveries plugin
├── Orchestration helpers (impl/attack_discovery/generation/**) write the events
└── Imports from @kbn/discoveries
Files Created:
x-pack/solutions/security/packages/kbn-discoveries/impl/attack_discovery/persistence/event_logging/constants.tsx-pack/solutions/security/packages/kbn-discoveries/impl/attack_discovery/persistence/event_logging/write_attack_discovery_event.tsx-pack/solutions/security/packages/kbn-discoveries/impl/attack_discovery/persistence/event_logging/write_attack_discovery_event.test.tsx-pack/solutions/security/packages/kbn-discoveries/impl/attack_discovery/persistence/event_logging/index.tsx-pack/solutions/security/packages/kbn-discoveries/impl/lib/persistence/get_duration_nanoseconds.tsx-pack/solutions/security/packages/kbn-discoveries/impl/lib/persistence/get_duration_nanoseconds.test.tsKey Design Decision:
AttackDiscoveryDataClient dependency to EventLogRefresher interface to avoid circular dependenciesFiles Modified:
x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/public/post/post_attack_discovery_generate.tsx-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/public/post/post_attack_discovery_generations_dismiss.tsFiles Retained as re-export shims (they were not deleted — they now forward to @kbn/discoveries so existing importers keep working):
x-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/public/post/get_duration_nanoseconds/index.tsx-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/public/post/helpers/write_attack_discovery_event/index.ts (export { writeAttackDiscoveryEvent } from '@kbn/discoveries';)Files Modified:
x-pack/solutions/security/plugins/discoveries/kibana.jsonc (added eventLog dependency)x-pack/solutions/security/plugins/discoveries/server/types.ts (added EventLogServiceSetup)x-pack/solutions/security/plugins/discoveries/server/plugin.ts (create eventLogger)x-pack/solutions/security/plugins/discoveries/server/workflows/register_workflow_steps.ts (pass getEventLogger/getEventLogIndex into the steps)Where the events are written: the orchestration layer in @kbn/discoveries (impl/attack_discovery/generation/**), not the generate step. executeGenerationWorkflow and the workflow-invocation helpers (write_generation_started_event/, write_alert_retrieval_started_event/, write_alert_retrieval_succeeded_event/, write_alert_retrieval_failed_event/, invoke_generation_workflow.ts, invoke_validation_workflow.ts) call writeAttackDiscoveryEvent.
Event Logging Flow:
At Start:
executionUuid supplied by the orchestrator (the alerting framework's execution ID on the scheduled path, so events are queryable by the same ID as the rule execution log)generation-started (and per-phase *-started) eventsOn Success:
getDurationNanosecondsgeneration-succeeded (and per-phase *-succeeded) events with metrics:
alertsContextCount: Number of alerts sent to LLMnewAlerts: Number of discoveries generatedduration: Time taken in nanosecondsOn Failure:
generation-failed (or the relevant per-phase *-failed) event with:
reason: Error messageoutcome: 'failure'Events follow the Elasticsearch event log schema:
{
'@timestamp': string,
event: {
// The full set is in ATTACK_DISCOVERY_EVENT_ACTIONS (event_logging/constants.ts):
// generation-started | generation-succeeded | generation-failed |
// generation-canceled | generation-dismissed |
// alert-retrieval-started | alert-retrieval-succeeded | alert-retrieval-failed |
// generate-step-started | generate-step-succeeded | generate-step-failed
action: string,
dataset: string, // Connector ID
duration?: number, // Duration in nanoseconds
end?: string, // ISO timestamp
outcome?: 'success' | 'failure',
provider: 'securitySolution.attackDiscovery',
reason?: string, // For failed generations
start?: string // ISO timestamp
},
kibana: {
alert: {
rule: {
consumer: 'siem',
execution: {
metrics?: {
alert_counts: {
active?: number, // Alerts sent to LLM
new?: number // Discoveries generated
}
},
status?: string, // Loading message
uuid: string // Execution UUID (ties events together)
}
}
},
space_ids: [string]
},
message: string,
tags: ['securitySolution', 'attackDiscovery'],
user: {
name: string
}
}
Created comprehensive unit tests for shared utilities:
get_duration_nanoseconds.test.ts:
write_attack_discovery_event.test.ts:
The following verification steps require manual execution:
Start Kibana and Elasticsearch
yarn es snapshot --E xpack.security.enabled=true
yarn start --no-base-path
Delete existing event log entries (clean slate)
curl -u elastic:changeme -X POST "http://localhost:9200/.kibana-event-log-*/_delete_by_query" -H 'Content-Type: application/json' -d'
{
"query": {
"term": {
"event.provider": "securitySolution.attackDiscovery"
}
}
}
'
Execute workflow manually
Query event log
curl -u elastic:changeme "http://localhost:9200/.kibana-event-log-*/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"term": {
"event.provider": "securitySolution.attackDiscovery"
}
},
"sort": [{"@timestamp": "asc"}]
}
'
Test GET /generations endpoint
curl -u elastic:changeme "http://localhost:5601/api/attack_discovery/generations?page=1&perPage=10" -H 'kbn-xsrf: true'
Test UI
None. This is an additive change that doesn't affect existing functionality.
The event-logging constants still live in elastic_assistant/common/constants.ts (no deprecation comments were added). The canonical action constants and ATTACK_DISCOVERY_EVENT_ACTIONS are also exported from @kbn/discoveries (impl/attack_discovery/persistence/event_logging/constants.ts); consolidating the two definitions is a future cleanup.
x-pack/platform/plugins/event_logx-pack/solutions/security/plugins/elastic_assistant/server/routes/attack_discovery/public/post/post_attack_discovery_generate.ts