Bind disk-reading guidance to states and accessible reasons

The guest-reading docs control required a literal Unavailable caption even though the reading state and its explanation are the contract. Keep retained labels, disk-specific title/assistive wiring and unavailable producer checks; negative controls reject lost reasons while permitting caption-only changes. Guide keyboard and phone users through the existing reading indicator and same-guest details without treating a dash as an agent-installation diagnosis. Add a consuming current-source Docs browser check. No human PR is imported, amended or re-authored; its source and independent integration remain separate.

Contract-Neutral: Align existing filesystem guidance and its executable checks with unchanged reading states and accessible reasons; no polling, telemetry, runtime, access, safety or recovery contract changes.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-13 07:33:36 +01:00
parent e3ea596b98
commit f6d9201a49
4 changed files with 180 additions and 10 deletions
+9 -4
View File
@@ -313,10 +313,15 @@ custom monitoring role if the required read privileges are absent.
### Current, retained and unavailable disk readings
In **Workloads**, read the disk cell's explanation or open the guest's details
and look under **Filesystems → Status**. Compact rows use **Prior** and **N/A**;
wider rows use **Last known** and **Unavailable**. Where an observation time is
shown, keep it with the reading; an unavailable time leaves freshness unknown.
In **Workloads**, select the disk reading indicator for its explanation, or
open the same guest's details and look under **Filesystems → Status**. Use the
details on a phone or with a keyboard rather than relying on a hover tooltip.
Retained readings use **Prior** in compact rows and **Last known** in wider rows.
An unavailable reading may show a dash or an unavailable label: the presence or
absence of a caption does not establish freshness or explain the cause. Read
the same guest's explanation before treating it as an agent setup problem.
Where an observation time is shown, keep it with the reading; an unavailable
time leaves freshness unknown.
| Reading | What it establishes |
| --- | --- |
@@ -0,0 +1,124 @@
// Exercise the shipped guidance through the production row, drawer and Docs
// renderer. Synthetic observations only; no guest request or setup action.
const fs = require('node:fs');
const crypto = require('node:crypto');
const assert = require('node:assert/strict');
const { chromium, webkit } = require('playwright');
const { createPublicationFixtureServer } = require('/workspace/frontend-modern/browser-tests/publication-fixture-server.cjs');
const root = '/workspace/frontend-modern';
const output = '/workspace/tmp/guest-disk-docs-context';
const origin = 'http://127.0.0.1:5331';
const hash = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
(async () => {
fs.mkdirSync(output, { recursive: true });
const result = {
result: 'incomplete',
playwright: require('playwright/package.json').version,
driver_sha256: hash(fs.readFileSync(__filename)),
cases: [],
captures: [],
cleanup: { browser: false, server: false },
limits: 'Current-source presentation and guidance with safe fixtures; not PR #2893, native recovery, installed use or release acceptance.',
};
let server, browser;
try {
assert.equal(result.playwright, JSON.parse(fs.readFileSync('/workspace/tests/integration/package-lock.json')).packages['node_modules/@playwright/test'].version);
server = createPublicationFixtureServer(root, 5331, 'browser-tests/guest-reading-help.html');
result.compiled = server.binding;
const docHash = hash(fs.readFileSync(root + '/public/docs/VM_DISK_MONITORING.md'));
assert.equal(server.binding.artifacts['docs/VM_DISK_MONITORING.md'], docHash);
assert.equal(hash(fs.readFileSync('/workspace/docs/VM_DISK_MONITORING.md')), docHash);
result.document_sha256 = docHash;
await server.listen();
for (const [name, engine, width] of [
['chromium-desktop', chromium, 1365],
['webkit-phone', webkit, 390],
]) {
browser = await engine.launch(engine === chromium
? { headless: true, channel: 'chromium', args: ['--no-sandbox'] }
: { headless: true });
const context = await browser.newContext({
viewport: { width, height: 900 },
isMobile: width <= 768,
hasTouch: width <= 768,
locale: 'en-GB',
});
const page = await context.newPage();
page.setDefaultTimeout(12000);
const record = { name, browser: browser.version(), width, errors: [], requests: [], docs: [] };
result.cases.push(record);
page.on('pageerror', (error) => record.errors.push(error.message));
page.on('response', (response) => {
if (new URL(response.url()).pathname === '/docs/VM_DISK_MONITORING.md') {
record.docs.push(response.body().then((body) => ({ status: response.status(), sha256: hash(body) })));
}
});
await page.route('**/*', (route) => {
const request = route.request(), url = new URL(request.url());
record.requests.push({ method: request.method(), path: url.pathname, external: url.origin !== origin });
if (url.origin !== origin) return route.abort();
if (url.pathname.startsWith('/api/')) return route.fulfill({
status: 200, contentType: 'application/json',
body: JSON.stringify({ success: true, data: [], alerts: [], anomalies: [], config: null }),
});
return route.continue();
});
await page.goto(origin, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => window.__readingHelp);
const disclosure = page.locator('[data-guest-id="fixture-pve:pve-a:101"] [data-row-action]').first();
if (width <= 768) await page.getByText('reading-guest', { exact: true }).tap();
else { await disclosure.focus(); await page.keyboard.press('Enter'); }
const drawer = page.getByRole('region', { name: 'Guest details' });
await drawer.waitFor({ state: 'visible' });
const details = drawer.getByTestId('guest-technical-details');
assert.match(await details.innerText(), /does not prove it is absent or stopped/);
const guidance = details.getByRole('link', { name: 'Filesystem reading guidance' });
if (width <= 768) await guidance.tap();
else { await guidance.focus(); await page.keyboard.press('Enter'); }
await page.locator('#a-missing-reading-is-not-an-installation-diagnosis').waitFor({ state: 'visible' });
const contextLink = page.getByRole('link', { name: "Check the reading's context", exact: true });
if (width <= 768) await contextLink.tap();
else { await contextLink.focus(); await page.keyboard.press('Enter'); }
const heading = page.locator('#current-retained-and-unavailable-disk-readings');
await heading.waitFor({ state: 'visible' });
await heading.scrollIntoViewIfNeeded();
const text = (await page.locator('article').innerText()).replace(/\s+/g, ' ');
for (const expected of [
'select the disk reading indicator', 'phone or with a keyboard',
'presence or absence of a caption does not establish freshness',
"same guest's explanation before treating it as an agent setup problem",
'unknown, not zero', 'not current free space',
'not a monitoring-coverage check', 'does not prove that the QEMU-only path recovered',
'Do not bypass a guest-read pause or widen token access',
]) assert.ok(text.includes(expected), `Missing rendered guidance: ${expected}`);
assert.doesNotMatch(text, /wider rows use Last known and Unavailable/);
record.guide = { url: page.url(), heading: await heading.innerText(), checks: 9 };
record.layout = await page.evaluate(() => ({ width: document.documentElement.clientWidth, scrollWidth: document.documentElement.scrollWidth }));
assert.ok(record.layout.scrollWidth <= record.layout.width + 1, JSON.stringify(record.layout));
const file = name + '-reading-context.png';
await page.screenshot({ path: output + '/' + file });
result.captures.push({ file, sha256: hash(fs.readFileSync(output + '/' + file)) });
record.docs = await Promise.all(record.docs);
assert.ok(record.docs.length > 0, 'Docs did not consume the changed producer');
for (const doc of record.docs) assert.deepEqual(doc, { status: 200, sha256: docHash });
assert.deepEqual(record.errors, []);
assert.equal(record.requests.filter((request) => !['GET', 'HEAD'].includes(request.method)).length, 0);
assert.equal(record.requests.filter((request) => request.external).length, 0);
await context.close();
await browser.close();
browser = null;
}
result.result = 'passed';
result.verified_at = new Date().toISOString();
} catch (error) {
result.error = String(error.stack || error);
throw error;
} finally {
if (browser) await browser.close();
result.cleanup.browser = true;
if (server) await server.close();
result.cleanup.server = true;
fs.writeFileSync(output + '/result.json', JSON.stringify(result, null, 2) + '\n');
}
})().catch((error) => { console.error(error); process.exitCode = 1; });
@@ -313,10 +313,15 @@ custom monitoring role if the required read privileges are absent.
### Current, retained and unavailable disk readings
In **Workloads**, read the disk cell's explanation or open the guest's details
and look under **Filesystems → Status**. Compact rows use **Prior** and **N/A**;
wider rows use **Last known** and **Unavailable**. Where an observation time is
shown, keep it with the reading; an unavailable time leaves freshness unknown.
In **Workloads**, select the disk reading indicator for its explanation, or
open the same guest's details and look under **Filesystems → Status**. Use the
details on a phone or with a keyboard rather than relying on a hover tooltip.
Retained readings use **Prior** in compact rows and **Last known** in wider rows.
An unavailable reading may show a dash or an unavailable label: the presence or
absence of a caption does not establish freshness or explain the cause. Read
the same guest's explanation before treating it as an agent setup problem.
Where an observation time is shown, keep it with the reading; an unavailable
time leaves freshness unknown.
| Reading | What it establishes |
| --- | --- |
+38 -2
View File
@@ -10,21 +10,41 @@ HEADING = "### Current, retained and unavailable disk readings\n"
ANCHOR = "#current-retained-and-unavailable-disk-readings"
def assert_disk_reason_wiring(test, row, presentation):
# An unavailable caption is optional presentation, not the reading's state
# or explanation. Bind the actual disk cell (not another metric's N/A) to
# its reason, and keep the current/retained/unavailable producer distinct.
disk_cell = row.split("{/* Disk */}", 1)[1].split("{/* IP Address", 1)[0]
test.assertIn("title={diskReadStatus()?.message}", disk_cell)
test.assertIn("title={getDiskStatusTooltip()}", disk_cell)
test.assertIn("{status().message}", disk_cell)
test.assertIn("state: reason.startsWith('prev-') ? 'last-known' : 'unavailable'", presentation)
test.assertIn("message: getWorkloadGuestDiskStatusMessage(reason)", presentation)
class GuestDiskReadingDocsTest(unittest.TestCase):
def section(self):
guide = (ROOT / "docs/VM_DISK_MONITORING.md").read_text(encoding="utf-8")
self.assertEqual(guide.count(HEADING), 1)
return " ".join(guide.split(HEADING, 1)[1].split("\n### ", 1)[0].split())
def test_explanations_use_existing_labels_without_claiming_freshness(self):
def test_explanations_distinguish_reading_states_without_requiring_an_empty_caption(self):
text = self.section()
row = (ROOT / "frontend-modern/src/components/Workloads/GuestRow.tsx").read_text()
drawer = (ROOT / "frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx").read_text()
presentation = (ROOT / "frontend-modern/src/utils/workloadGuestPresentation.ts").read_text()
for label in ("Prior", "N/A", "Last known", "Unavailable"):
for label in ("Prior", "Last known"):
with self.subTest(label=label):
self.assertIn(label, text)
self.assertIn(f"'{label}'", row)
for empty_state in ("N/A", "Unavailable", "a dash"):
self.assertIn(empty_state, text)
assert_disk_reason_wiring(self, row, presentation)
for distinction in ("select the disk reading indicator", "same guest's details",
"phone or with a keyboard", "rather than relying on a hover tooltip",
"presence or absence of a caption does not establish freshness",
"same guest's explanation before treating it as an agent setup problem"):
self.assertIn(distinction, text)
self.assertIn("Filesystems → Status", text)
self.assertIn("label: 'Filesystems'", drawer)
self.assertIn("makeDetailRow('Status'", drawer)
@@ -35,6 +55,22 @@ class GuestDiskReadingDocsTest(unittest.TestCase):
"A cooldown ending does not establish a successful new read"):
self.assertIn(distinction, text)
def test_reason_contract_accepts_caption_removal_not_reason_removal(self):
row = (ROOT / "frontend-modern/src/components/Workloads/GuestRow.tsx").read_text()
presentation = (ROOT / "frontend-modern/src/utils/workloadGuestPresentation.ts").read_text()
# A future caption-only edit must not require false documentation or
# pass just because another metric still contains the old label.
assert_disk_reason_wiring(self, row.replace("'N/A'", "null").replace("'Unavailable'", "null"),
presentation)
for required in ("title={diskReadStatus()?.message}", "title={getDiskStatusTooltip()}",
"{status().message}"):
with self.subTest(missing=required), self.assertRaises(AssertionError):
assert_disk_reason_wiring(self, row.replace(required, ""), presentation)
for required in ("state: reason.startsWith('prev-') ? 'last-known' : 'unavailable'",
"message: getWorkloadGuestDiskStatusMessage(reason)"):
with self.subTest(missing=required), self.assertRaises(AssertionError):
assert_disk_reason_wiring(self, row, presentation.replace(required, ""))
def test_inventory_workflow_does_not_confuse_selection_with_coverage(self):
text = self.section()
for distinction in ("not a monitoring-coverage check", "clear numeric disk conditions",