Label key-collision lifecycle events in the finding timeline
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run

Completes the deferred UI half of dad1152fc: the content_replaced
lifecycle event (emitted when a same-key re-detection's text is
substantially different from the existing finding — a key collision)
now renders as "Re-detected with different details" in the finding
timeline instead of the identifier-formatter fallback, so the operator
reads what actually happened in plain language. The event metadata
already carries the previous and new titles.

Ceremony this line deferred from the backend commit: patrol-intelligence
contract Current State entry (lifecycle label map rule + the new label),
ai-runtime cross-reference updated, and the label pinned in
FindingsPanel.test.ts lifecycleLabels (the subsystem's accepted proof).
111 FindingsPanel tests green.
This commit is contained in:
rcourtman 2026-06-09 22:10:53 +01:00
parent dad1152fc4
commit f4d57c6836
4 changed files with 25 additions and 4 deletions

View file

@ -2343,10 +2343,8 @@ old issue: both `content_replaced` and `regressed` events appear).
Rephrasings and identical re-detections stay event-free per the
heartbeat-not-transition rule. The shared lifecycle presentation
(`frontend-modern/src/utils/aiFindingPresentation.ts`,
patrol-intelligence subsystem) renders unknown lifecycle types through the
identifier-formatter fallback, so the event reads "Content replaced" today;
a dedicated label is patrol-intelligence UI work and lands with that
subsystem's own ceremony.
patrol-intelligence subsystem) labels the event "Re-detected with different
details" — see that contract's Current State entry for the UI-side rule.
`TestFindingsStore_KeyCollisionRecordsContentReplacedEvent` and its
companion tests (`internal/ai/findings_lifecycle_test.go`) pin the behavior.

View file

@ -434,6 +434,18 @@ Patrol-specific presentation helpers.
## Current State
The finding lifecycle timeline renders backend lifecycle event types through
the shared label map in
`frontend-modern/src/utils/aiFindingPresentation.ts`
(`formatFindingLifecycleType`), with unknown types falling back to the
identifier formatter. The `content_replaced` event — emitted by the
ai-runtime findings store when a same-key re-detection's text is
substantially different from the existing finding (a key collision; see the
ai-runtime contract's Current State entry) — is labeled "Re-detected with
different details" so the operator timeline explains the shift in plain
language; the event's metadata carries the previous and new titles.
`FindingsPanel.test.ts` (`lifecycleLabels`) pins the label.
The Patrol findings panel
(`frontend-modern/src/components/AI/FindingsPanel.tsx`) dropped its "Open
related infrastructure / workloads / storage / recovery" cross-jump chip

View file

@ -1000,6 +1000,16 @@ describe('aiFindingPresentation', () => {
it('has dismissed label', () => {
expect(formatFindingLifecycleType('dismissed')).toBe('Dismissed');
});
it('labels a key-collision content replacement as a re-detection with different details', () => {
// Backend emits content_replaced when a same-key re-detection's text
// is substantially different from the existing finding (a key
// collision); the timeline label must explain the shift in operator
// language rather than the raw identifier fallback.
expect(formatFindingLifecycleType('content_replaced')).toBe(
'Re-detected with different details',
);
});
});
describe('formatLifecycleType', () => {

View file

@ -109,6 +109,7 @@ const FINDING_LIFECYCLE_LABELS: Record<string, string> = {
loop_state: 'Loop state changed',
seen_while_suppressed: 'Seen while suppressed',
loop_transition_violation: 'Invalid transition blocked',
content_replaced: 'Re-detected with different details',
};
const FINDING_STATUS_BADGE_CLASSES: Record<string, string> = {