From 1a0cd7c9d5aa02de754535a293dc0cacd4ef6fa0 Mon Sep 17 00:00:00 2001
From: "pulse-triage[bot]"
<249995291+pulse-triage[bot]@users.noreply.github.com>
Date: Thu, 1 Oct 2026 19:25:52 +0100
Subject: [PATCH] Keep private-file and uninstall guidance clear through copy
lint
Split two existing instructions into plain sentences without changing private-token handling, host-local removal or file ownership requirements. Verify the actual installer and removed-agent guidance and clipboard outputs in desktop Chromium and phone WebKit.
Contract-Neutral: Copy-only sentence changes preserve credential transport, installation and removal contracts.
Change-source: pulse-maintainer
---
.../browser-tests/unix-guidance-copy.cjs | 193 ++++++++++++++++++
.../browser-tests/unix-private-install.tsx | 12 +-
frontend-modern/browser-verification.json | 49 ++---
.../InfrastructureAgentDoctorPage.tsx | 2 +-
.../infrastructureOperationsModel.tsx | 2 +-
5 files changed, 225 insertions(+), 33 deletions(-)
create mode 100644 frontend-modern/browser-tests/unix-guidance-copy.cjs
diff --git a/frontend-modern/browser-tests/unix-guidance-copy.cjs b/frontend-modern/browser-tests/unix-guidance-copy.cjs
new file mode 100644
index 000000000..c074bb014
--- /dev/null
+++ b/frontend-modern/browser-tests/unix-guidance-copy.cjs
@@ -0,0 +1,193 @@
+// Scoped copy proof: actual installer/Doctor/styles, synthetic token API only.
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const { chromium, webkit } = require('playwright');
+(async () => {
+ const root = '/workspace/frontend-modern';
+ process.chdir(root);
+ const artifacts = path.join(root, 'node_modules', 'unix-guidance-browser');
+ fs.mkdirSync(artifacts, { recursive: true });
+ const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js'));
+ const server = await createServer({
+ root,
+ configFile: path.join(root, 'vite.config.ts'),
+ cacheDir: path.join(root, 'node_modules', '.vite-unix-guidance-copy'),
+ optimizeDeps: {
+ noDiscovery: true,
+ include: ['solid-js', 'solid-js/web', '@solidjs/router'],
+ },
+ server: { host: '127.0.0.1', port: 5244, strictPort: true },
+ });
+ const secret = 'browser-synthetic-install-token';
+ const results = [];
+ let browser;
+ try {
+ await server.listen();
+ for (const [engine, width, tone] of [
+ ['chromium', 1280, 'light'],
+ ['webkit', 390, 'dark'],
+ ]) {
+ browser = await { chromium, webkit }[engine].launch(
+ engine === 'chromium'
+ ? { headless: true, channel: 'chromium', args: ['--no-sandbox'] }
+ : { headless: true },
+ );
+ const page = await browser.newPage({
+ viewport: { width, height: 900 },
+ ...(engine === 'webkit' ? { isMobile: true, hasTouch: true } : {}),
+ });
+ const errors = [];
+ page.on('pageerror', (error) => {
+ errors.push(error.message);
+ console.log('PAGE_ERROR', error.message);
+ });
+ page.on('console', (message) => {
+ if (message.type() === 'error') {
+ errors.push(message.text());
+ console.log('CONSOLE_ERROR', message.text());
+ }
+ });
+ await page.addInitScript(() => {
+ window.__copies = [];
+ Object.defineProperty(navigator, 'clipboard', {
+ value: {
+ writeText: async (text) => window.__copies.push(text),
+ },
+ });
+ });
+ await page.route('**/api/**', async (route) => {
+ const pathname = new URL(route.request().url()).pathname;
+ // Do not intercept the production /src/api/*.ts module requests.
+ if (!pathname.startsWith('/api/')) return route.continue();
+ if (pathname === '/api/security/status')
+ return route.fulfill({
+ json: {
+ requiresAuth: true,
+ hasAuthentication: true,
+ apiTokenConfigured: true,
+ },
+ });
+ if (pathname === '/api/agent-install-command')
+ return route.fulfill({
+ json: {
+ token: secret,
+ record: {
+ id: 'synthetic-record',
+ name: 'Synthetic installer',
+ prefix: 'browser',
+ suffix: 'token',
+ createdAt: new Date().toISOString(),
+ },
+ },
+ });
+ if (pathname === '/api/state')
+ return route.fulfill({ json: { connectedInfrastructure: [] } });
+ return route.fulfill({ json: { data: [] } });
+ });
+ await page.goto('http://127.0.0.1:5244/browser-tests/unix-private-install.html', {
+ waitUntil: 'domcontentloaded',
+ timeout: 60_000,
+ });
+ await page
+ .getByRole('heading', { name: 'Private Unix agent installation', exact: true })
+ .waitFor({ timeout: 60_000 });
+ if (tone === 'dark')
+ await page.evaluate(() => document.documentElement.classList.add('dark'));
+ await page.getByRole('button', { name: 'Generate token', exact: true }).first().click();
+ const dialog = page.getByRole('dialog', { name: 'API token ready' });
+ await dialog.waitFor();
+ await dialog.getByRole('button', { name: 'Dismiss', exact: true }).click();
+ await dialog.waitFor({ state: 'hidden' });
+ const privateNote = page
+ .locator('span')
+ .filter({
+ hasText: /^Before running, save the separately revealed token/,
+ })
+ .first();
+ const fileText = await privateNote.innerText();
+ assert.ok(
+ fileText.includes(
+ 'This command does not prompt or delete your file. Remove it through the same trusted file path afterwards.',
+ ),
+ );
+ for (const warning of [
+ '0600',
+ '0700',
+ 'Never put the token in a GUI command field',
+ 'console or SSH instead',
+ ])
+ assert.ok(fileText.includes(warning));
+ await page
+ .getByRole('button', {
+ name: 'Copy Install from a private token file (no terminal) command',
+ exact: true,
+ })
+ .click();
+ const fileCommand = await page.evaluate(() => window.__copies.at(-1));
+ assert.ok(fileCommand.includes('/root/.config/pulse-agent/bootstrap-token'));
+ assert.ok(fileCommand.includes('--token-file'));
+ assert.ok(!fileCommand.includes('read -r'));
+ assert.ok(!fileCommand.includes(secret));
+ await privateNote.screenshot({ path: path.join(artifacts, `${engine}-private-file.png`) });
+ await page
+ .getByRole('button', { name: 'Show details for removed-fixture', exact: true })
+ .click();
+ const removalNote = page.locator('p').filter({
+ hasText:
+ 'This agent was removed from Pulse, but the agent software may still be installed on its host.',
+ });
+ const removalText = await removalNote.innerText();
+ assert.ok(
+ removalText.includes('token at its silent prompt. Never insert it into the command.'),
+ );
+ assert.ok(removalText.includes('Pulse does not run commands remotely'));
+ await page
+ .getByRole('button', {
+ name: 'Copy Linux / macOS / FreeBSD uninstall command for removed-fixture',
+ exact: true,
+ })
+ .click();
+ const removalCommand = await page.evaluate(() => window.__copies.at(-1));
+ assert.ok(removalCommand.includes('removed-agent-42'));
+ assert.ok(removalCommand.includes('--uninstall'));
+ assert.ok(!removalCommand.includes('--preflight-only'));
+ assert.ok(!removalCommand.includes(secret));
+ await removalNote.screenshot({ path: path.join(artifacts, `${engine}-removed-note.png`) });
+ assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
+ await page.screenshot({ path: path.join(artifacts, `${engine}-viewport.png`) });
+ assert.deepEqual(errors, []);
+ results.push({
+ engine,
+ version: browser.version(),
+ width,
+ tone,
+ fileText,
+ removalText,
+ privateFileCopied: true,
+ scopedRemovalCopied: true,
+ errors,
+ });
+ await browser.close();
+ browser = undefined;
+ }
+ fs.writeFileSync(
+ path.join(artifacts, 'result.json'),
+ JSON.stringify(
+ {
+ playwright: require('playwright/package.json').version,
+ results,
+ },
+ null,
+ 2,
+ ),
+ );
+ console.log(JSON.stringify({ result: 'passed', artifacts, results }));
+ } finally {
+ if (browser) await browser.close();
+ await server.close();
+ }
+})().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/frontend-modern/browser-tests/unix-private-install.tsx b/frontend-modern/browser-tests/unix-private-install.tsx
index dd4ef40c6..0e6a2ec45 100644
--- a/frontend-modern/browser-tests/unix-private-install.tsx
+++ b/frontend-modern/browser-tests/unix-private-install.tsx
@@ -91,7 +91,17 @@ const Doctor = () => {
agentIdentity: { hostname: 'doctor.example', commandsEnabled: false },
},
} as InfrastructureAgentDoctorTarget;
- return ;
+ const removedTarget = {
+ ...target,
+ key: 'agent:removed-fixture',
+ connectionId: 'agent:removed-fixture',
+ displayName: 'removed-fixture',
+ status: 'removed',
+ needsCredentialRepair: false,
+ connection: undefined,
+ diagnostic: { agentId: 'removed-agent-42', hostname: 'removed.example' },
+ } as InfrastructureAgentDoctorTarget;
+ return ;
};
render(
() => (
diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json
index 949d210a0..b6c48716f 100644
--- a/frontend-modern/browser-verification.json
+++ b/frontend-modern/browser-verification.json
@@ -1,27 +1,18 @@
{
"version": 1,
- "base_sha": "9189a7262e84b201a5af6649caf0f8cff7c6d144",
- "verified_at": "2026-10-01T13:06:33.056850Z",
+ "base_sha": "53e69c06fe24e3c07deaddb5e40418d07bb0b34b",
+ "verified_at": "2026-10-01T18:25:48.680085Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx",
- "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx",
- "frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx",
- "frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx",
- "frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx",
- "frontend-modern/src/utils/agentInstallCommand.ts"
+ "frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx"
],
"content_sha256": {
- "frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx": "416c7627303c665f956168a32e5aaf6af4268abacdd7e93045ab26cd45732d14",
- "frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx": "d2ff336c1e602794fd696aed3541684498adfcfeaf1f4e603e21a44865fce3bb",
- "frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx": "983a44871cfb908851c215a177e4a39185f7ac9017cc1b2152323b7ee396995a",
- "frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx": "c57bf5a99d457d6d21f0d22e0cc34af3618db214391fd09afce34743173a1c0f",
- "frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx": "276c89076d36670752f5d37be1895d739ae13501ea9d87d1cc3a497c7e6f5895",
- "frontend-modern/src/utils/agentInstallCommand.ts": "9bb98b1db1b49aa478f894b346479951954899d4c6049d991ec4c75730cafa30"
+ "frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx": "f9b44049c02a5e5146d882befeefe8aa5bab221a2c4a0951e529c274a416010c",
+ "frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx": "1f04fe06cfbb1874c657883e07b4c7ce0992399696b5bed05f3ea029b1f00c0f"
},
"routes": [
- "/browser-tests/unix-private-install.html",
- "/browser-tests/unix-private-install.html?optional=1"
+ "/browser-tests/unix-private-install.html"
],
"viewports": [
{
@@ -34,29 +25,27 @@
}
],
"states": [
- "Actual infrastructure installer, operations closures, Agent Doctor and token dialog with production CSS; synthetic APIs, not installed agent/appliance execution",
- "Required-auth Unix command and credential remain separate; single-line command grammar, private file no-terminal variant and separate-token instructions",
- "Canonical repair/removal identity and custom CA preserved; saved-state update has no replacement credential; removal has no new-binary preflight",
- "Optional-auth commands require no terminal or token-file; default TLS verification and monitoring-only issuance unchanged",
- "Desktop Chromium light and phone-emulated WebKit dark; four final screenshots visually inspected"
+ "Production installer and Agent Doctor with synthetic APIs, required authentication and separately issued token; not native installation or enrollment",
+ "Private-file note preserves trusted editor/upload, root 0600 file and 0700 directory, no GUI secret and console/SSH fallback; command preserves the user-owned file",
+ "Removed agent note preserves host-local detachment, silent prompt and no token in copied command; command retains exact removed-agent identity and no new-binary preflight",
+ "Desktop Chromium light and phone-emulated WebKit dark; four affected-note screenshots inspected, outer page width contained"
],
"interactions": [
- "Generate scoped host token, copy token with keyboard, Escape, reopen existing reveal without issuing another token",
- "Copy actual Linux installer, no-terminal private-file installer and Agent Doctor repair commands; raw credential absent from each clipboard result",
- "Change endpoint and custom CA; copy actual operations-state repair/removal/saved-state update commands; identity/profile/trust checked",
- "Reload with optional auth and confirm tokenless commands"
+ "Generate separate token and dismiss token dialog",
+ "Copy the private-file installation command; verify fixed private path and token secrecy",
+ "Expand removed agent and copy its scoped uninstall command; verify token secrecy and no preflight"
],
- "command": "pulse-worker-browser frontend-modern/browser-tests/unix-private-install.cjs",
+ "command": "pulse-worker-browser frontend-modern/browser-tests/unix-guidance-copy.cjs",
"browser_versions": {
"playwright": "1.56.1",
"chromium": "141.0.7390.37",
"webkit": "26.0"
},
"artifacts": [
- "/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/chromium-commands.png",
- "/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/chromium-token.png",
- "/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/result.json",
- "/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/webkit-commands.png",
- "/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/webkit-token.png"
+ "/var/lib/pulse-maintainer/worker-outputs/web-product-yqww6hbj/browser/unix/chromium-private-file.png",
+ "/var/lib/pulse-maintainer/worker-outputs/web-product-yqww6hbj/browser/unix/chromium-removed-note.png",
+ "/var/lib/pulse-maintainer/worker-outputs/web-product-yqww6hbj/browser/unix/webkit-private-file.png",
+ "/var/lib/pulse-maintainer/worker-outputs/web-product-yqww6hbj/browser/unix/webkit-removed-note.png",
+ "/var/lib/pulse-maintainer/worker-outputs/web-product-yqww6hbj/browser/unix/result.json"
]
}
diff --git a/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx b/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx
index dd6ada842..2697cd82d 100644
--- a/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx
+++ b/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx
@@ -943,7 +943,7 @@ export const InfrastructureAgentDoctorPage: Component
{(entry) => (
diff --git a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
index ac4e77f97..dbc1fafc4 100644
--- a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
+++ b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
@@ -403,7 +403,7 @@ export const buildCommandsByPlatform = (
/root/.config/pulse-agent/bootstrap-token using a trusted file editor
or upload path. The file must be root-owned with mode 0600 in a
root-owned 0700 directory. Never put the token in a GUI command
- field. This command does not prompt or delete your file; remove it through the
+ field. This command does not prompt or delete your file. Remove it through the
same trusted file path afterwards. If your GUI cannot create private files, use
console or SSH instead. Bash must already be installed.