mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-16 20:43:29 +00:00
Fix tenant-scoped service discovery execution
This commit is contained in:
parent
b2dd6abfd8
commit
dfcfe3fbd1
4 changed files with 72 additions and 7 deletions
|
|
@ -5198,6 +5198,14 @@ single-resource discovery, and `pulse_discovery` refreshes before any
|
|||
`discovery_interval_hours: 0` is the only manual-command-scan mode: recurring
|
||||
scans stay stopped, but explicit admin-triggered refreshes may use the
|
||||
hardcoded trusted catalog.
|
||||
Each per-organization AI service now passes its organization-pinned
|
||||
`AgentServer` interface directly into `internal/ai/discovery_adapter.go`.
|
||||
Deep discovery does not recover the concrete global `agentexec.Server` or
|
||||
silently drop command execution when the runtime supplies an
|
||||
`agentexec.OrganizationServer`; connected-agent lookup and command dispatch
|
||||
therefore remain on the same tenant-scoped view. The interface-backed service
|
||||
regression in `internal/ai/service_test.go` requires a real deep-discovery run
|
||||
to dispatch the trusted probe catalog and retain its command evidence.
|
||||
The value boundary for keeping Discovery is observed workload context:
|
||||
Assistant and Patrol may consume normalized service name, version, endpoint,
|
||||
port, config path, data path, log path, bind-mount, confidence, and user-note
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@ const (
|
|||
minDiscoveryCommandTimeoutSeconds = 1
|
||||
)
|
||||
|
||||
// discoveryCommandAdapter adapts agentexec.Server to servicediscovery.CommandExecutor
|
||||
// discoveryCommandAdapter adapts the AI service's organization-scoped agent
|
||||
// server view to servicediscovery.CommandExecutor.
|
||||
type discoveryCommandAdapter struct {
|
||||
server *agentexec.Server
|
||||
server AgentServer
|
||||
}
|
||||
|
||||
// newDiscoveryCommandAdapter creates a new adapter
|
||||
func newDiscoveryCommandAdapter(server *agentexec.Server) *discoveryCommandAdapter {
|
||||
func newDiscoveryCommandAdapter(server AgentServer) *discoveryCommandAdapter {
|
||||
return &discoveryCommandAdapter{server: server}
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +98,12 @@ func (a *discoveryCommandAdapter) IsAgentConnected(agentID string) bool {
|
|||
if a.server == nil {
|
||||
return false
|
||||
}
|
||||
return a.server.IsAgentConnected(agentID)
|
||||
for _, agent := range a.server.GetConnectedAgents() {
|
||||
if agent.AgentID == agentID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nonNilContext(ctx context.Context) context.Context {
|
||||
|
|
|
|||
|
|
@ -608,10 +608,10 @@ func (s *Service) initDiscoveryServiceLocked() {
|
|||
return
|
||||
}
|
||||
|
||||
// Create command executor adapter (wraps agentexec.Server)
|
||||
// Create a command executor adapter over the organization-scoped server view.
|
||||
var cmdExecutor servicediscovery.CommandExecutor
|
||||
if agentSrv, ok := s.agentServer.(*agentexec.Server); ok {
|
||||
cmdExecutor = newDiscoveryCommandAdapter(agentSrv)
|
||||
if s.agentServer != nil {
|
||||
cmdExecutor = newDiscoveryCommandAdapter(s.agentServer)
|
||||
}
|
||||
|
||||
// Create deep scanner
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ import (
|
|||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/providers"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/servicediscovery"
|
||||
unifiedresources "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/aicontracts"
|
||||
)
|
||||
|
|
@ -353,6 +356,54 @@ func TestService_LoadConfig_SyncsInfraDiscoveryLifecycle(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestService_DeepDiscoveryUsesInterfaceBackedAgentServer(t *testing.T) {
|
||||
var commandCalls atomic.Int32
|
||||
agentServer := &mockAgentServer{
|
||||
agents: []agentexec.ConnectedAgent{{
|
||||
AgentID: "agent-1",
|
||||
Hostname: "home-assistant",
|
||||
Platform: "linux",
|
||||
}},
|
||||
executeFunc: func(_ context.Context, _ string, cmd agentexec.ExecuteCommandPayload) (*agentexec.CommandResultPayload, error) {
|
||||
commandCalls.Add(1)
|
||||
return &agentexec.CommandResultPayload{
|
||||
RequestID: cmd.RequestID,
|
||||
Success: true,
|
||||
Stdout: "probe output",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewService(config.NewConfigPersistence(t.TempDir()), agentServer)
|
||||
defer svc.Stop()
|
||||
registry := unifiedresources.NewRegistry(nil)
|
||||
registry.IngestSnapshot(models.StateSnapshot{})
|
||||
svc.SetReadState(registry)
|
||||
|
||||
discoveryService := svc.GetDiscoveryService()
|
||||
if discoveryService == nil {
|
||||
t.Fatal("expected deep discovery service to be initialized")
|
||||
}
|
||||
discoveryService.SetCommandScanningEnabled(true)
|
||||
|
||||
result, err := discoveryService.DiscoverResource(context.Background(), servicediscovery.DiscoveryRequest{
|
||||
ResourceType: servicediscovery.ResourceTypeAgent,
|
||||
ResourceID: "agent-1",
|
||||
TargetID: "agent-1",
|
||||
Hostname: "home-assistant",
|
||||
Force: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DiscoverResource() error = %v", err)
|
||||
}
|
||||
if commandCalls.Load() == 0 {
|
||||
t.Fatal("expected deep discovery to dispatch commands through the interface-backed agent server")
|
||||
}
|
||||
if len(result.RawCommandOutput) == 0 {
|
||||
t.Fatal("expected deep discovery to retain command evidence")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_GetCostSummary_NoStore(t *testing.T) {
|
||||
svc := NewService(nil, nil)
|
||||
svc.costStore = nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue