From a4602fe73101d06b756b02345110de2d46ada616 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 9 Jun 2026 16:08:39 +0100 Subject: [PATCH] Surface discovery abstention reason instead of an Unknown Service dead-end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When discovery has no in-guest command evidence, the backend abstains (empty service, confidence 0) rather than confabulate a service, and explains why in ai_reasoning (e.g. 'Discovery could not run commands on this resource... Enable Pulse Commands (Settings → Infrastructure)...'). The Discovery tab discarded that explanation: hasMeaningfulDiscoveryContext() ignores ai_reasoning, so an abstention failed the render gate and fell into the generic 'Unknown Service / Discovery completed but couldnt identify a known service' state with no reason and no next step. After a 'Discovery complete' run, the user saw a dead-end. The not-identified branch now surfaces ai_reasoning (the actionable explanation) and a clickable 'Open Settings → Infrastructure' link when command execution isn't confirmed enabled, with a neutral 'Service not identified' heading. Identified guests are unaffected (they render via the valid-discovery block). Regression test added; verified live against a mock guest that abstains. --- .../src/components/Discovery/DiscoveryTab.tsx | 22 +++++++-- .../Discovery/__tests__/DiscoveryTab.test.tsx | 49 +++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx index 515e76817..f2bc679a0 100644 --- a/frontend-modern/src/components/Discovery/DiscoveryTab.tsx +++ b/frontend-modern/src/components/Discovery/DiscoveryTab.tsx @@ -700,10 +700,26 @@ export const DiscoveryTab: Component = (props) => { d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> -

Unknown Service

-

- Discovery completed but couldn't identify a known service. +

+ {discovery()?.ai_reasoning ? 'Service not identified' : 'Unknown Service'}

+

+ {/* The backend abstains (empty service, confidence 0) rather than + confabulate a service when it has no in-guest command evidence, + and explains why in ai_reasoning. Surface that explanation + instead of a dead-end "couldn't identify a known service", so a + completed-but-empty scan tells the user what happened and how to + get real results. */} + {discovery()?.ai_reasoning || + "Discovery completed but couldn't identify a known service."} +

+ +

+ + Open {commandSettingsTarget.label} + +

+

Last scanned: {formatDiscoveryAge(discovery()!.updated_at)} diff --git a/frontend-modern/src/components/Discovery/__tests__/DiscoveryTab.test.tsx b/frontend-modern/src/components/Discovery/__tests__/DiscoveryTab.test.tsx index 6a96e1fa3..fba851149 100644 --- a/frontend-modern/src/components/Discovery/__tests__/DiscoveryTab.test.tsx +++ b/frontend-modern/src/components/Discovery/__tests__/DiscoveryTab.test.tsx @@ -139,6 +139,55 @@ describe('DiscoveryTab', () => { expect(screen.queryByText('Unknown Container')).toBeNull(); }); + it('surfaces the abstention reason and a settings link when a completed scan had no command evidence', async () => { + // The backend abstains (empty service, confidence 0) rather than confabulate + // a service when it has no in-guest command evidence, and explains why in + // ai_reasoning. The tab must surface that explanation + an actionable settings + // link instead of a dead-end "couldn't identify a known service". + vi.mocked(discoveryApi.getDiscovery).mockResolvedValue({ + id: 'system-container:minipc:112', + resource_type: 'system-container', + resource_id: '112', + target_id: 'minipc', + hostname: 'debian-go', + service_type: '', + service_name: '', + service_version: '', + category: 'unknown', + cli_access: 'pct exec 112 -- /bin/bash', + facts: [], + config_paths: [], + data_paths: [], + log_paths: [], + ports: [], + user_notes: '', + user_secrets: {}, + confidence: 0, + ai_reasoning: + 'Discovery could not run commands on this resource, so its service was not identified. Enable "Pulse Commands" for the host agent (Settings → Infrastructure) to run real discovery instead of guessing.', + discovered_at: '2026-06-09T00:00:00Z', + updated_at: '2026-06-09T00:00:00Z', + scan_duration: 0, + }); + + render(() => ( + + )); + + expect(await screen.findByText('Service not identified')).toBeInTheDocument(); + expect(screen.getByText(/Discovery could not run commands on this resource/i)).toBeInTheDocument(); + const settingsLink = screen.getByRole('link', { name: /Open Settings → Infrastructure/i }); + expect(settingsLink).toHaveAttribute('href', '/settings/infrastructure'); + expect( + screen.queryByText("Discovery completed but couldn't identify a known service"), + ).toBeNull(); + }); + it('shows a drawer run action and triggers discovery for the current resource', async () => { const discovered: ResourceDiscovery = { id: 'discovery-1',