Files
pulse/internal/agentcapabilities/patrol_scope_tools.go
T
rcourtman 39f988b220 Remove AI Discovery
AI Discovery ran a separate model-backed service scanner beside Patrol
and the Assistant: its own store, scheduler, model route, API, tool and
drawer UI. No telemetry counter ever measured its use (the telemetry
discovery_enabled flag OR'd it with network subnet discovery), the
maintainer judged it a flop, and the context it produced is already
available to the models through pulse_query, pulse_read and the
unified-resource context sections.

This deletes internal/servicediscovery, internal/ai/infradiscovery, the
pulse_discovery tool, the /api/discovery routes, the mock Discovery
fixtures, the Discovery tabs in the resource, guest, node and Docker host
drawers, the identified-service cards, readiness badges, provenance
markers, suggested web-interface URLs, the Workloads AI Context column,
the chat "Discovery is off" hint and the AI Service Context settings page.
The old settings route redirects to Assistant settings. The model-bound
sanitizer allow-list existed only to pass Discovery context through, so it
goes too, along with the CopyableCodeRow primitive that only the
Discovery tab used.

Network subnet discovery (/api/discover, the Infrastructure discovery
settings) and the discoveryTarget agent coordinates are separate features
and stay. Stored discovery_enabled, discovery_interval_hours and
discovery_model AI settings are ignored on load; the settings API decoder
is lenient, so older frontends saving those fields still succeed.
2026-10-11 22:20:25 +01:00

121 lines
4.1 KiB
Go

package agentcapabilities
import "strings"
// IsPatrolInfrastructureEvidenceToolName reports whether a provider tool can
// return fresh canonical or provider-observed infrastructure evidence for a
// Patrol investigation. Planning, synthesis, retained knowledge, and finding
// lifecycle reads are deliberately outside this closed vocabulary: they may
// help explain evidence, but cannot satisfy the grounding gate or unlock a
// typed remediation proposal by themselves.
func IsPatrolInfrastructureEvidenceToolName(name string) bool {
switch strings.TrimSpace(name) {
case PulseQueryToolName,
PulseMetricsToolName,
PulseStorageToolName,
PulseDockerToolName,
PulseKubernetesToolName,
PulseReadToolName,
PulsePMGToolName:
return true
default:
return false
}
}
// PatrolEvidenceToolNamesForResourceTypes returns the smallest useful
// read-only evidence surface for an exact, core-resolved Patrol resource
// scope. The bool is false when the scope is empty or contains an unknown
// resource type; callers must then retain their normal governed profile
// rather than guessing from prompt text or accidentally hiding a capability.
//
// pulse_query and pulse_read are the cross-resource escape hatches: a focused
// investigation can still follow canonical topology and inspect a causal
// dependency without receiving every subsystem-specific schema up front.
func PatrolEvidenceToolNamesForResourceTypes(resourceTypes []string) ([]string, bool) {
return patrolEvidenceToolNamesForResourceTypes(resourceTypes, true)
}
func patrolEvidenceToolNamesForResourceTypes(resourceTypes []string, includeDeepRead bool) ([]string, bool) {
if len(resourceTypes) == 0 {
return nil, false
}
names := []string{PulseQueryToolName}
seen := map[string]bool{
PulseQueryToolName: true,
}
if includeDeepRead {
names = append(names, PulseReadToolName)
seen[PulseReadToolName] = true
}
add := func(name string) {
if !seen[name] {
seen[name] = true
names = append(names, name)
}
}
known := 0
for _, raw := range resourceTypes {
resourceType := strings.ToLower(strings.TrimSpace(raw))
if resourceType == "" {
continue
}
known++
switch resourceType {
case "app-container", "app_container", "docker-container", "docker_container", "docker-host", "docker":
add(PulseDockerToolName)
case "storage", "storage-pool", "physical-disk", "physical_disk", "pbs", "backup":
add(PulseMetricsToolName)
add(PulseStorageToolName)
case "k8s-cluster", "k8s-node", "k8s-pod", "k8s-deployment", "kubernetes", "kubernetes-cluster":
add(PulseKubernetesToolName)
case "pmg":
add(PulsePMGToolName)
case "network-endpoint", "endpoint":
// pulse_query and pulse_read already cover endpoint evidence.
case "agent", "host", "truenas", "node", "vm", "system-container", "container", "service":
add(PulseMetricsToolName)
default:
return nil, false
}
}
if known == 0 {
return nil, false
}
return names, true
}
// PatrolDetectionToolNamesForResourceTypes composes the scoped evidence
// surface with the finding lifecycle owned by the detection profile. It may
// only reduce the already-governed profile manifest.
func PatrolDetectionToolNamesForResourceTypes(resourceTypes []string) ([]string, bool) {
// Watch owns symptom detection from the canonical seed and provider reads.
// Agent-routed file/log/exec inspection belongs to the separate finding
// investigation, where runtime availability can be evaluated explicitly.
names, ok := patrolEvidenceToolNamesForResourceTypes(resourceTypes, false)
if !ok {
return nil, false
}
return append(names,
PatrolGetFindingsToolName,
PatrolReportFindingToolName,
PatrolAssessFindingToolName,
), true
}
// PatrolInvestigationToolNamesForResourceTypes composes scoped evidence with
// the side-effect-free capability lookup and typed proposal sink. Planning,
// approval, execution, and verification remain outside this surface.
func PatrolInvestigationToolNamesForResourceTypes(resourceTypes []string) ([]string, bool) {
names, ok := PatrolEvidenceToolNamesForResourceTypes(resourceTypes)
if !ok {
return nil, false
}
return append(names,
PatrolActionCapabilitiesToolName,
PatrolProposeActionToolName,
), true
}