mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Stabilize Core E2E release paths
Contract-Neutral: Canonical route normalization and test stabilization without a public subsystem contract change
This commit is contained in:
parent
685a671c61
commit
5d01c5e469
5 changed files with 185 additions and 167 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue