Skip to content

Commit a1ef2a2

Browse files
committed
Consolidate investigation state schema
1 parent f3e91d4 commit a1ef2a2

15 files changed

Lines changed: 117 additions & 203 deletions

File tree

src/platform/packages/shared/kbn-workflows/managed/definitions/nightshift_investigations/investigation/investigation_workflow.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ steps:
152152
{% elsif inputs.context and inputs.context.size > 0 %}
153153
Additional context: {{ inputs.context | json }}
154154
{% endif %}
155-
# Keep this in sync with investigationAgentOutputSchema in @kbn/significant-events-schema
155+
# Keep this in sync with investigationStateSchema in @kbn/significant-events-schema
156156
# (x-pack/platform/packages/shared/kbn-significant-events-schema/src/significant_events/investigation_state.ts)
157157
# — this is the same schema the investigation_progress_report tool streams live, so the
158158
# final structured output here must match exactly (including maxLength/maxItems: the UI

x-pack/platform/packages/shared/kbn-investigation-output/src/investigation_output.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ describe('InvestigationOutput', () => {
189189
const gap = 'No GeoIP enrichment available for the attacker IPs.';
190190
const stateWithRecoveredGap: InvestigationState = {
191191
...finalState,
192-
blind_spots: [{ title: gap, description: gap }],
192+
blind_spots: [{ title: gap, confidence: 0.8, description: gap }],
193193
};
194194

195195
renderWithI18n(<InvestigationOutput status="complete" state={stateWithRecoveredGap} />);

x-pack/platform/packages/shared/kbn-investigation-output/src/use_investigation_state.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ describe('useInvestigationState', () => {
379379
});
380380
});
381381

