fix(alerts): backport delivery diagnosis response ordering

Backport runtime and regression tests from 495562ef66. Reproduced both failures on exact release-line parent 101bae339; patched 38 focused tests, TypeScript and three-width Chromium pass. Retain local qualification and exact runtime browser receipt. No recipient receipt or release HOLD clearance claimed.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-06 17:42:51 +01:00
parent 101bae3396
commit 645302da7e
17 changed files with 400 additions and 10 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,43 @@
# Release-line Overview diagnosis ordering regression
Exact runtime parent: `101bae33963ee65a5a83bc229d081b43825de889`.
Backport source: `495562ef663e473889490a774a80cf8d04405732` (main).
Regression provenance: `32e620a6c36b4ce08729de747cb232dfabf5c1ba`
introduced the asynchronous delivery diagnosis projection on 26 August 2026;
it is an ancestor of this parent and v6.4.1. The unversioned completion path
is also present in v6.4.3-rc.1. This repairs an existing candidate surface,
not new product scope (RELEASE_PROMOTION_POLICY, Release Train rules 2/4).
The main runtime patch and its two regression tests apply without adaptation.
With only those tests and the browser fixture added to the exact parent,
2 tests fail and 11 pass: overlapping reads replace the current disabled
warning, and an obsolete response repopulates an empty active set. Chromium
also fails the retained-warning assertion (0 versus 1) at its first viewport.
These are local release-line reproductions, not inherited main receipts.
With the six-line runtime patch, all 13 tests pass. Another 25 adjacent
presentation and delivery-action tests pass. TypeScript (`tsc --noEmit`) exits
0. No full repository suite was run for this bounded UI backport.
Commands:
- `npm --prefix frontend-modern test -- src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx`
- `npm --prefix frontend-modern test -- src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts`
- `npm --prefix frontend-modern run type-check`
- `pulse-heavy-run -- node scripts/check-alert-diagnosis-ordering.mjs`
The fixture uses the real Overview component and synthetic API responses on
loopback only. It does not exercise an installed backend, actual delivery,
recipient receipt, or production routing. Latest-started ownership prevents
superseded successful reads from updating state, including empty-set
invalidation. Failed refreshes still retain the previous snapshot; no new
freshness claim or indicator is added.
This does not resolve warning-projection ordering, SQLite crashes, paid
convergence, recipient qualification or the release HOLD. No public release
or issue-resolution claim follows from this component evidence.
Patched Chromium passes at 1440, 900 and 390 pixels: warning retained, stale
dispatch absent, new alert present, text within viewport and no page errors.
Desktop and phone screenshots were visually inspected: warning and controls
remain visible. Receipt binds the runtime file hash to the exact parent.
@@ -0,0 +1,14 @@
> pulse-modern@1.0.0 test
> vitest run src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
✓ src/features/alerts/__tests__/deliveryDiagnosisPresentation.test.ts (16 tests) 137ms
✓ src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx (9 tests) 358ms
Test Files 2 passed (2)
Tests 25 passed (25)
Start at 17:38:35
Duration 2.35s (transform 1.13s, setup 212ms, collect 1.21s, tests 495ms, environment 727ms, prepare 184ms)
@@ -0,0 +1 @@
{"result":"passed","viewports":[1440,900,390],"scope":"Real Overview and Chromium; scripted diagnoses, not installed delivery or receipt"}
@@ -0,0 +1,14 @@
> pulse-modern@1.0.0 test
> vitest run src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
✓ src/features/alerts/__tests__/useAlertOverviewState.test.tsx (8 tests) 41ms
✓ src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx (5 tests) 156ms
Test Files 2 passed (2)
Tests 13 passed (13)
Start at 17:38:05
Duration 1.84s (transform 964ms, setup 109ms, collect 1.18s, tests 197ms, environment 658ms, prepare 123ms)
@@ -0,0 +1,18 @@
node:internal/modules/run_main:107
triggerUncaughtException(
^
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at file:///var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/scripts/check-alert-diagnosis-ordering.mjs:103:12 {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
Node.js v24.20.0
@@ -0,0 +1,80 @@
> pulse-modern@1.0.0 test
> vitest run src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx src/features/alerts/__tests__/useAlertOverviewState.test.tsx
RUN v3.2.7 /var/lib/pulse-maintainer/team-worktrees/20260906T163509Z-release-line/pulse@release_v6.4/frontend-modern
src/features/alerts/__tests__/useAlertOverviewState.test.tsx (8 tests | 1 failed) 47ms
× useAlertOverviewState > invalidates pending diagnosis reads when the active set becomes empty 13ms
→ expected { a1: { alertIdentifier: 'a1', …(1) } } to deeply equal {}
✓ useAlertOverviewState > owns overview stats, filtering, and acknowledge flows outside the tab shell 4ms
✓ useAlertOverviewState > keeps a stable id order for alerts sharing a startTime (#1218) 1ms
✓ useAlertOverviewState > counts alerts with missing startTime in total24h 1ms
✓ useAlertOverviewState > groups only explicit shared-system correlations across resources 1ms
✓ useAlertOverviewState > groups explicit infrastructure synthesis without hiding the supporting detector 1ms
✓ useAlertOverviewState > does not infer correlation by truncating resource paths 1ms
✓ useAlertOverviewState > still groups multiple detector signals for the exact same resource 1ms
src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx (5 tests | 1 failed) 167ms
✓ OverviewTab delivery status line > renders held-notification status from the bulk diagnosis endpoint 95ms
✓ OverviewTab delivery status line > does not turn dispatch evidence into receipt for 'ready' 16ms
✓ OverviewTab delivery status line > does not turn dispatch evidence into receipt for 'cooldown' 17ms
× OverviewTab delivery status line > ignores an older diagnosis response after the active alert set changes 24ms
→ expected <span …(2)></span> to be null
✓ OverviewTab delivery status line > renders no delivery line when the diagnosis fetch fails 10ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
FAIL src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx > OverviewTab delivery status line > ignores an older diagnosis response after the active alert set changes
AssertionError: expected <span …(2)></span> to be null
- Expected:
null
+ Received:
<span
class="text-xs text-muted"
title="Alert delivery is currently eligible for notification delivery."
>
Dispatch requested 11:15 AM
</span>
src/features/alerts/__tests__/OverviewTab.deliverystatus.test.tsx:145:56
143| finishOlder([makeDiagnosis('a1', { lastNotified: '2026-08-26T10:15…
144| await Promise.resolve();
145| expect(screen.queryByText(/^Dispatch requested /)).toBeNull();
| ^
146| expect(screen.getByText('Notifications are turned off')).toBeTruth…
147| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯
FAIL src/features/alerts/__tests__/useAlertOverviewState.test.tsx > useAlertOverviewState > invalidates pending diagnosis reads when the active set becomes empty
AssertionError: expected { a1: { alertIdentifier: 'a1', …(1) } } to deeply equal {}
- Expected
+ Received
- {}
+ {
+ "a1": {
+ "alertIdentifier": "a1",
+ "reason": "ready",
+ },
+ }
src/features/alerts/__tests__/useAlertOverviewState.test.tsx:85:40
83| finish([{ alertIdentifier: 'a1', reason: 'ready' } as AlertDeliver…
84| await Promise.resolve();
85| expect(result.deliveryDiagnoses()).toEqual({});
| ^
86| });
87|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯
Test Files 2 failed (2)
Tests 2 failed | 11 passed (13)
Start at 17:37:40
Duration 1.88s (transform 1.03s, setup 143ms, collect 1.24s, tests 214ms, environment 607ms, prepare 329ms)
@@ -0,0 +1,3 @@
> pulse-modern@1.0.0 type-check
> tsc --noEmit
@@ -15,6 +15,15 @@
## Purpose
Overview delivery diagnoses use latest-started refresh ownership. Older bulk
responses cannot overwrite newer card notification status, and an empty active
alert set invalidates outstanding reads. Disposal also prevents updates. Failed
refreshes retain the existing snapshot; this ordering repair does not add a
freshness indicator or establish recipient receipt. Verify response overlap in
`OverviewTab.deliverystatus.test.tsx`, empty-set invalidation in
`useAlertOverviewState.test.tsx`, and rendered ordering at three widths using
`scripts/check-alert-diagnosis-ordering.mjs`.
Confirmed canonical metric recovery publishes the clearing evaluation's value,
observation time, and resolved metric wording in the snapshot consumed by
recent-resolution reads and notification callbacks. Clone that snapshot before
@@ -20,6 +20,15 @@
## Purpose
Overview delivery diagnoses use latest-started refresh ownership. Older bulk
responses cannot overwrite newer card notification status, and an empty active
alert set invalidates outstanding reads. Disposal also prevents updates. Failed
refreshes retain the existing snapshot; this ordering repair does not add a
freshness indicator or establish recipient receipt. Verify response overlap in
`OverviewTab.deliverystatus.test.tsx`, empty-set invalidation in
`useAlertOverviewState.test.tsx`, and rendered ordering at three widths using
`scripts/check-alert-diagnosis-ordering.mjs`.
Proxmox backup presentation treats every manifestless PBS artifact as
non-recoverable. It renders the artifact as `Running` when current writer
visibility is absent or a matching writer is active, and as danger-tone
+8 -8
View File
@@ -1,13 +1,13 @@
{
"version": 1,
"base_sha": "2d550da942c6f0a7e7896e7aca86493983f90b84",
"verified_at": "2026-09-06T15:54:59.817913Z",
"base_sha": "101bae33963ee65a5a83bc229d081b43825de889",
"verified_at": "2026-09-06T16:42:39.585210Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/features/alerts/deliveryDiagnosisPresentation.ts"
"frontend-modern/src/features/alerts/useAlertOverviewState.ts"
],
"content_sha256": {
"frontend-modern/src/features/alerts/deliveryDiagnosisPresentation.ts": "bc3eadf8f790517b377430a42eb67e8fdfcadb53bb13b87cd971d6a9ab91d607"
"frontend-modern/src/features/alerts/useAlertOverviewState.ts": "64d0b891e7ad228e8590da859dc25e825b6164c8cf76a01983a219d6cd079b23"
},
"routes": [
"/qualification (loopback synthetic Overview, not production routing)"
@@ -27,11 +27,11 @@
}
],
"states": [
"Ready and cooldown show Dispatch requested; cooldown says next eligible; missing timestamp shows Notification pending. Scripted diagnoses, not recipient receipt.",
"390px screenshot visually inspected: longer status wraps within card and controls remain visible."
"Newer notifications-disabled response remains visible after older dispatch response completes. Synthetic diagnoses, not recipient receipt.",
"Desktop and phone screenshots visually inspected: current warning and card controls remain visible within viewport."
],
"interactions": [
"pulse-heavy-run -- node scripts/check-alert-dispatch-copy.mjs passed at all three widths; two dispatch labels, no Notified labels, one pending label, text fits viewport, no page errors.",
"Evidence: /var/lib/pulse-maintainer/queue/staging/20260906T155036Z-release-line/browser-dispatch.log and browser-dispatch/*.png."
"pulse-heavy-run -- node scripts/check-alert-diagnosis-ordering.mjs passed at 1440, 900 and 390 pixels: retained warning, absent stale dispatch, new alert present, text within viewport, no page errors.",
"Exact-parent failing and patched passing evidence: docs/qualification/release-v6.4-diagnosis-ordering/."
]
}
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createSignal } from 'solid-js';
import { cleanup, render, screen, waitFor } from '@solidjs/testing-library';
import { DEFAULT_LOCALE, setActiveLocale } from '@/i18n';
import type { Alert, AlertDeliveryDiagnosis } from '@/types/api';
@@ -124,6 +125,27 @@ describe('OverviewTab delivery status line', () => {
if (state.reason === 'cooldown') expect(screen.getByText(/next eligible/)).toBeTruthy();
});
it('ignores an older diagnosis response after the active alert set changes', async () => {
let finishOlder!: (value: AlertDeliveryDiagnosis[]) => void;
getDeliveryDiagnoses.mockReturnValueOnce(
new Promise((resolve) => {
finishOlder = resolve;
}),
);
getDeliveryDiagnoses.mockResolvedValueOnce([
makeDiagnosis('a1', { status: 'suppressed', reason: 'notifications_disabled' }),
]);
const [alerts, setAlerts] = createSignal<Record<string, Alert>>({ a1: makeAlert('a1') });
render(() => <OverviewTab {...defaultProps()} activeAlerts={alerts()} />);
await waitFor(() => expect(getDeliveryDiagnoses).toHaveBeenCalledTimes(1));
setAlerts({ a1: makeAlert('a1'), a2: makeAlert('a2') });
await waitFor(() => expect(screen.getByText('Notifications are turned off')).toBeTruthy());
finishOlder([makeDiagnosis('a1', { lastNotified: '2026-08-26T10:15:00Z' })]);
await Promise.resolve();
expect(screen.queryByText(/^Dispatch requested /)).toBeNull();
expect(screen.getByText('Notifications are turned off')).toBeTruthy();
});
it('renders no delivery line when the diagnosis fetch fails', async () => {
const activeAlerts: Record<string, Alert> = { a1: makeAlert('a1') };
getDeliveryDiagnoses.mockRejectedValue(new Error('boom'));
@@ -4,12 +4,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AlertsAPI } from '@/api/alerts';
import { notificationStore } from '@/stores/notifications';
import type { Alert } from '@/types/api';
import type { Alert, AlertDeliveryDiagnosis } from '@/types/api';
import { useAlertOverviewState } from '../useAlertOverviewState';
vi.mock('@/api/alerts', () => ({
AlertsAPI: {
getDeliveryDiagnoses: vi.fn(),
acknowledge: vi.fn(),
bulkAcknowledge: vi.fn(),
unacknowledge: vi.fn(),
@@ -47,6 +48,7 @@ describe('useAlertOverviewState', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-22T12:00:00Z'));
vi.mocked(AlertsAPI.getDeliveryDiagnoses).mockReset().mockResolvedValue([]);
vi.mocked(AlertsAPI.acknowledge).mockReset();
vi.mocked(AlertsAPI.unacknowledge).mockReset();
vi.mocked(AlertsAPI.bulkAcknowledge).mockReset();
@@ -58,6 +60,31 @@ describe('useAlertOverviewState', () => {
vi.useRealTimers();
});
it('invalidates pending diagnosis reads when the active set becomes empty', async () => {
let finish!: (value: AlertDeliveryDiagnosis[]) => void;
vi.mocked(AlertsAPI.getDeliveryDiagnoses).mockReturnValueOnce(
new Promise((resolve) => {
finish = resolve;
}),
);
const [activeAlerts, setActiveAlerts] = createSignal<Record<string, Alert>>({
a1: makeAlert('a1', new Date().toISOString()),
});
const { result } = renderHook(() =>
useAlertOverviewState({
activeAlerts,
overrides: () => [],
showAcknowledged: () => true,
updateAlert: vi.fn(),
}),
);
expect(AlertsAPI.getDeliveryDiagnoses).toHaveBeenCalledOnce();
setActiveAlerts({});
finish([{ alertIdentifier: 'a1', reason: 'ready' } as AlertDeliveryDiagnosis]);
await Promise.resolve();
expect(result.deliveryDiagnoses()).toEqual({});
});
it('owns overview stats, filtering, and acknowledge flows outside the tab shell', async () => {
const now = Date.now();
const [activeAlerts] = createSignal<Record<string, Alert>>({
@@ -68,17 +68,21 @@ export function useAlertOverviewState(props: UseAlertOverviewStateProps) {
Record<string, AlertDeliveryDiagnosis>
>({});
let diagnosisStateDisposed = false;
let diagnosisRequestVersion = 0;
onCleanup(() => {
diagnosisStateDisposed = true;
});
const refreshDeliveryDiagnoses = async () => {
// A slower previous refresh must not replace a newer notification state.
// Increment even for an empty alert set to invalidate outstanding requests.
const requestVersion = ++diagnosisRequestVersion;
if (activeAlerts().length === 0) {
setDeliveryDiagnoses({});
return;
}
try {
const list = await AlertsAPI.getDeliveryDiagnoses();
if (diagnosisStateDisposed) return;
if (diagnosisStateDisposed || requestVersion !== diagnosisRequestVersion) return;
const next: Record<string, AlertDeliveryDiagnosis> = {};
for (const diagnosis of list) {
next[diagnosis.alertIdentifier || diagnosis.alertId] = diagnosis;
+146
View File
@@ -0,0 +1,146 @@
// Isolated real-browser component qualification; no installed backend or delivery claim.
import { createServer } from "../frontend-modern/node_modules/vite/dist/node/index.js";
import solid from "../frontend-modern/node_modules/vite-plugin-solid/dist/esm/index.mjs";
import { chromium } from "@playwright/test";
import { resolve } from "node:path";
import { mkdirSync } from "node:fs";
import assert from "node:assert/strict";
const root = resolve("frontend-modern");
process.chdir(root);
const fixture = `
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
import { Router, Route } from '@solidjs/router';
import { AlertsAPI } from '/src/api/alerts';
import { NotificationsAPI } from '/src/api/notifications';
import { OverviewTab } from '/src/features/alerts/OverviewTab';
import '/src/index.css';
let finishOlder;
let requests = 0;
AlertsAPI.getDeliveryDiagnoses = () => {
requests++;
if (requests === 1) return new Promise(resolve => { finishOlder = resolve; });
return Promise.resolve([{alertIdentifier:'a1', alertId:'a1', status:'suppressed',
reason:'notifications_disabled', message:'Notifications disabled by current configuration'}]);
};
window.finishOlder = () => finishOlder([{alertIdentifier:'a1', alertId:'a1',
status:'would_send',reason:'ready',lastNotified:'2026-08-26T10:15:00Z'}]);
window.requestCount = () => requests;
AlertsAPI.getEvents = async () => [];
NotificationsAPI.getHealth = async () => ({queue:{status:'healthy'}});
const alert = id => ({id,resourceId:id,resourceName:'VM '+id,type:'cpu',level:'warning',
message:'High CPU on '+id,startTime:new Date().toISOString(),acknowledged:false,node:'node1'});
function Fixture() {
const [alerts, setAlerts] = createSignal({a1:alert('a1')});
return <main class="p-4"><button onClick={()=>setAlerts({a1:alert('a1'),a2:alert('a2')})}>Add alert</button>
<OverviewTab overrides={[]} activeAlerts={alerts()}
updateAlert={()=>{}} showQuickTip={()=>false} dismissQuickTip={()=>{}} showAcknowledged={()=>true}
setShowAcknowledged={()=>{}} alertsDisabled={()=>false}/></main>; }
render(()=><Router><Route path="/qualification" component={Fixture}/></Router>,document.getElementById('root'));
`;
const server = await createServer({
root,
configFile: false,
optimizeDeps: {
noDiscovery: true,
entries: [],
esbuildOptions: { target: "esnext" },
},
esbuild: { target: "esnext" },
plugins: [
solid(),
{
name: "dispatch-fixture",
configureServer(s) {
s.middlewares.use((req, res, next) => {
if (req.url === "/qualification") {
res.setHeader("Content-Type", "text/html");
res.end(
'<div id="root"></div><script type="module" src="/dispatch-fixture.tsx"></script>',
);
} else next();
});
},
resolveId(id) {
if (id === "/dispatch-fixture.tsx") return id;
},
load(id) {
if (id === "/dispatch-fixture.tsx") return fixture;
},
},
],
resolve: { alias: { "@": resolve(root, "src") } },
server: { host: "127.0.0.1", port: 5199, strictPort: true },
});
let browser;
try {
await server.listen();
browser = await chromium.launch({ headless: true });
mkdirSync("/tmp/pulse-alert-diagnosis-ordering", { recursive: true });
for (const width of [1440, 900, 390]) {
const page = await browser.newPage({ viewport: { width, height: 1000 } });
const errors = [];
page.on("pageerror", (e) => {
errors.push(e.message);
console.error(e.message);
});
page.on("console", (m) => {
if (m.type() === "error") console.error(m.text());
});
await page.route("http://127.0.0.1:5199/api/**", (route) =>
route.fulfill({ json: [] }),
);
await page.goto("http://127.0.0.1:5199/qualification");
await page.waitForFunction(() => window.requestCount?.() === 1);
await page.getByRole("button", { name: "Add alert", exact: true }).click();
await page
.getByText("Notifications are turned off", { exact: true })
.waitFor();
await page.evaluate(async () => {
window.finishOlder();
await Promise.resolve();
});
assert.equal(
await page
.getByText("Notifications are turned off", { exact: true })
.count(),
1,
);
assert.equal(await page.getByText(/^Dispatch requested /).count(), 0);
assert.equal(
await page.getByText("High CPU on a2", { exact: true }).count(),
1,
);
const label = page.getByText("Notifications are turned off", {
exact: true,
});
assert.equal(
await label.evaluate((el) => {
const range = document.createRange();
range.selectNodeContents(el);
return [...range.getClientRects()].every(
(b) => b.left >= 0 && b.right <= innerWidth,
);
}),
true,
"current status must fit viewport",
);
assert.deepEqual(errors, []);
await page.screenshot({
path: "/tmp/pulse-alert-diagnosis-ordering/" + width + ".png",
fullPage: true,
});
await page.close();
}
console.log(
JSON.stringify({
result: "passed",
viewports: [1440, 900, 390],
scope:
"Real Overview and Chromium; scripted diagnoses, not installed delivery or receipt",
}),
);
} finally {
await browser?.close();
await server.close();
}