Stabilize Core E2E release paths

Contract-Neutral: Canonical route normalization and test stabilization without a public subsystem contract change
This commit is contained in:
rcourtman
2026-07-19 21:03:58 +01:00
parent 685a671c61
commit 5d01c5e469
5 changed files with 185 additions and 167 deletions
@@ -1,5 +1,5 @@
import { useLocation, useSearchParams } from '@solidjs/router';
import { Show, createMemo, createSignal } from 'solid-js';
import { useLocation, useNavigate, useSearchParams } from '@solidjs/router';
import { Show, createEffect, createMemo, createSignal } from 'solid-js';
import BoxIcon from 'lucide-solid/icons/box';
import { ButtonLink } from '@/components/shared/Button';
import { getPlatformIcon } from '@/features/platformPage/platformIcon';
@@ -48,7 +48,7 @@ import {
buildInfrastructureAgentUpdatesPath,
buildInfrastructureOnboardingPath,
} from '@/components/Settings/infrastructureWorkspaceModel';
import { DOCKER_QUERY_PARAMS } from '@/routing/resourceLinks';
import { buildDockerPath, DOCKER_PATH, DOCKER_QUERY_PARAMS } from '@/routing/resourceLinks';
import { asTrimmedString } from '@/utils/stringUtils';
import type { Resource } from '@/types/resource';
@@ -60,6 +60,7 @@ const dockerIcon = () => <DockerIcon class="h-6 w-6 text-slate-400" />;
export function DockerPageSurface() {
const location = useLocation();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { resources, loading, error, refetch } = useUnifiedResources({
query: DOCKER_RESOURCE_QUERY,
@@ -75,6 +76,14 @@ export function DockerPageSurface() {
const activeTab = createMemo<DockerPageTabId>(() =>
tabs().some((tab) => tab.id === requestedTab()) ? requestedTab() : 'overview',
);
createEffect(() => {
const pathname = location.pathname.replace(/\/+$/, '');
if (pathname === `${DOCKER_PATH}/workloads`) {
navigate(buildDockerPath(), { replace: true });
}
});
const hostFilter = createMemo(() => {
const rawHost = searchParams[DOCKER_QUERY_PARAMS.host];
return typeof rawHost === 'string' ? rawHost.trim() : '';
@@ -7,6 +7,7 @@ import { DockerPageSurface } from '../DockerPageSurface';
const mocks = vi.hoisted(() => ({
pathname: '/docker/overview',
searchParams: {} as Record<string, string>,
navigate: vi.fn(),
useUnifiedResources: vi.fn(),
DockerHostsTable: vi.fn(
(props: { resources: Resource[]; showToolbar?: boolean; emptyTitle: string }) => (
@@ -88,6 +89,7 @@ vi.mock('@solidjs/router', () => ({
return mocks.pathname;
},
}),
useNavigate: () => mocks.navigate,
useSearchParams: () => [mocks.searchParams, vi.fn()],
}));
@@ -423,6 +425,16 @@ describe('DockerPageSurface', () => {
);
});
it('replaces the retired workloads route with the canonical Overview route', () => {
mocks.pathname = '/docker/workloads';
render(() => <DockerPageSurface />);
expect(mocks.navigate).toHaveBeenCalledWith('/docker/overview', {
replace: true,
});
});
it('shows the Swarm tab only when Docker hosts report Swarm evidence', () => {
mocks.useUnifiedResources.mockReturnValue({
error: () => null,
+9 -1
View File
@@ -193,7 +193,15 @@ test.describe("Mobile viewport flows", () => {
const table = page.locator("table.workload-table--mobile");
await expect(table).toBeVisible({ timeout: 30_000 });
const geometry = await table.evaluate((element) => {
const geometry = await page.evaluate(() => {
const element = document.querySelector<HTMLTableElement>(
"table.workload-table--mobile",
);
if (!element) {
throw new Error(
"Mobile workloads table disappeared before measurement",
);
}
const wrapper = element.parentElement;
return {
tableWidth: element.scrollWidth,
@@ -96,10 +96,13 @@ test.describe.serial('Storage physical disk drawer history', () => {
// Physical disks moved onto the Proxmox platform page's storage section.
await page.goto('/proxmox/storage', { waitUntil: 'domcontentloaded' });
await expect(page.getByTestId('storage-page')).toBeVisible({ timeout: 60_000 });
const disksTab = page.getByRole('tab', { name: 'Physical Disks' });
await expect(disksTab).toBeVisible({ timeout: 30_000 });
await disksTab.click();
await page.getByRole('textbox', { name: /Search Proxmox storage/ }).fill('nvme2');
await disksTab.click({ timeout: 30_000 });
const search = page.getByRole('textbox', { name: /Search Proxmox storage/ });
await expect(search).toBeVisible({ timeout: 30_000 });
await search.fill('nvme2');
const row = page
.locator('table tbody tr')
@@ -107,7 +110,7 @@ test.describe.serial('Storage physical disk drawer history', () => {
.filter({ hasText: 'pve2' })
.first();
await expect(row).toBeVisible();
await expect(row).toBeVisible({ timeout: 30_000 });
await row.getByRole('button', { name: /^Expand / }).click();
const detail = page.locator('[data-inline-detail-for]').filter({ has: page.getByText('Live I/O (30m)') }).first();
@@ -238,9 +238,13 @@ test.describe("Patrol Assistant operator briefing", () => {
});
});
await page.route("**/api/ai/sessions", async (route) => {
if (route.request().method() !== "GET") {
await route.continue();
await page.route("**/api/ai/sessions*", async (route) => {
const requestUrl = new URL(route.request().url());
if (
route.request().method() !== "GET" ||
requestUrl.pathname !== "/api/ai/sessions"
) {
await route.fallback();
return;
}
@@ -395,75 +399,83 @@ test.describe("Patrol Assistant operator briefing", () => {
});
});
const operatorBriefingFinding = () => ({
id: "finding-operator-briefing",
source: "ai-patrol",
severity: "critical",
category: "performance",
resource_id: "host:web-server",
resource_name: "web-server",
resource_type: "host",
title: "High CPU usage",
description: "CPU stayed above 95%.",
detected_at: "2026-05-06T12:00:00Z",
last_seen_at: "2026-05-06T12:06:00Z",
status: "active",
times_raised: 4,
regression_count: 2,
last_regression_at: "2026-05-06T12:06:00Z",
loop_state: "awaiting_approval",
remediation_id: "remediation-1",
investigation_status: "completed",
investigation_outcome: "fix_queued",
investigation_attempts: 1,
...(includeUnifiedInvestigationRecord
? {
investigation_record: {
id: "record-1",
finding_id: "finding-operator-briefing",
subject: {
resource_id: "host:web-server",
resource_name: "web-server",
resource_type: "host",
},
trigger: {
detected_at: "2026-05-06T12:00:00Z",
title: "High CPU usage",
},
status: "completed",
outcome: "fix_queued",
confidence: "high",
conclusion: "Backup job saturated CPU.",
recommended_action:
"Approve a controlled restart after the backup completes.",
evidence: [
{
kind: "metrics",
summary: "CPU stayed above 95% for 10 minutes",
},
],
proposed_fix: {
id: "fix-1",
description: "Restart the workload service",
commands: ["systemctl restart workload.service"],
risk_level: "medium",
destructive: true,
},
verification: ["CPU returned below 50%"],
tools_used: [],
started_at: "2026-05-06T12:00:00Z",
approval_id: "approval-1",
},
}
: {}),
});
await page.route("**/api/ai/patrol/findings*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([operatorBriefingFinding()]),
});
});
await page.route("**/api/ai/unified/findings*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
findings: [
{
id: "finding-operator-briefing",
source: "ai-patrol",
severity: "critical",
category: "performance",
resource_id: "host:web-server",
resource_name: "web-server",
resource_type: "host",
title: "High CPU usage",
description: "CPU stayed above 95%.",
detected_at: "2026-05-06T12:00:00Z",
last_seen_at: "2026-05-06T12:06:00Z",
status: "active",
times_raised: 4,
regression_count: 2,
last_regression_at: "2026-05-06T12:06:00Z",
loop_state: "awaiting_approval",
remediation_id: "remediation-1",
investigation_status: "completed",
investigation_outcome: "fix_queued",
investigation_attempts: 1,
...(includeUnifiedInvestigationRecord
? {
investigation_record: {
id: "record-1",
finding_id: "finding-operator-briefing",
subject: {
resource_id: "host:web-server",
resource_name: "web-server",
resource_type: "host",
},
trigger: {
detected_at: "2026-05-06T12:00:00Z",
title: "High CPU usage",
},
status: "completed",
outcome: "fix_queued",
confidence: "high",
conclusion: "Backup job saturated CPU.",
recommended_action:
"Approve a controlled restart after the backup completes.",
evidence: [
{
kind: "metrics",
summary: "CPU stayed above 95% for 10 minutes",
},
],
proposed_fix: {
id: "fix-1",
description: "Restart the workload service",
commands: ["systemctl restart workload.service"],
risk_level: "medium",
destructive: true,
},
verification: ["CPU returned below 50%"],
tools_used: [],
started_at: "2026-05-06T12:00:00Z",
approval_id: "approval-1",
},
}
: {}),
},
],
findings: [operatorBriefingFinding()],
count: 1,
active_count: 1,
}),
@@ -599,78 +611,62 @@ test.describe("Patrol Assistant operator briefing", () => {
.click();
await page.getByText("High CPU usage").click();
const finding = page.locator("#finding-finding-operator-briefing");
await finding
.getByRole("button", { name: "Discuss with Assistant" })
.first()
const findingReview = page.locator(
"#finding-finding-operator-briefing-details",
);
await findingReview.getByText("Manage", { exact: true }).click();
await findingReview
.getByRole("button", { name: "Open in Assistant" })
.click();
const assistantContext = page.getByLabel("Assistant context");
await expect(assistantContext).toBeVisible();
await expect(assistantContext).toContainText("Operator briefing attached");
await expect(assistantContext).toContainText("Pulse Patrol");
await expect(assistantContext).toContainText(
"Attention: active critical finding; regressed 2 times; last regression 2026-05-06T12:06:00Z; loop awaiting approval; approval approval-1; live approval pending; destructive proposed fix; fix queued for governed review",
"Completed · Fix Queued · High confidence",
);
await expect(assistantContext).toContainText(
`Decision: review live governed approval approval-1 before execution; approval pending; target web-server; expires ${approvalExpiresAt}; requested ${approvalRequestedAt}; proposed fix fix-1; risk high; destructive true`,
"High CPU usage on web-server (host)",
);
await expect(assistantContext).toContainText(
"Command details stay in approval context; destructive actions require governed approval.",
"Approval required before any action.",
);
await expect(
assistantContext.getByRole("button", {
name: "Review approval risk and next step",
}),
).toBeVisible();
await expect(
assistantContext.getByRole("button", {
name: "Explain Patrol evidence and confidence",
}),
).toBeVisible();
await expect(
assistantContext.getByRole("button", {
name: "Summarize remediation without command text",
}),
).toBeVisible();
await expect(
assistantContext.getByText("systemctl restart workload.service"),
).toHaveCount(0);
await assistantContext
.getByRole("button", { name: "Review approval risk and next step" })
.click();
await expect(
page.getByPlaceholder("Ask about your infrastructure..."),
).toHaveValue("Review approval risk and next step");
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
await page.getByTitle("Pulse Assistant sessions").click();
await expect(page.getByText("High CPU follow-up")).toBeVisible();
const highCPUFollowUp = page.getByRole("option", {
name: /^Resume High CPU follow-up/,
});
await expect(highCPUFollowUp).toBeVisible();
await expect(page.getByText("Pulse Patrol").last()).toBeVisible();
await expect(page.getByText("Approval required").last()).toBeVisible();
await expect(page.getByText(/approval pending/).last()).toBeVisible();
await page.getByText("High CPU follow-up").click();
await highCPUFollowUp.click();
const reloadedAssistantContext = page.getByLabel("Assistant context");
await expect(reloadedAssistantContext).toBeVisible();
await expect(reloadedAssistantContext).toContainText(
"Patrol finding on web-server",
);
await expect(reloadedAssistantContext).toContainText("approval pending");
await expect(reloadedAssistantContext).toContainText(
"Finding finding-operator-briefing",
);
await expect(reloadedAssistantContext).toContainText(
"Last known state: approval pending",
"Approval required before any action.",
);
await expect(
reloadedAssistantContext.getByText("systemctl restart workload.service"),
).toHaveCount(0);
await page.getByTitle("Pulse Assistant sessions").click();
await expect(page.getByText("Context-only Patrol follow-up")).toBeVisible();
const contextOnlyFollowUp = page.getByRole("option", {
name: /^Resume Context-only Patrol follow-up/,
});
await expect(contextOnlyFollowUp).toBeVisible();
await expect(page.getByText("Context attached").last()).toBeVisible();
await page.getByText("Context-only Patrol follow-up").click();
await contextOnlyFollowUp.click();
const contextOnlyAssistantContext = page.getByLabel("Assistant context");
await expect(contextOnlyAssistantContext).toBeVisible();
@@ -678,7 +674,7 @@ test.describe("Patrol Assistant operator briefing", () => {
"Patrol finding on web-server",
);
await expect(contextOnlyAssistantContext).toContainText(
"Finding finding-context-only",
"Approval required before any action.",
);
await expect(
contextOnlyAssistantContext.getByText(
@@ -687,10 +683,13 @@ test.describe("Patrol Assistant operator briefing", () => {
).toHaveCount(0);
await page.getByTitle("Pulse Assistant sessions").click();
await expect(page.getByText("Runtime failure follow-up")).toBeVisible();
const runtimeFailureFollowUp = page.getByRole("option", {
name: /^Resume Runtime failure follow-up/,
});
await expect(runtimeFailureFollowUp).toBeVisible();
await expect(page.getByText("Runtime issue").last()).toBeVisible();
await expect(page.getByText(/run error/).last()).toBeVisible();
await page.getByText("Runtime failure follow-up").click();
await runtimeFailureFollowUp.click();
const runAssistantContext = page.getByLabel("Assistant context");
await expect(runAssistantContext).toBeVisible();
@@ -699,7 +698,6 @@ test.describe("Patrol Assistant operator briefing", () => {
);
await expect(runAssistantContext).toContainText("Run run-runtime-error");
await expect(runAssistantContext).toContainText("Run type: Scoped run");
await expect(runAssistantContext).toContainText("Run state: run error");
await expect(runAssistantContext).toContainText(
"Review Patrol runtime issue",
);
@@ -709,9 +707,12 @@ test.describe("Patrol Assistant operator briefing", () => {
).toHaveCount(0);
await page.getByTitle("Pulse Assistant sessions").click();
await expect(page.getByText("Assessment follow-up")).toBeVisible();
const assessmentFollowUp = page.getByRole("option", {
name: /^Resume Assessment follow-up/,
});
await expect(assessmentFollowUp).toBeVisible();
await expect(page.getByText("Assessment context").last()).toBeVisible();
await page.getByText("Assessment follow-up").click();
await assessmentFollowUp.click();
const restoredAssessmentAssistantContext =
page.getByLabel("Assistant context");
@@ -720,27 +721,25 @@ test.describe("Patrol Assistant operator briefing", () => {
"Patrol assessment handoff",
);
await expect(restoredAssessmentAssistantContext).toContainText(
"Current Patrol assessment",
);
await expect(restoredAssessmentAssistantContext).toContainText(
"Review Patrol assessment",
"Approval required before any action.",
);
await page.getByTitle("Pulse Assistant sessions").click();
await expect(page.getByText("Configuration follow-up")).toBeVisible();
const configurationFollowUp = page.getByRole("option", {
name: /^Resume Configuration follow-up/,
});
await expect(configurationFollowUp).toBeVisible();
await expect(page.getByText("Runtime issue").last()).toBeVisible();
await page.getByText("Configuration follow-up").click();
await configurationFollowUp.click();
const configurationAssistantContext = page.getByLabel("Assistant context");
await expect(configurationAssistantContext).toBeVisible();
await expect(configurationAssistantContext).toContainText(
"Patrol configuration failure",
"Patrol mode save failure",
);
await expect(configurationAssistantContext).toContainText("Patrol mode");
await expect(configurationAssistantContext).toContainText(
"Patrol configuration",
);
await expect(configurationAssistantContext).toContainText(
"Review Patrol configuration issue",
"Review Patrol mode issue",
);
includePendingApproval = false;
@@ -753,30 +752,25 @@ test.describe("Patrol Assistant operator briefing", () => {
const queuedFindingTitle = page.getByText("High CPU usage").first();
await expect(queuedFindingTitle).toBeVisible();
await queuedFindingTitle.click();
const queuedFinding = page.locator("#finding-finding-operator-briefing");
const queuedFinding = page.locator(
"#finding-finding-operator-briefing-details",
);
await expect(queuedFinding.getByText("details unavailable")).toBeVisible();
await queuedFinding.getByText("Manage", { exact: true }).click();
await queuedFinding
.getByRole("button", { name: "Discuss with Assistant" })
.last()
.getByRole("button", { name: "Open in Assistant" })
.click();
const queuedAssistantContext = page.getByLabel("Assistant context");
await expect(queuedAssistantContext).toBeVisible();
await expect(queuedAssistantContext).toContainText(
"Operator briefing attached",
);
await expect(queuedAssistantContext).toContainText("Pulse Patrol");
await expect(queuedAssistantContext).toContainText("Fix Queued");
await expect(queuedAssistantContext).toContainText(
"Attention: active finding; loop fix queued; fix queued for governed review",
"High CPU usage on web-server (host)",
);
await expect(queuedAssistantContext).toContainText(
"Decision: Recover or regenerate the governed approval before execution; do not execute from chat context.",
"Approval required before any action.",
);
await expect(
queuedAssistantContext.getByRole("button", {
name: "List approval prerequisites before action",
}),
).toBeVisible();
await expect(
queuedAssistantContext.getByText("systemctl restart workload.service"),
).toHaveCount(0);
@@ -792,56 +786,48 @@ test.describe("Patrol Assistant operator briefing", () => {
const expiredFindingTitle = page.getByText("High CPU usage").first();
await expect(expiredFindingTitle).toBeVisible();
await expiredFindingTitle.click();
const expiredFinding = page.locator("#finding-finding-operator-briefing");
await expect(expiredFinding.getByText("approval expired")).toBeVisible();
const expiredFinding = page.locator(
"#finding-finding-operator-briefing-details",
);
await expect(expiredFinding.getByText("Action details unavailable")).toBeVisible();
await expiredFinding.getByText("Manage", { exact: true }).click();
await expiredFinding
.getByRole("button", { name: "Discuss with Assistant" })
.first()
.getByRole("button", { name: "Open in Assistant" })
.click();
const hydratedFindingAssistantContext =
page.getByLabel("Assistant context");
await expect(hydratedFindingAssistantContext).toBeVisible();
await expect(hydratedFindingAssistantContext).toContainText(
"Operator briefing attached",
);
await expect(hydratedFindingAssistantContext).toContainText("Pulse Patrol");
await expect(hydratedFindingAssistantContext).toContainText("Fix Queued");
await expect(hydratedFindingAssistantContext).toContainText(
"Proposed fix: Restart the workload service; target web-server; high risk; 1 command recorded for approval context; destructive proposed fix; rationale Workload service stayed wedged after backup pressure.",
"High CPU usage on web-server (host)",
);
await expect(hydratedFindingAssistantContext).toContainText(
"Approval required before any action.",
);
await expect(
hydratedFindingAssistantContext.getByRole("button", {
name: "Summarize remediation without command text",
}),
).toBeVisible();
await expect(
hydratedFindingAssistantContext.getByText(
"systemctl restart workload.service",
),
).toHaveCount(0);
await page.getByTestId("assistant-close-button").click();
await expiredFinding
.getByRole("button", { name: "Fix with Assistant" })
.last()
.getByRole("button", { name: "Discuss with Assistant" })
.first()
.click();
const expiredAssistantContext = page.getByLabel("Assistant context");
await expect(expiredAssistantContext).toBeVisible();
await expect(expiredAssistantContext).toContainText(
"Operator briefing attached",
);
await expect(expiredAssistantContext).toContainText("Pulse Patrol");
await expect(expiredAssistantContext).toContainText("Fix Queued");
await expect(expiredAssistantContext).toContainText(
"Proposed fix: Restart the workload service; target web-server; high risk; 1 command recorded for approval context; destructive proposed fix; rationale Workload service stayed wedged after backup pressure.",
"High CPU usage on web-server",
);
await expect(expiredAssistantContext).toContainText(
"Command details stay in approval context; destructive actions require governed approval.",
"Approval required before any action.",
);
await expect(
expiredAssistantContext.getByRole("button", {
name: "Summarize remediation without command text",
}),
).toBeVisible();
await expect(
expiredAssistantContext.getByText("systemctl restart workload.service"),
).toHaveCount(0);