382-
it('ignores malformed progress payloads instead of rendering them', async () => {
382+
it('ignores progress payloads with unscored recommendations instead of rendering them', async () => {
383383
const http = createHttp();
384384
const { result } = renderHook(() =>
385385
useInvestigationState({ http, workflowExecutionId: 'exec-1', isRunning: true })
@@ -389,7 +389,13 @@ describe('useInvestigationState', () => {
389389
const snapshot = { summary: 'valid', hypotheses: [] };
390390
act(() => {
391391
mockSubject.next(progressEvent(snapshot));
392-
mockSubject.next(progressEvent({ nonsense: true }));
392+
mockSubject.next(
393+
progressEvent({
394+
summary: 'Historical output.',
395+
hypotheses: [],
396+
recommendations: [{ title: 'Restart the service' }],
397+
})
398+
);
393399
});
394400

395401
await waitFor(() => {

x-pack/platform/packages/shared/kbn-investigation-output/src/use_investigation_state.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ export interface UseInvestigationStateResult {
8585

8686
/**
8787
* Surfaces the current state of an investigation, live or completed, from a single source:
88-
* `investigationStateSchema` — the backward-compatible read schema for the stricter current state
89-
* the investigation agent streams via `investigation_progress` `tool_ui` events and returns from
90-
* the `investigate` step.
88+
* `investigationStateSchema` — the same schema the investigation agent streams via
89+
* `investigation_progress` `tool_ui` events and returns from the `investigate` step.
9190
*
9291
* - While the investigation runs, the underlying agent execution's id isn't known upfront (it's
9392
* auto-generated) — the workflow tags it with `workflowExecutionId` as metadata instead of

x-pack/platform/packages/shared/kbn-significant-events-schema/index.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,9 @@ export {
9090
type InvestigationImpact,
9191
type InvestigationImpactEntity,
9292
type InvestigationRecommendation,
93-
type InvestigationRecommendationOutput,
9493
type InvestigationBlindSpot,
95-
type InvestigationBlindSpotOutput,
9694
type InvestigationRunStatus,
9795
type InvestigationState,
98-
type InvestigationAgentOutput,
9996
type TriggerFeedback,
10097
type InvestigationEvidence,
10198
type InvestigationEvidenceCode,
@@ -114,10 +111,7 @@ export {
114111
investigationImpactSchema,
115112
investigationHypothesisSchema,
116113
investigationRecommendationSchema,
117-
investigationRecommendationsOutputSchema,
118114
investigationBlindSpotSchema,
119-
investigationBlindSpotsOutputSchema,
120-
investigationAgentOutputSchema,
121115
triggerFeedbackSchema,
122116
type BlastRadiusEntry,
123117
type CausalFeature,

x-pack/platform/packages/shared/kbn-significant-events-schema/src/significant_events/index.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,25 +68,19 @@ export {
6868
investigationImpactSchema,
6969
investigationHypothesisSchema,
7070
investigationRecommendationSchema,
71-
investigationRecommendationsOutputSchema,
7271
investigationBlindSpotSchema,
73-
investigationBlindSpotsOutputSchema,
7472
triggerFeedbackSchema,
7573
investigationStateSchema,
76-
investigationAgentOutputSchema,
7774
type TriggerFeedback,
7875
type InvestigationEvidence,
7976
type InvestigationEvidenceCode,
8077
type InvestigationHypothesis,
8178
type InvestigationImpact,
8279
type InvestigationImpactEntity,
8380
type InvestigationRecommendation,
84-
type InvestigationRecommendationOutput,
8581
type InvestigationBlindSpot,
86-
type InvestigationBlindSpotOutput,
8782
type InvestigationRunStatus,
8883
type InvestigationState,
89-
type InvestigationAgentOutput,
9084
} from './investigation_state';
9185
export {
9286
type SignificantEventsTuningConfig,

x-pack/platform/packages/shared/kbn-significant-events-schema/src/significant_events/investigation_state.ts

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
* tool. Consumers follow the agent execution's event stream and filter for this event to
2323
* receive live, schema-typed updates while the investigation is still running. Every emission
2424
* carries the FULL current investigation state (never a delta) — see
25-
* {@link investigationAgentOutputSchema}.
25+
* {@link investigationStateSchema}.
2626
*/
2727
export const INVESTIGATION_PROGRESS_UI_EVENT = 'investigation_progress' as const;
2828

@@ -144,21 +144,10 @@ export type InvestigationHypothesis = z.infer<typeof investigationHypothesisSche
144144
/** Max recommendation entries a current investigation can emit. Keep in sync with YAML maxItems. */
145145
export const MAX_RECOMMENDATIONS = 3;
146146

147-
/** Historical recommendation limit retained by the read schema for persisted investigations. */
148-
const MAX_LEGACY_RECOMMENDATIONS = 5;
149-
150147
const investigationItemConfidenceSchema = z.number().min(0).max(1);
151148

152-
const sortByConfidence = <T extends { confidence?: number }>(items: T[]): T[] =>
153-
[...items].sort((first, second) => {
154-
if (first.confidence === undefined) {
155-
return second.confidence === undefined ? 0 : 1;
156-
}
157-
if (second.confidence === undefined) {
158-
return -1;
159-
}
160-
return second.confidence - first.confidence;
161-
});
149+
const sortByConfidence = <T extends { confidence: number }>(items: T[]): T[] =>
150+
[...items].sort((first, second) => second.confidence - first.confidence);
162151

163152
/**
164153
* One concrete, actionable step to resolve or mitigate the issue — a command, config change, or
@@ -169,7 +158,7 @@ export const investigationRecommendationSchema = z.object({
169158
/** The action itself, stated concretely (e.g. "Revert the pool-size config change"). */
170159
title: z.string().max(MAX_MEDIUM_STRING_LENGTH),
171160
/** How strongly the findings support that this action will resolve or mitigate the confirmed problem. */
172-
confidence: investigationItemConfidenceSchema.optional(),
161+
confidence: investigationItemConfidenceSchema,
173162
/** Why this step helps, or detail needed to carry it out, when the title alone isn't enough. */
174163
description: z.string().max(MAX_TEXT_LENGTH).optional(),
175164
/** A command, config snippet, or code change backing this step, when one applies. Raw source,
@@ -178,24 +167,9 @@ export const investigationRecommendationSchema = z.object({
178167
});
179168
export type InvestigationRecommendation = z.infer<typeof investigationRecommendationSchema>;
180169

181-
const investigationRecommendationOutputSchema = investigationRecommendationSchema.required({
182-
confidence: true,
183-
});
184-
export type InvestigationRecommendationOutput = z.infer<
185-
typeof investigationRecommendationOutputSchema
186-
>;
187-
188-
export const investigationRecommendationsOutputSchema = z
189-
.array(investigationRecommendationOutputSchema)
190-
.max(MAX_RECOMMENDATIONS)
191-
.overwrite(sortByConfidence);
192-
193170
/** Max blind spot entries a current investigation can emit. Keep in sync with YAML maxItems. */
194171
export const MAX_BLIND_SPOTS = 3;
195172

196-
/** Historical blind spot limit retained by the read schema for persisted investigations. */
197-
const MAX_LEGACY_BLIND_SPOTS = 10;
198-
199173
/**
200174
* A signal the agent wanted but could not access (e.g. missing instrumentation) — an actionable
201175
* knowledge gap, not an incident-specific fact. Structured so consumers don't have to split a
@@ -205,22 +179,12 @@ export const investigationBlindSpotSchema = z.object({
205179
/** The missing data source or access, named concisely (e.g. "No traces for the cart service"). */
206180
title: z.string().max(MAX_MEDIUM_STRING_LENGTH),
207181
/** How strongly the findings support that closing this gap would materially improve the investigation. */
208-
confidence: investigationItemConfidenceSchema.optional(),
182+
confidence: investigationItemConfidenceSchema,
209183
/** Why this gap mattered to the investigation. */
210184
description: z.string().max(MAX_TEXT_LENGTH),
211185
});
212186
export type InvestigationBlindSpot = z.infer<typeof investigationBlindSpotSchema>;
213187

214-
const investigationBlindSpotOutputSchema = investigationBlindSpotSchema.required({
215-
confidence: true,
216-
});
217-
export type InvestigationBlindSpotOutput = z.infer<typeof investigationBlindSpotOutputSchema>;
218-
219-
export const investigationBlindSpotsOutputSchema = z
220-
.array(investigationBlindSpotOutputSchema)
221-
.max(MAX_BLIND_SPOTS)
222-
.overwrite(sortByConfidence);
223-
224188
/** Max evidence entries per trigger-feedback proposal. Keep in sync with the YAML maxItems. */
225189
export const MAX_TRIGGER_FEEDBACK_EVIDENCE = 10;
226190

@@ -276,9 +240,7 @@ export type TriggerFeedback = z.infer<typeof triggerFeedbackSchema>;
276240
export const MAX_HYPOTHESES = 50;
277241

278242
/**
279-
* Read-facing investigation state. It accepts historical unscored recommendations and blind spots
280-
* at their original limits while ranking any scored items. Current agent output uses the stricter
281-
* {@link investigationAgentOutputSchema}; both shapes render through this compatible state type.
243+
* Full investigation state shared by agent output, persistence, and consumers.
282244
*/
283245
export const investigationStateSchema = z.object({
284246
/** Current ("what's happening now") or final narrative summary of the investigation. */
@@ -312,21 +274,16 @@ export const investigationStateSchema = z.object({
312274
/** Concrete, actionable steps to resolve or mitigate the issue. */
313275
recommendations: z
314276
.array(investigationRecommendationSchema)
315-
.max(MAX_LEGACY_RECOMMENDATIONS)
277+
.max(MAX_RECOMMENDATIONS)
316278
.overwrite(sortByConfidence)
317279
.optional(),
318280
/**
319-
* Actionable knowledge gaps discovered during the investigation. Replaces the free-text
320-
* `gaps_found` string array. Investigations persisted before this field existed still carry
321-
* `gaps_found`, which this schema strips as a key it no longer declares — so recovering them
322-
* means rewriting the raw payload before it reaches this schema, as
323-
* `normalizeLegacyInvestigationState` in `@kbn/investigation-output` does. Those gaps are also
324-
* folded into the memory `_gaps/overview` page by the workflow's `merge_investigation_gaps`
325-
* step, so they survive outside this payload either way.
281+
* Actionable knowledge gaps discovered during the investigation. Replaces the legacy free-text
282+
* `gaps_found` string array, which this schema ignores.
326283
*/
327284
blind_spots: z
328285
.array(investigationBlindSpotSchema)
329-
.max(MAX_LEGACY_BLIND_SPOTS)
286+
.max(MAX_BLIND_SPOTS)
330287
.overwrite(sortByConfidence)
331288
.optional(),
332289
/**
@@ -345,10 +302,3 @@ export const investigationStateSchema = z.object({
345302
impact: investigationImpactSchema.optional(),
346303
});
347304
export type InvestigationState = z.infer<typeof investigationStateSchema>;
348-
349-
/** Strict state emitted by current agents; reads remain tolerant of historical unscored items. */
350-
export const investigationAgentOutputSchema = investigationStateSchema.extend({
351-
recommendations: investigationRecommendationsOutputSchema.optional(),
352-
blind_spots: investigationBlindSpotsOutputSchema.optional(),
353-
});
354-
export type InvestigationAgentOutput = z.infer<typeof investigationAgentOutputSchema>;

0 commit comments

Comments
 (0)