mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
2261 lines
82 KiB
Go
2261 lines
82 KiB
Go
package unifiedresources
|
|
|
|
// Unified Resources Architecture — Code Standards Enforcement
|
|
//
|
|
// END STATE (State Read Consolidation — SRC):
|
|
//
|
|
// The Unified Resources Registry is the PRIMARY read surface for all
|
|
// internal business logic. StateSnapshot is the write/ingest buffer and
|
|
// frontend wire DTO only.
|
|
//
|
|
// Architecture:
|
|
//
|
|
// 1. StateSnapshot (models.StateSnapshot)
|
|
// WRITE-ONLY ingest buffer. Monitoring/polling populates typed arrays.
|
|
// The registry reads from it via IngestSnapshot(). Frontend reads via
|
|
// ToFrontend()/WebSocket. Internal business logic MUST NOT read from
|
|
// StateSnapshot directly — use the ReadState interface instead.
|
|
//
|
|
// 2. Unified Resources Registry (unifiedresources.ResourceRegistry)
|
|
// The canonical read model. Provides cross-source identity resolution,
|
|
// deduplication, typed views, and a normalized resource model.
|
|
// Implements the ReadState interface with typed accessor methods
|
|
// (VMs(), Containers(), Nodes(), etc.) backed by cached per-type
|
|
// indexes that are O(1) to read and invalidated per ingest cycle.
|
|
//
|
|
// Consumer package policy:
|
|
//
|
|
// - internal/ai/*, internal/api/*, internal/infradiscovery/,
|
|
// internal/servicediscovery/: MUST use ReadState. Direct state reads
|
|
// are banned for all migrated resource types.
|
|
//
|
|
// - internal/monitoring/, internal/mock/, internal/models/,
|
|
// internal/websocket/: Exempt (producer/wire-format packages).
|
|
// Monitoring remains a producer package, but snapshot-shaped export
|
|
// helpers are progressively derived from ReadState-backed canonical data
|
|
// rather than treated as state-owned truth. Workload export helpers
|
|
// (VMsSnapshot/ContainersSnapshot) now also derive from ReadState-backed
|
|
// canonical data instead of from StateSnapshot-owned guest arrays. PBS
|
|
// instance export helpers now follow the same rule via
|
|
// ReadState.PBSInstances(). Backup-alert guest lookup assembly now also
|
|
// derives VM/container identity from ReadState workload views instead of
|
|
// from snapshot-owned guest arrays. Backup polling and recovery ingest
|
|
// guest-context assembly now also derive workload node/name/type data from
|
|
// ReadState instead of from snapshot-owned guest arrays. Storage-backup
|
|
// preservation now also derives node/storage membership from
|
|
// ReadState.StoragePools() instead of from snapshot-owned storage arrays.
|
|
// Physical-disk refresh/merge paths now also derive disk, node, and linked
|
|
// host context from ReadState instead of from snapshot-owned physical-disk
|
|
// arrays.
|
|
//
|
|
// - All state.* field access patterns and GetState() calls are
|
|
// enforced as hard bans (SRC-04b). Migration is complete — zero
|
|
// direct state access remains in consumer packages.
|
|
//
|
|
// See: docs/architecture/state-read-consolidation-plan-2026-02.md
|
|
// Progress: docs/architecture/state-read-consolidation-progress-2026-02.md
|
|
//
|
|
// The tests below enforce these rules by scanning consumer packages for
|
|
// banned direct-state access patterns.
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
// readConsumerGoFiles returns the contents of all non-test .go files in the
|
|
// specified directory (relative to the repo internal/ root).
|
|
func readConsumerGoFiles(t *testing.T, relDir string) map[string]string {
|
|
t.Helper()
|
|
|
|
// Walk up from unifiedresources/ to internal/
|
|
internalDir := filepath.Join("..", relDir)
|
|
|
|
files := make(map[string]string)
|
|
err := filepath.Walk(internalDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
data, readErr := os.ReadFile(path)
|
|
if readErr != nil {
|
|
t.Fatalf("failed to read %s: %v", path, readErr)
|
|
}
|
|
files[path] = string(data)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to walk %s: %v", relDir, err)
|
|
}
|
|
return files
|
|
}
|
|
|
|
// bannedPattern defines a state access pattern that should not appear in
|
|
// consumer code because the resource type has been migrated to the registry.
|
|
type bannedPattern struct {
|
|
re *regexp.Regexp
|
|
message string
|
|
}
|
|
|
|
var migratedResourcePatterns = []bannedPattern{
|
|
{
|
|
re: regexp.MustCompile(`state\.PhysicalDisks\b`),
|
|
message: "use unified resources registry (GetByType/ListByType with ResourceTypePhysicalDisk) instead of state.PhysicalDisks",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.CephClusters\b`),
|
|
message: "use unified resources registry (GetByType/ListByType with ResourceTypeCeph) instead of state.CephClusters",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`GetCephClusters\(\)`),
|
|
message: "GetCephClusters() was removed — use unified resources registry instead",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`StorageProvider\b`),
|
|
message: "StorageProvider was removed — storage pools are accessed via unified resources registry",
|
|
},
|
|
|
|
// SRC-04b hard bans: state.* field access patterns and GetState() calls.
|
|
// Converted from ratchet ceilings (all reached 0) to hard bans on 2026-03-01.
|
|
// Consumer packages must use ReadState typed accessors exclusively.
|
|
{
|
|
re: regexp.MustCompile(`state\.VMs\b`),
|
|
message: "use ReadState.VMs() instead of state.VMs — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.Containers\b`),
|
|
message: "use ReadState.Containers() instead of state.Containers — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.Nodes\b`),
|
|
message: "use ReadState.Nodes() instead of state.Nodes — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.DockerHosts\b`),
|
|
message: "use ReadState.DockerHosts() instead of state.DockerHosts — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.Hosts\b`),
|
|
message: "use ReadState.Hosts() instead of state.Hosts — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.Storage\b`),
|
|
message: "use ReadState.StoragePools() instead of state.Storage — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.KubernetesClusters\b`),
|
|
message: "use ReadState.K8sClusters() instead of state.KubernetesClusters — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.PBSInstances\b`),
|
|
message: "use ReadState.PBSInstances() instead of state.PBSInstances — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`state\.PMGInstances\b`),
|
|
message: "use ReadState.PMGInstances() instead of state.PMGInstances — direct state access banned (SRC-04b)",
|
|
},
|
|
{
|
|
re: regexp.MustCompile(`\.GetState\(\)`),
|
|
message: "use ReadState interface instead of GetState() — direct state access banned (SRC-04b)",
|
|
},
|
|
}
|
|
|
|
// consumerPackage defines a package directory to scan and any files that are
|
|
// exempt from the banned patterns (e.g., adapters that bridge between layers).
|
|
type consumerPackage struct {
|
|
dir string
|
|
exemptFiles map[string]bool
|
|
}
|
|
|
|
var consumerPackages = []consumerPackage{
|
|
{dir: "ai/tools", exemptFiles: nil},
|
|
{dir: "ai/chat", exemptFiles: nil},
|
|
{dir: "ai", exemptFiles: nil},
|
|
{dir: "api", exemptFiles: nil},
|
|
{dir: "servicediscovery", exemptFiles: nil},
|
|
}
|
|
|
|
// TestNoDirectStateAccessForMigratedResources ensures that consumer packages
|
|
// do not directly access state.* fields, call GetState(), or use removed
|
|
// provider interfaces. All resource types have been migrated to the unified
|
|
// resources registry and ReadState interface (SRC-04b).
|
|
func TestNoDirectStateAccessForMigratedResources(t *testing.T) {
|
|
// Collect all consumer file contents, deduplicating across overlapping
|
|
// package entries (e.g., "ai" walks into "ai/tools" and "ai/chat").
|
|
// Track exempt files so per-package exemptions are preserved.
|
|
allFiles := make(map[string]string)
|
|
exemptFiles := make(map[string]bool)
|
|
for _, pkg := range consumerPackages {
|
|
for path, content := range readConsumerGoFiles(t, pkg.dir) {
|
|
allFiles[path] = content
|
|
if pkg.exemptFiles[filepath.Base(path)] {
|
|
exemptFiles[path] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
for path, content := range allFiles {
|
|
if exemptFiles[path] {
|
|
continue
|
|
}
|
|
for _, bp := range migratedResourcePatterns {
|
|
if matches := bp.re.FindAllStringIndex(content, -1); len(matches) > 0 {
|
|
for _, m := range matches {
|
|
line := 1 + strings.Count(content[:m[0]], "\n")
|
|
t.Errorf("%s:%d: %s", path, line, bp.message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneResourceClonesDockerSecurityPosture(t *testing.T) {
|
|
original := Resource{
|
|
ID: "app-container:docker-host-1:web",
|
|
Type: ResourceTypeAppContainer,
|
|
Docker: &DockerData{
|
|
HostSourceID: "docker-host-1",
|
|
Runtime: "docker",
|
|
Security: &models.DockerHostSecurity{
|
|
AuthorizationPlugins: []string{"opa"},
|
|
MutatingCommandsBlocked: true,
|
|
MutatingCommandsBlockedReason: "authorization plugin configured",
|
|
},
|
|
},
|
|
}
|
|
|
|
cloned := cloneResource(&original)
|
|
if cloned.Docker == nil || cloned.Docker.Security == nil {
|
|
t.Fatalf("clone lost docker security posture: %#v", cloned.Docker)
|
|
}
|
|
if !reflect.DeepEqual(cloned.Docker.Security, original.Docker.Security) {
|
|
t.Fatalf("docker security posture clone = %#v, want %#v", cloned.Docker.Security, original.Docker.Security)
|
|
}
|
|
|
|
cloned.Docker.Security.AuthorizationPlugins[0] = "changed"
|
|
if got := original.Docker.Security.AuthorizationPlugins[0]; got != "opa" {
|
|
t.Fatalf("docker security posture was aliased through clone, original plugin = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestCloneResourceCopiesHostThermalState(t *testing.T) {
|
|
warningLevel := 1
|
|
resource := Resource{
|
|
ID: "agent:mac-host",
|
|
Type: ResourceTypeAgent,
|
|
Agent: &AgentData{
|
|
Sensors: &HostSensorMeta{
|
|
ThermalState: &HostThermalState{
|
|
Source: "pmset",
|
|
Pressure: "constrained",
|
|
ThermalWarningLevel: &warningLevel,
|
|
LimitsPercent: map[string]int{"cpu_speed_limit": 72},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
clone := cloneResource(&resource)
|
|
clone.Agent.Sensors.ThermalState.LimitsPercent["cpu_speed_limit"] = 95
|
|
*clone.Agent.Sensors.ThermalState.ThermalWarningLevel = 2
|
|
|
|
originalState := resource.Agent.Sensors.ThermalState
|
|
if got := originalState.LimitsPercent["cpu_speed_limit"]; got != 72 {
|
|
t.Fatalf("original thermal limit = %d, want 72 after clone mutation", got)
|
|
}
|
|
if originalState.ThermalWarningLevel == nil || *originalState.ThermalWarningLevel != 1 {
|
|
t.Fatalf("original thermal warning level = %+v, want 1 after clone mutation", originalState.ThermalWarningLevel)
|
|
}
|
|
}
|
|
|
|
func TestProxmoxWorkloadActionTargetsStayBackendAuthored(t *testing.T) {
|
|
apiSource, err := os.ReadFile(filepath.Join("..", "api", "resources.go"))
|
|
if err != nil {
|
|
t.Fatalf("read api resources source: %v", err)
|
|
}
|
|
api := string(apiSource)
|
|
if !strings.Contains(api, "hostID := proxmoxLinkedAgentID(resource.Proxmox)") {
|
|
t.Fatal("Proxmox workload discovery targets must use the linked node agent ID")
|
|
}
|
|
if strings.Contains(api, "hostID := strings.TrimSpace(resource.Proxmox.NodeName)") {
|
|
t.Fatal("Proxmox workload discovery targets must not use node display names as agent IDs")
|
|
}
|
|
|
|
frontendSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "utils", "workloads.ts"))
|
|
if err != nil {
|
|
t.Fatalf("read frontend workload source: %v", err)
|
|
}
|
|
frontend := string(frontendSource)
|
|
if strings.Contains(frontend, "const agentId = (guest.node || '').trim();") &&
|
|
strings.Contains(frontend, "const resourceId = String(guest.vmid);") {
|
|
t.Fatal("frontend workload mapping must not infer Proxmox action targets from node plus VMID")
|
|
}
|
|
}
|
|
|
|
func TestDockerSwarmEvidenceGuardStaysInAdapter(t *testing.T) {
|
|
data, err := os.ReadFile("adapters.go")
|
|
if err != nil {
|
|
t.Fatalf("failed to read adapters.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
|
|
requiredSnippets := []string{
|
|
"func hasReportableDockerSwarmInfo(info *models.DockerSwarmInfo) bool",
|
|
`state == "inactive"`,
|
|
"func convertSwarm(info *models.DockerSwarmInfo) *DockerSwarmInfo",
|
|
"if !hasReportableDockerSwarmInfo(info) {",
|
|
"return nil",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("adapters.go must contain %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAPIResourcesKeepsOwnedSupplementalGapFillAndVMwareAlias(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "api", "resources.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read ../api/resources.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
|
|
requiredSnippets := []string{
|
|
"seedSources := unifiedSeedSources(seed.resources)",
|
|
"!sourceOwnedBySupplementalProvider(source, ownedSources) || unifiedSeedIncludesSource(seedSources, source)",
|
|
`case "vmware", "vmware-vsphere":`,
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("../api/resources.go must contain %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceAPIUsesCanonicalTenantUnifiedSeed(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "api", "resources.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read resources.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
|
|
if strings.Contains(source, "GetStateForTenant(") {
|
|
t.Fatalf("internal/api/resources.go must not fall back to tenant StateSnapshot seeding")
|
|
}
|
|
if !strings.Contains(source, "UnifiedResourceSnapshotForTenant(orgID)") {
|
|
t.Fatalf("internal/api/resources.go must use tenant unified resource snapshots as the canonical seed")
|
|
}
|
|
}
|
|
|
|
func TestAgentlessAvailabilityTargetKindStaysCanonical(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join("..", "config", "availability.go"): {
|
|
"AvailabilityTargetMachine AvailabilityTargetKind = \"machine\"",
|
|
"AvailabilityTargetService AvailabilityTargetKind = \"service\"",
|
|
"AvailabilityTargetDevice AvailabilityTargetKind = \"device\"",
|
|
"TargetKind AvailabilityTargetKind `json:\"targetKind,omitempty\"`",
|
|
"target.TargetKind = AvailabilityTargetKind(strings.ToLower(strings.TrimSpace(string(target.TargetKind))))",
|
|
"LinkedResourceID string `json:\"linkedResourceId,omitempty\"`",
|
|
},
|
|
filepath.Join("..", "monitoring", "availability_poller.go"): {
|
|
"TargetKind string `json:\"targetKind,omitempty\"`",
|
|
"TargetKind: string(target.TargetKind),",
|
|
"TargetKind: string(target.TargetKind),",
|
|
"tags = append(tags, string(target.TargetKind))",
|
|
"LinkedResourceID: strings.TrimSpace(target.LinkedResourceID),",
|
|
},
|
|
"types.go": {
|
|
"Availability *AvailabilityData `json:\"availability,omitempty\"`",
|
|
"TargetKind string `json:\"targetKind,omitempty\"`",
|
|
"LinkedResourceID string `json:\"linkedResourceId,omitempty\"`",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "api", "availabilityTargets.ts"): {
|
|
"export type AvailabilityTargetKind = 'machine' | 'service' | 'device';",
|
|
"targetKind?: AvailabilityTargetKind;",
|
|
},
|
|
// standalonePageModel.ts no longer references targetKind: commit
|
|
// 1e16cf34f intentionally narrowed the Machines surface to Pulse
|
|
// Agent resources only, so agentless availability targets are no
|
|
// longer classified by kind for the Machines list. The server
|
|
// contract (config/availability.go, monitoring/availability_poller.go,
|
|
// types.go, frontend-modern/src/api/availabilityTargets.ts) still
|
|
// preserves targetKind for any future UI that wants to consume it.
|
|
}
|
|
|
|
for path, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must keep agentless availability target kind contract via %q", path, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCanonicalStorageMetadataPreservesBackingPoolField(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
"types.go": {
|
|
"Pool string `json:\"pool,omitempty\"`",
|
|
},
|
|
"adapters.go": {
|
|
"Pool: storage.Pool,",
|
|
},
|
|
"views.go": {
|
|
"func (v StoragePoolView) Pool() string {",
|
|
"return v.r.Storage.Pool",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "types", "resource.ts"): {
|
|
"pool?: string;",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "hooks", "useUnifiedResources.ts"): {
|
|
"pool?: string;",
|
|
},
|
|
}
|
|
|
|
for path, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must contain %q", path, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCanonicalStorageMetadataCarriesFullZFSPoolReport(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
"types.go": {
|
|
"ZFSPool *models.ZFSPool `json:\"zfsPool,omitempty\"`",
|
|
},
|
|
"adapters.go": {
|
|
"normalized := storage.ZFSPool.NormalizeCollections()",
|
|
"ZFSPool: zfsPool,",
|
|
},
|
|
filepath.Join("..", "monitoring", "monitor.go"): {
|
|
"payload[\"zfsPool\"] = storage.ZFSPool",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "types", "resource.ts"): {
|
|
"zfsPool?: ZFSPool;",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "hooks", "useUnifiedResources.ts"): {
|
|
"zfsPool?: unknown;",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "features", "storageBackups", "storageAdapters.ts"): {
|
|
"zfsPool: storageMeta?.zfsPool ?? platformData.zfsPool,",
|
|
},
|
|
}
|
|
|
|
for path, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must contain %q", path, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCephPoolsProjectThroughCanonicalStoragePath(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join("..", "models", "models.go"): {
|
|
"func CephPoolStorageID(instanceName, poolName string) string",
|
|
"func StorageFromCephPool(cluster CephCluster, pool CephPool) Storage",
|
|
`Type: "ceph",`,
|
|
"ID: CephPoolStorageID(cluster.Instance, name),",
|
|
},
|
|
"registry.go": {
|
|
"models.CephPoolStorage(cluster)",
|
|
"rr.ingestStorage(storage)",
|
|
},
|
|
filepath.Join("..", "monitoring", "ceph.go"): {
|
|
"m.checkCephPoolStorage(cluster)",
|
|
"models.CephPoolStorage(cluster)",
|
|
`m.metricsStore.Write("storage", storage.ID, "usage", storage.Usage, timestamp)`,
|
|
"m.alertManager.CheckStorage(storage)",
|
|
},
|
|
}
|
|
|
|
for path, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must keep Ceph pool storage on canonical storage identity via %q", path, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceAPIExposesDedicatedFacetReads(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join("..", "api", "resources.go"): {
|
|
"HandleGetResourceFacets",
|
|
"HandleGetResourceTimeline",
|
|
"HandleListResourceTimeline",
|
|
"unified.ParseResourceChangeFilters(r.URL.Query()[\"kind\"], r.URL.Query()[\"sourceType\"], r.URL.Query()[\"sourceAdapter\"])",
|
|
"filters.IncludeRelated = true",
|
|
"GetRecentChangesFiltered(resourceID, since, limit, filters)",
|
|
"CountRecentChangesFiltered(resourceID, since, filters)",
|
|
"CountRecentChangesByKindFiltered(resourceID, since, filters)",
|
|
"CountRecentChangesBySourceTypeFiltered(resourceID, since, filters)",
|
|
"sourceAdapter",
|
|
`strings.TrimSuffix(r.URL.Path, "/") == "/api/resources/timeline"`,
|
|
"strings.HasSuffix(r.URL.Path, \"/facets\")",
|
|
"strings.HasSuffix(r.URL.Path, \"/timeline\")",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "api", "resources.ts"): {
|
|
"static async getGlobalTimeline(",
|
|
"`/api/resources/timeline${buildTimelineQuery(options)}`",
|
|
"static async getTimeline(",
|
|
"static async getFacetBundle(",
|
|
},
|
|
}
|
|
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must expose canonical resource timeline/facet snippet %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestActionExecutionContractStaysAPIOwned(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
// Single map entry per file: Go map literals silently drop
|
|
// duplicate keys, so two entries for the same file would
|
|
// erase the first. Keeping all actions.go pins together
|
|
// guarantees every snippet actually runs.
|
|
filepath.Join(".", "actions.go"): {
|
|
"func BeginActionExecution(record ActionAuditRecord, actor string, now time.Time)",
|
|
"func CompleteActionExecution(record ActionAuditRecord, result *ExecutionResult, actor string, now time.Time)",
|
|
"func ValidateActionExecutionStart(record ActionAuditRecord, now time.Time) error",
|
|
// ErrActionPlanDrift is the canonical error returned when the
|
|
// payload at execute time does not match the PlanHash recorded
|
|
// at approval time. Pinning it here keeps the broker contract
|
|
// honest: drift refusal cannot silently turn into another error
|
|
// kind that callers fail to detect.
|
|
"ErrActionPlanDrift = errors.New(",
|
|
// ErrResourceRemediationLocked is the canonical error returned
|
|
// when the operator has set NeverAutoRemediate=true on the
|
|
// target resource. Pin it here so the broker contract stays
|
|
// honest: per-resource lock refusal cannot silently turn into
|
|
// another error kind that callers fail to detect.
|
|
"ErrResourceRemediationLocked = errors.New(",
|
|
// ActionVerificationResult is the canonical post-execution
|
|
// read-after-write outcome carrier. The broker writes it onto
|
|
// ExecutionResult.Verification; pinning the type here keeps the
|
|
// shape stable across refactors so frontend audit surfaces and
|
|
// any other consumers can rely on it.
|
|
"type ActionVerificationResult struct",
|
|
"Verification *ActionVerificationResult `json:\"verification,omitempty\"`",
|
|
},
|
|
filepath.Join(".", "store.go"): {
|
|
"RecordActionExecutionStart(record ActionAuditRecord, event ActionLifecycleEvent) error",
|
|
"RecordActionExecutionResult(record ActionAuditRecord, event ActionLifecycleEvent) error",
|
|
"func (s *SQLiteResourceStore) RecordActionExecutionStart(record ActionAuditRecord, event ActionLifecycleEvent) error",
|
|
"func (s *SQLiteResourceStore) RecordActionExecutionResult(record ActionAuditRecord, event ActionLifecycleEvent) error",
|
|
// Audit-log secret redaction must run at every persistence
|
|
// boundary so operator-authored reasons and command output do
|
|
// not leak credentials into the plaintext SQL audit history.
|
|
// Pin the call sites in store.go to keep redaction wired even
|
|
// across future refactors.
|
|
"record = RedactAuditRecord(normalized)",
|
|
"normalizedRecord = RedactAuditRecord(normalizedRecord)",
|
|
// SQLite audit reads must also pass through the shared
|
|
// redaction boundary so legacy rows cannot expose verification
|
|
// command/output/note details after read-time normalization.
|
|
"func redactActionAuditRecordFromStore(record ActionAuditRecord) ActionAuditRecord",
|
|
"return redactActionAuditRecordFromStore(record), nil",
|
|
"func (s *SQLiteResourceStore) migrateActionAuditRedaction() error",
|
|
},
|
|
filepath.Join(".", "audit_redaction.go"): {
|
|
"func RedactAuditText(s string) string",
|
|
"func RedactAuditRecord(record ActionAuditRecord) ActionAuditRecord",
|
|
"func redactActionExecutionResult(result *ExecutionResult) *ExecutionResult",
|
|
},
|
|
filepath.Join(".", "capabilities.go"): {
|
|
"type ResourceCapability struct",
|
|
"type ResourceActionReadiness struct",
|
|
"ReasonCode string `json:\"reasonCode,omitempty\"`",
|
|
},
|
|
filepath.Join(".", "types.go"): {
|
|
"ActionReadiness []ResourceActionReadiness `json:\"actionReadiness,omitempty\"`",
|
|
},
|
|
filepath.Join("..", "api", "actions.go"): {
|
|
"type ActionExecutor interface",
|
|
"type ActionAvailabilityChecker interface",
|
|
"CheckActionAvailable(ctx context.Context, req unified.ActionRequest, resource unified.Resource) unified.ResourceActionReadiness",
|
|
"func (h *ResourceHandlers) HandleExecuteAction(w http.ResponseWriter, r *http.Request)",
|
|
"func (h *ResourceHandlers) validateActionPlanFresh(orgID string, record unified.ActionAuditRecord) error",
|
|
"func recordRefusedActionExecution(store unified.ResourceStore, record unified.ActionAuditRecord",
|
|
"func (h *ResourceHandlers) publishActionCompleted(record unified.ActionAuditRecord)",
|
|
"store.RecordActionExecutionStart(started, startEvent)",
|
|
"store.RecordActionExecutionResult(completed, doneEvent)",
|
|
"agentcapabilities.AgentErrCodeActionExecutionUnavailable",
|
|
"agentcapabilities.AgentErrCodeActionPlanDrift",
|
|
"agentcapabilities.AgentErrCodeActionExecutorUnavailable",
|
|
},
|
|
filepath.Join("..", "api", "resources.go"): {
|
|
"actionExecutor ActionExecutor",
|
|
"actionCompleted func(unified.ActionAuditRecord)",
|
|
"func (h *ResourceHandlers) SetActionExecutor(executor ActionExecutor)",
|
|
"func (h *ResourceHandlers) SetActionCompletedPublisher(",
|
|
"func (h *ResourceHandlers) applyActionAvailability(ctx context.Context, resources []unified.Resource)",
|
|
"resources[i].ActionReadiness = readinesses",
|
|
},
|
|
filepath.Join("..", "api", "agent_events.go"): {
|
|
"func (b *AgentEventBroadcaster) PublishActionCompletedRecord(record unifiedresources.ActionAuditRecord)",
|
|
"payload := AgentEventActionCompletedPayload{",
|
|
"b.PublishActionCompleted(payload)",
|
|
},
|
|
filepath.Join("..", "api", "router.go"): {
|
|
"r.resourceHandlers.SetActionCompletedPublisher(r.agentEventBroadcaster.PublishActionCompletedRecord)",
|
|
},
|
|
filepath.Join("..", "api", "router_routes_monitoring.go"): {
|
|
`"POST /api/actions/{id}/execute"`,
|
|
"RequireScope(config.ScopeAIExecute, r.withExternalAgentCapabilityActivity(",
|
|
"r.resourceHandlers.HandleExecuteAction",
|
|
},
|
|
}
|
|
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourcePolicySummaryContractOmitsRawSignals(t *testing.T) {
|
|
policy := ResourcePolicy{
|
|
Sensitivity: ResourceSensitivityRestricted,
|
|
Routing: ResourceRoutingPolicy{
|
|
Scope: ResourceRoutingScopeLocalOnly,
|
|
Redact: []ResourceRedactionHint{
|
|
ResourceRedactionHostname,
|
|
},
|
|
},
|
|
}
|
|
|
|
summary := strings.Join(ResourcePolicySummaryLines(&policy), "\n")
|
|
if strings.Contains(summary, "Raw Signals") || strings.Contains(summary, "allowCloudRawSignals") || strings.Contains(summary, "cloud_summary=") {
|
|
t.Fatalf("resource policy summary leaked raw-signals wording: %q", summary)
|
|
}
|
|
}
|
|
|
|
func TestResourceChangeFilterParsingIsOwnedByUnifiedResources(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join(".", "change_filters.go"): {
|
|
"func ParseResourceChangeFilters(kinds, sourceTypes, sourceAdapters []string) (ResourceChangeFilters, error)",
|
|
"func parseResourceChangeKinds(values []string) ([]ChangeKind, error)",
|
|
"func parseResourceChangeSourceTypes(values []string) ([]ChangeSourceType, error)",
|
|
"func parseResourceChangeSourceAdapters(values []string) ([]ChangeSourceAdapter, error)",
|
|
},
|
|
filepath.Join("..", "api", "resources.go"): {
|
|
"unified.ParseResourceChangeFilters(r.URL.Query()[\"kind\"], r.URL.Query()[\"sourceType\"], r.URL.Query()[\"sourceAdapter\"])",
|
|
},
|
|
}
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPlatformActivityTimelineContractStaysCanonical(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join(".", "changes.go"): {
|
|
`ChangeActivity ChangeKind = "activity"`,
|
|
`AdapterVMware ChangeSourceAdapter = "vmware_adapter"`,
|
|
},
|
|
filepath.Join(".", "activity_changes.go"): {
|
|
"type PlatformActivityChange struct {",
|
|
"func BuildPlatformActivityChange(resourceID string, activity PlatformActivityChange) *ResourceChange {",
|
|
"Kind: ChangeActivity,",
|
|
"SourceType: SourcePlatformEvent,",
|
|
"func platformActivityChangeID(resourceID string, sourceAdapter ChangeSourceAdapter, activityType, nativeID string, occurredAt time.Time, title, message string) string {",
|
|
},
|
|
filepath.Join(".", "change_filters.go"): {
|
|
"case string(ChangeActivity):",
|
|
"case string(AdapterVMware):",
|
|
},
|
|
filepath.Join(".", "change_presentation.go"): {
|
|
"case ChangeActivity:",
|
|
},
|
|
filepath.Join(".", "store.go"): {
|
|
"ON CONFLICT(id) DO NOTHING",
|
|
"if existing.ID == change.ID && change.ID != \"\" {",
|
|
},
|
|
filepath.Join("..", "monitoring", "monitor.go"): {
|
|
"type MonitorSupplementalChangesProvider interface {",
|
|
"recordSupplementalResourceChanges(store, supplementalChanges)",
|
|
"func recordSupplementalResourceChanges(store ResourceStoreInterface, changes []unifiedresources.ResourceChange) {",
|
|
},
|
|
filepath.Join("..", "monitoring", "vmware_poller.go"): {
|
|
"cachedChangesByOrg map[string]map[string][]unifiedresources.ResourceChange",
|
|
"p.cachedChangesByOrg[entry.orgID][entry.connectionID] = changes",
|
|
"func (p *VMwarePoller) SupplementalChanges(_ *Monitor, orgID string) []unifiedresources.ResourceChange {",
|
|
},
|
|
filepath.Join("..", "vmware", "activity_changes.go"): {
|
|
"func (p *Provider) ActivityChanges() []unifiedresources.ResourceChange {",
|
|
`ActivityType: "vmware_task",`,
|
|
`ActivityType: "vmware_event",`,
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "types", "resource.ts"): {
|
|
"| 'activity'",
|
|
"| 'vmware_adapter'",
|
|
},
|
|
filepath.Join("..", "..", "frontend-modern", "src", "utils", "resourceChangePresentation.ts"): {
|
|
"activity: {",
|
|
"vmware_adapter: {",
|
|
"case 'activity':",
|
|
},
|
|
}
|
|
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourcePolicyPresentationUsesCanonicalLabels(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join(".", "policy_presentation.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read policy_presentation.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
|
|
requiredSnippets := []string{
|
|
"ResourceSensitivityOrder",
|
|
"ResourceRoutingScopeOrder",
|
|
"ResourceRedactionHintOrder",
|
|
"ResourceSensitivityLabel(",
|
|
"ResourceRoutingScopeLabel(",
|
|
"ResourceRedactionHintLabel(",
|
|
"ResourcePolicyRedactionLabels(",
|
|
"ResourcePolicyRedactionLabelsFromCounts(",
|
|
"ResourcePolicySensitivitySummaryFromCounts(",
|
|
"ResourcePolicyRoutingSummaryFromCounts(",
|
|
"ResourcePolicySummaryLines(",
|
|
"ResourcePolicyRedacts(",
|
|
"ResourcePolicyUsesAISafeSummary(",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("policy_presentation.go must contain %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourcePolicyCloneHelperUsedByAIConsumers(t *testing.T) {
|
|
requiredFiles := []string{
|
|
filepath.Join("..", "ai", "chat", "context_prefetch.go"),
|
|
filepath.Join("..", "ai", "tools", "tools_query.go"),
|
|
filepath.Join(".", "policy_metadata.go"),
|
|
}
|
|
requiredSnippets := map[string]string{
|
|
filepath.Join("..", "ai", "chat", "context_prefetch.go"): "unifiedresources.CanonicalGovernanceMetadata(resource)",
|
|
filepath.Join("..", "ai", "tools", "tools_query.go"): "unifiedresources.CanonicalGovernanceMetadata(resource)",
|
|
filepath.Join(".", "policy_metadata.go"): "func CanonicalGovernanceMetadata(resource *Resource) (*ResourcePolicy, string)",
|
|
}
|
|
for _, name := range requiredFiles {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
snippet := requiredSnippets[name]
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAISafeSummarySuffixHelperIsOwnedByUnifiedResources(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join(".", "policy_metadata.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read policy_metadata.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"func resourceAISafeSummaryPolicySuffix(sensitivity ResourceSensitivity) string",
|
|
"return \"redacted for cloud summary\"",
|
|
"return \"local-only context\"",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("policy_metadata.go must contain %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCanonicalMetadataRefreshHelperUsedByConsumers(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join(".", "policy_metadata.go"): {
|
|
"func RefreshCanonicalMetadata(resource *Resource)",
|
|
"func RefreshCanonicalMetadataSlice(resources []Resource) []Resource",
|
|
},
|
|
filepath.Join(".", "clone.go"): {
|
|
"RefreshCanonicalMetadata(&out)",
|
|
},
|
|
filepath.Join("..", "api", "resources.go"): {
|
|
"unified.RefreshCanonicalMetadata(&resourceCopy)",
|
|
"unified.RefreshCanonicalMetadataSlice(paged)",
|
|
"unified.RefreshCanonicalMetadataSlice(children)",
|
|
},
|
|
filepath.Join("..", "ai", "resource_context.go"): {
|
|
"unifiedresources.RefreshCanonicalMetadataSlice(urp.GetInfrastructure())",
|
|
"unifiedresources.RefreshCanonicalMetadataSlice(urp.GetWorkloads())",
|
|
"unifiedresources.RefreshCanonicalMetadataSlice(urp.GetAll())",
|
|
},
|
|
filepath.Join("..", "ai", "intelligence.go"): {
|
|
"unifiedresources.RefreshCanonicalMetadataSlice(unifiedResourceProvider.GetAll())",
|
|
},
|
|
}
|
|
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourcePolicyLabelHelpersUsedByAIConsumers(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join("..", "ai", "intelligence.go"): {
|
|
"func (i *Intelligence) HasCorrelationsSource() bool",
|
|
"func (i *Intelligence) GetCorrelations(resourceID string) []*correlation.Correlation",
|
|
"func (i *Intelligence) FormatCorrelationsContext(resourceID string) string",
|
|
},
|
|
filepath.Join("..", "ai", "service.go"): {
|
|
"intel.FormatCorrelationsContext(resourceID)",
|
|
},
|
|
filepath.Join("..", "ai", "patrol_ai.go"): {
|
|
"intelFacade.GetCorrelations(\"\")",
|
|
},
|
|
filepath.Join("..", "api", "ai_intelligence_handlers.go"): {
|
|
"intel.HasCorrelationsSource()",
|
|
"intel.GetCorrelations(resourceID)",
|
|
},
|
|
filepath.Join("..", "ai", "chat", "knowledge_extractor.go"): {
|
|
"unifiedresources.ResourcePolicyLabel(",
|
|
"unifiedresources.ResourcePolicyRedactedValue(",
|
|
},
|
|
filepath.Join("..", "ai", "chat", "context_prefetch.go"): {
|
|
"tools.CanonicalDiscoveryResourceType(",
|
|
"tools.DiscoveryProviderResourceType(",
|
|
"tools.CanonicalDiscoveryTargetID(",
|
|
"unifiedresources.ResourcePolicyRequiresGovernedSummary(mention.Policy)",
|
|
"unifiedresources.FormatResourcePolicyGovernedSummary(mention.AISafeSummary, mention.Policy)",
|
|
},
|
|
filepath.Join("..", "ai", "tools", "tools_discovery.go"): {
|
|
"func CanonicalDiscoveryResourceType(raw string) string",
|
|
"func DiscoveryProviderResourceType(canonical string) string",
|
|
"func CanonicalDiscoveryTargetID(discovery *ResourceDiscoveryInfo, fallbackTargetID string) string",
|
|
},
|
|
filepath.Join("..", "ai", "resource_export.go"): {
|
|
"unifiedresources.ResourceRedactionLabelsFromHints(redactionHints)",
|
|
},
|
|
filepath.Join("..", "ai", "resource_context.go"): {
|
|
"unifiedresources.ResourcePolicyLabel(",
|
|
"unifiedresources.ResourcePolicyRedactedText(",
|
|
"unifiedresources.ResourceDisplayName(",
|
|
"unifiedresources.ResourceClusterName(",
|
|
"unifiedresources.ResourceIPSummary(",
|
|
},
|
|
filepath.Join("..", "ai", "chat", "service.go"): {
|
|
"unifiedresources.ResourcePolicyRedactedTextWithReferences(",
|
|
"unifiedresources.ResourcePolicyReference(",
|
|
},
|
|
filepath.Join("..", "unifiedresources", "unified_ai_adapter.go"): {
|
|
"ResourceDisplayName(results[i])",
|
|
"ResourceDisplayName(results[j])",
|
|
},
|
|
filepath.Join(".", "policy_presentation.go"): {
|
|
"func ResourcePolicyLabel(name, aiSafeSummary string, policy *ResourcePolicy) string",
|
|
"if ResourcePolicyRequiresGovernedSummary(policy) {",
|
|
"return ResourcePolicyRedactedLabel",
|
|
"return ResourcePolicyRequiresGovernedSummary(policy)",
|
|
"Policy: sensitivity=%s, routing=%s",
|
|
"func ResourcePolicyRedactedValue(value string, policy *ResourcePolicy, hints ...ResourceRedactionHint) string",
|
|
"func ResourcePolicyRedactedText(value string, resource Resource) string",
|
|
"func ResourcePolicyRedactedTextWithReferences(value string, resource Resource, references ...ResourcePolicyRedactionReference) string",
|
|
"func ResourcePolicyReference(value string, hints ...ResourceRedactionHint) ResourcePolicyRedactionReference",
|
|
"const ResourcePolicyRedactedLabel = \"redacted by policy\"",
|
|
"func ResourceRedactionLabelsFromHints(hints []ResourceRedactionHint) []string",
|
|
"func ResourceClusterName(resource Resource) string",
|
|
"func ResourceIPSummary(resource Resource, limit int) string",
|
|
"func ResourcePolicyRequiresGovernedSummary(policy *ResourcePolicy) bool",
|
|
"func ResourcePolicyGovernedSummaryPreamble() string",
|
|
"func ResourcePolicyGovernedSummaryFooter() string",
|
|
"func FormatResourcePolicyGovernedSummary(summary string, policy *ResourcePolicy) string",
|
|
"func ResourceDisplayName(resource Resource) string",
|
|
},
|
|
}
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPolicyPostureSummaryIsOwnedByUnifiedResources(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join(".", "policy_posture.go"): {
|
|
"type PolicyPostureSummary struct {",
|
|
"func SummarizePolicyPosture(resources []Resource) *PolicyPostureSummary",
|
|
"type ResourcePolicyPostureSummary struct {",
|
|
"func ResourcePolicyPostureContract(summary *PolicyPostureSummary) *ResourcePolicyPostureSummary",
|
|
},
|
|
filepath.Join("..", "ai", "intelligence.go"): {
|
|
"unifiedresources.PolicyPostureSummary",
|
|
"unifiedresources.SummarizePolicyPosture(",
|
|
},
|
|
filepath.Join("..", "ai", "resource_context.go"): {
|
|
"unifiedresources.SummarizePolicyPosture(allResources)",
|
|
},
|
|
filepath.Join("..", "api", "resources.go"): {
|
|
"resourcePolicyPostureAggregation(allResources)",
|
|
"unified.ResourcePolicyPostureContract(unified.SummarizePolicyPosture(canonicalResources))",
|
|
},
|
|
}
|
|
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExportDecisionHelpersUsedByAIConsumers(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "ai", "resource_export.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read resource_export.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"unifiedresources.ExportSensitivityFloor(sensitivityCounts)",
|
|
"unifiedresources.ExportDecisionForContext(sensitivityFloor, localOnlyCount, len(redactions))",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/ai/resource_export.go must pin canonical export decision snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExportDecisionHelpersCanonicalInUnifiedResources(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("privacy.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read privacy.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"func ExportSensitivityFloor(counts map[ResourceSensitivity]int) DataSensitivity",
|
|
"func ExportDecisionForContext(sensitivityFloor DataSensitivity, localOnlyCount int, redactionCount int) (ExportDecision, string)",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/unifiedresources/privacy.go must pin canonical export helper snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRootCauseEngineUsesCanonicalRelationshipModel(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "ai", "correlation", "rootcause.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read rootcause.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"type RelationshipType = unifiedresources.RelationshipType",
|
|
"type ResourceRelationship = unifiedresources.ResourceRelationship",
|
|
"GetRelationships(resourceID string) []ResourceRelationship",
|
|
"score += relationshipScore(rel.Type)",
|
|
"func relationshipScore(t RelationshipType) float64",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/ai/correlation/rootcause.go must pin canonical relationship snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceDisplayNameUsedByInfrastructureConsumers(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join("..", "monitoring", "connected_infrastructure.go"): {
|
|
"unifiedresources.ResourceDisplayName(resource)",
|
|
},
|
|
filepath.Join(".", "monitored_systems.go"): {
|
|
"if name := ResourceDisplayName(*resource); name != \"\" {",
|
|
},
|
|
}
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(string(data), snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTopLevelSystemResolverPinsCanonicalInfrastructureCounting(t *testing.T) {
|
|
requiredSnippets := map[string][]string{
|
|
filepath.Join(".", "monitored_systems.go"): {
|
|
"resolveMonitoredSystemTopLevelSystems(rs).Count()",
|
|
"ResolveTopLevelSystems(resources)",
|
|
},
|
|
filepath.Join(".", "top_level_systems.go"): {
|
|
"ResolveTopLevelSystems(resources []Resource) TopLevelSystemResolver",
|
|
"match.Confidence < HighConfidenceThreshold",
|
|
"topLevelSystemGroupingExplanation(",
|
|
"monitoredSystemCandidateAllowsHostAttachment(candidate)",
|
|
"isNonUniqueIP(normalizedIP)",
|
|
"When adding a new top-level monitored-system source, update:",
|
|
},
|
|
}
|
|
for name, snippets := range requiredSnippets {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must contain %q", name, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceTimelineStoreIndexesSupportFilteredReads(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("store.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read store.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"idx_resource_changes_kind_time",
|
|
"idx_resource_changes_source_type_time",
|
|
"idx_resource_changes_source_adapter_time",
|
|
"ensureResourceChangesIndexes",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/unifiedresources/store.go must pin filtered timeline index snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceChangeEmissionCoversRelationshipAndCapabilityChanges(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("change_emission.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read change_emission.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"change.RelatedResources = relatedResourceIDs(change.ResourceID, before, after)",
|
|
"case resourceRestartChanged(before, after):",
|
|
"case resourceIncidentChanged(before, after):",
|
|
"if !reflect.DeepEqual(before.Relationships, after.Relationships) {",
|
|
"changed = append(changed, \"relationships\")",
|
|
"if resourceIncidentChanged(before, after) {",
|
|
"changed = append(changed, \"incidents\")",
|
|
"if dockerRestartChanged(before, after) {",
|
|
"changed = append(changed, \"docker.restartCount\", \"docker.uptimeSeconds\")",
|
|
"if kubernetesRestartChanged(before, after) {",
|
|
"changed = append(changed, \"kubernetes.restarts\", \"kubernetes.uptimeSeconds\")",
|
|
"if !reflect.DeepEqual(before.Capabilities, after.Capabilities) {",
|
|
"changed = append(changed, \"capabilities\")",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/unifiedresources/change_emission.go must pin canonical relationship/capability change detection snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceChangePresentationUsesCanonicalLabels(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("change_presentation.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read change_presentation.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"func ChangeKindLabel(kind ChangeKind) string",
|
|
"func DescribeChange(change ResourceChange) ChangePresentation",
|
|
"func FormatResourceChangeSummary(change ResourceChange) string",
|
|
"func resourceRelationshipSummary(relationships []ResourceRelationship) string",
|
|
"resourceStateSummary(resource Resource) string",
|
|
"resourceRestartSummary(resource Resource) string",
|
|
"resourceIncidentSummary(resource Resource) string",
|
|
"resourceIncidentSummaryFromSlice(incidents []ResourceIncident) string",
|
|
"resourceIncidentLabel(incident ResourceIncident) string",
|
|
"resourceConfigSummary(resource Resource) string",
|
|
"KindLabel: ChangeKindLabel(change.Kind)",
|
|
"presentation.SourceType = strings.TrimSpace(string(change.SourceType))",
|
|
"presentation.SourceAdapter = strings.TrimSpace(string(change.SourceAdapter))",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/unifiedresources/change_presentation.go must pin canonical change presentation snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceRelationshipContextUsesCanonicalRelationshipPresentation(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "ai", "service.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read service.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"func (s *Service) buildResourceRelationshipContext(resourceID string) string",
|
|
"if relationshipContext := s.buildResourceRelationshipContext(resourceID); relationshipContext != \"\" {",
|
|
"Get canonical relationship context from unified resources.",
|
|
"unifiedresources.FormatResourceRelationshipContext(resource, 3)",
|
|
"unifiedresources.FormatResourceRecentChangesContext(changes, false, \"###\")",
|
|
"type canonicalResourceGetter interface {",
|
|
"intel.FormatCorrelationsContext(resourceID)",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/ai/service.go must pin canonical relationship presentation snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceRelationshipModelUsesCanonicalEdgeComment(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("relationships.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read relationships.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"// ResourceRelationship represents a typed relationship edge between two unified resources.",
|
|
"type ResourceRelationship struct {",
|
|
"const parentRelationshipDiscoverer = \"resource_registry\"",
|
|
"func ResourceRelationshipsWithCanonicalParent(resource Resource) []ResourceRelationship",
|
|
"relationshipType := parentRelationshipType(resource.Type)",
|
|
"Metadata: map[string]any{",
|
|
"func parentRelationshipType(resourceType ResourceType) RelationshipType",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/unifiedresources/relationships.go must pin canonical relationship edge snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPatrolSeedCorrelationContextUsesCanonicalSummaryFormatter(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "ai", "patrol_ai.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read patrol_ai.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"# Known Resource Correlations",
|
|
"correlation.FormatCorrelationSummary(c)",
|
|
"memory.ChangeFromUnifiedResourceChange(change)",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/ai/patrol_ai.go must pin canonical correlation presentation snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIntelligenceRecentChangesUseCanonicalSummaryFormatter(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "ai", "intelligence.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read intelligence.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"func (i *Intelligence) GetRecentChanges(since time.Time, limit int) []unifiedresources.ResourceChange",
|
|
"func (i *Intelligence) DescribeResource(resourceID string) (string, string)",
|
|
"func (i *Intelligence) HasRecentChangesSource() bool",
|
|
"unifiedresources.FormatResourceRecentChangesContext(recent, includeResourcePrefix, \"##\")",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/ai/intelligence.go must pin canonical recent-change snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMemoryChangeConversionHelpersAreSharedAcrossAIConsumers(t *testing.T) {
|
|
requiredFiles := map[string][]string{
|
|
filepath.Join("..", "ai", "patrol_ai.go"): {
|
|
"memory.ChangeFromUnifiedResourceChange(change)",
|
|
},
|
|
filepath.Join("..", "ai", "intelligence.go"): {
|
|
"memory.ResourceChangeFromMemoryChange(change)",
|
|
},
|
|
filepath.Join("..", "ai", "memory", "presentation.go"): {
|
|
"func ChangeFromUnifiedResourceChange(change unifiedresources.ResourceChange) Change",
|
|
"func ResourceChangeFromMemoryChange(change Change) unifiedresources.ResourceChange",
|
|
},
|
|
}
|
|
|
|
for path, snippets := range requiredFiles {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
source := string(data)
|
|
for _, snippet := range snippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s must pin canonical memory conversion snippet %q", path, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAIRecentChangesHandlerUsesCanonicalIntelligencePath(t *testing.T) {
|
|
data, err := os.ReadFile(filepath.Join("..", "api", "ai_intelligence_handlers.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read ai_intelligence_handlers.go: %v", err)
|
|
}
|
|
source := string(data)
|
|
requiredSnippets := []string{
|
|
"intel := patrol.GetIntelligence()",
|
|
"intel.HasRecentChangesSource()",
|
|
"intel.GetRecentChanges(since, 100)",
|
|
"intel.DescribeResource(change.ResourceID)",
|
|
"unifiedresources.FormatResourceChangeSummary(change)",
|
|
"Recent changes not initialized",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("internal/api/ai_intelligence_handlers.go must pin canonical recent-changes snippet %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourcePresentationsUseSharedDurationHelper(t *testing.T) {
|
|
requiredFiles := []string{
|
|
"change_presentation.go",
|
|
"relationship_presentation.go",
|
|
}
|
|
for _, name := range requiredFiles {
|
|
data, err := os.ReadFile(filepath.Join(name))
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", name, err)
|
|
}
|
|
if !strings.Contains(string(data), "utils.FormatDurationAgo(") {
|
|
t.Fatalf("%s must use the shared utils.FormatDurationAgo helper", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceFacetCountsAreCanonicalResourceFields(t *testing.T) {
|
|
typesData, err := os.ReadFile(filepath.Join("types.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read types.go: %v", err)
|
|
}
|
|
typesSource := string(typesData)
|
|
if !strings.Contains(typesSource, "FacetCounts ResourceFacetCounts") {
|
|
t.Fatalf("internal/unifiedresources/types.go must expose Resource.FacetCounts on the canonical resource model")
|
|
}
|
|
if !strings.Contains(typesSource, "json:\"facetCounts,omitempty\"") {
|
|
t.Fatalf("internal/unifiedresources/types.go must keep the facetCounts JSON contract")
|
|
}
|
|
if !strings.Contains(typesSource, "RecentChangeKinds map[ChangeKind]int") {
|
|
t.Fatalf("internal/unifiedresources/types.go must expose grouped timeline counts on the canonical facet model")
|
|
}
|
|
if !strings.Contains(typesSource, "RecentChangeSourceTypes map[ChangeSourceType]int") {
|
|
t.Fatalf("internal/unifiedresources/types.go must expose grouped timeline source-type counts on the canonical facet model")
|
|
}
|
|
if !strings.Contains(typesSource, "RecentChangeSourceAdapters map[ChangeSourceAdapter]int") {
|
|
t.Fatalf("internal/unifiedresources/types.go must expose grouped timeline source-adapter counts on the canonical facet model")
|
|
}
|
|
|
|
cloneData, err := os.ReadFile(filepath.Join("clone.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read clone.go: %v", err)
|
|
}
|
|
cloneSource := string(cloneData)
|
|
requiredSnippets := []string{
|
|
"out.FacetCounts = resourceFacetCounts(out)",
|
|
"func resourceFacetCounts(resource Resource) ResourceFacetCounts",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(cloneSource, snippet) {
|
|
t.Fatalf("internal/unifiedresources/clone.go must derive canonical facet counts via %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCanonicalIdentityIsCanonicalResourceField(t *testing.T) {
|
|
typesData, err := os.ReadFile(filepath.Join("types.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read types.go: %v", err)
|
|
}
|
|
typesSource := string(typesData)
|
|
requiredSnippets := []string{
|
|
"json:\"canonicalIdentity,omitempty\"",
|
|
"type CanonicalIdentity struct {",
|
|
"DisplayName string `json:\"displayName,omitempty\"`",
|
|
"Aliases []string `json:\"aliases,omitempty\"`",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(typesSource, snippet) {
|
|
t.Fatalf("internal/unifiedresources/types.go must keep the canonical identity contract snippet %q", snippet)
|
|
}
|
|
}
|
|
|
|
cloneData, err := os.ReadFile(filepath.Join("clone.go"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read clone.go: %v", err)
|
|
}
|
|
cloneSource := string(cloneData)
|
|
requiredCloneSnippets := []string{
|
|
"RefreshCanonicalMetadata(&out)",
|
|
}
|
|
for _, snippet := range requiredCloneSnippets {
|
|
if !strings.Contains(cloneSource, snippet) {
|
|
t.Fatalf("internal/unifiedresources/clone.go must preserve canonical identity via %q", snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNoLegacyHostResourceTypeSymbol prevents reintroducing the removed
|
|
// ResourceTypeHost symbol. v6 code must use ResourceTypeAgent and
|
|
// CanonicalResourceType() for legacy normalization.
|
|
func TestNoLegacyHostResourceTypeSymbol(t *testing.T) {
|
|
internalDir := filepath.Join("..")
|
|
err := filepath.Walk(internalDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
content := string(data)
|
|
if !strings.Contains(content, "ResourceTypeHost") {
|
|
return nil
|
|
}
|
|
|
|
normalizedPath := filepath.ToSlash(path)
|
|
t.Errorf("%s: legacy ResourceTypeHost symbol detected; use ResourceTypeAgent instead", normalizedPath)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to scan internal packages: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResourceParentBySourceStateRemainsInternal(t *testing.T) {
|
|
resourceType := reflect.TypeOf(Resource{})
|
|
|
|
parentIDField, ok := resourceType.FieldByName("ParentID")
|
|
if !ok {
|
|
t.Fatalf("expected Resource.ParentID field")
|
|
}
|
|
if got := parentIDField.Tag.Get("json"); got != "parentId,omitempty" {
|
|
t.Fatalf("Resource.ParentID json tag = %q, want %q", got, "parentId,omitempty")
|
|
}
|
|
|
|
parentBySourceField, ok := resourceType.FieldByName("parentBySource")
|
|
if !ok {
|
|
t.Fatalf("expected Resource.parentBySource field")
|
|
}
|
|
if parentBySourceField.IsExported() {
|
|
t.Fatalf("expected Resource.parentBySource to remain internal-only")
|
|
}
|
|
if got := parentBySourceField.Tag.Get("json"); got != "" {
|
|
t.Fatalf("expected Resource.parentBySource to have no JSON contract, got %q", got)
|
|
}
|
|
}
|
|
|
|
// TestNoLegacyMigrationHintsInRuntimeCode prevents reintroducing runtime
|
|
// messages that point removed aliases at the wrong token guidance.
|
|
func TestNoLegacyMigrationHintsInRuntimeCode(t *testing.T) {
|
|
bannedPhrases := []string{
|
|
`no longer supported; use "agent"`,
|
|
`no longer supported; use "agent:*"`,
|
|
`app_container is no longer supported; use container`,
|
|
}
|
|
|
|
internalDir := filepath.Join("..")
|
|
err := filepath.Walk(internalDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(path)
|
|
for _, phrase := range bannedPhrases {
|
|
if !strings.Contains(content, phrase) {
|
|
continue
|
|
}
|
|
t.Errorf("%s: banned legacy migration hint detected: %q", normalizedPath, phrase)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to scan internal packages: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestV6AgentRegistrationArtifactsStayCanonical prevents the release-facing
|
|
// agent registration journey and eval instructions from drifting back to
|
|
// legacy /api/state.hosts or legacy agent.type="host" assumptions.
|
|
func TestV6AgentRegistrationArtifactsStayCanonical(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
integrationRoots := []string{
|
|
filepath.Join(repoRoot, "tests", "integration", "tests"),
|
|
filepath.Join(repoRoot, "tests", "integration", "evals"),
|
|
}
|
|
globalBannedPatterns := []*regexp.Regexp{
|
|
regexp.MustCompile(`state\.hosts\b`),
|
|
regexp.MustCompile(`hosts array`),
|
|
regexp.MustCompile(`agent\.type\s*=\s*"host"`),
|
|
regexp.MustCompile(`type:\s*'host'`),
|
|
regexp.MustCompile(`"type"\s*:\s*"host"`),
|
|
regexp.MustCompile(`resourceType"\s*:\s*"host"`),
|
|
regexp.MustCompile(`resourceType:\s*'host'`),
|
|
regexp.MustCompile(`/api/resources\?type=host`),
|
|
}
|
|
|
|
for _, root := range integrationRoots {
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
switch filepath.Ext(path) {
|
|
case ".ts", ".tsx", ".md":
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
data, readErr := os.ReadFile(path)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(path)
|
|
|
|
for _, pattern := range globalBannedPatterns {
|
|
matches := pattern.FindAllStringIndex(content, -1)
|
|
for _, match := range matches {
|
|
line := 1 + strings.Count(content[:match[0]], "\n")
|
|
t.Errorf("%s:%d: banned legacy integration/eval pattern %q", normalizedPath, line, pattern.String())
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to scan %s: %v", root, err)
|
|
}
|
|
}
|
|
|
|
artifacts := []struct {
|
|
path string
|
|
requiredSnippets []string
|
|
bannedPatterns []*regexp.Regexp
|
|
}{
|
|
{
|
|
path: filepath.Join(
|
|
repoRoot,
|
|
"tests",
|
|
"integration",
|
|
"tests",
|
|
"journeys",
|
|
"04-agent-install-registration.spec.ts",
|
|
),
|
|
requiredSnippets: []string{
|
|
"state.resources",
|
|
"type: 'unified'",
|
|
},
|
|
bannedPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile(`state\.hosts\b`),
|
|
regexp.MustCompile(`hosts array`),
|
|
regexp.MustCompile(`type:\s*'host'`),
|
|
regexp.MustCompile(`"type"\s*:\s*"host"`),
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(
|
|
repoRoot,
|
|
"tests",
|
|
"integration",
|
|
"evals",
|
|
"tasks",
|
|
"agent-registration.md",
|
|
),
|
|
requiredSnippets: []string{
|
|
"resources[]",
|
|
`agent.type = "unified"`,
|
|
},
|
|
bannedPatterns: []*regexp.Regexp{
|
|
regexp.MustCompile("`hosts` array"),
|
|
regexp.MustCompile(`agent\.type\s*=\s*"host"`),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, artifact := range artifacts {
|
|
data, err := os.ReadFile(artifact.path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", artifact.path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(artifact.path)
|
|
|
|
for _, snippet := range artifact.requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required canonical v6 snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
|
|
for _, pattern := range artifact.bannedPatterns {
|
|
matches := pattern.FindAllStringIndex(content, -1)
|
|
for _, match := range matches {
|
|
line := 1 + strings.Count(content[:match[0]], "\n")
|
|
t.Errorf(
|
|
"%s:%d: banned legacy agent registration artifact pattern %q",
|
|
normalizedPath,
|
|
line,
|
|
pattern.String(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestV6AIEvalPromptsStayCanonical prevents internal AI eval scenarios from
|
|
// teaching legacy pulse_query list types after the v6 canonicalization.
|
|
func TestV6AIEvalPromptsStayCanonical(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
path := filepath.Join(repoRoot, "internal", "ai", "eval", "scenarios.go")
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(path)
|
|
|
|
requiredSnippets := []string{
|
|
`type=system-containers`,
|
|
`type=app-containers`,
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required canonical AI eval snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
|
|
bannedSnippets := []string{
|
|
`type=containers`,
|
|
`type=docker`,
|
|
}
|
|
for _, snippet := range bannedSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
continue
|
|
}
|
|
t.Errorf("%s: banned legacy AI eval snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
|
|
// TestV6AlertConfigAliasesStayStripped keeps alert-config compatibility tests
|
|
// pinned on dropping removed legacy resource-type aliases and host-era keys.
|
|
func TestV6AlertConfigAliasesStayStripped(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
path := filepath.Join(repoRoot, "internal", "alerts", "config_aliases_test.go")
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(path)
|
|
|
|
requiredSnippets := []string{
|
|
`TestAlertConfigUnmarshal_LegacyHostAliasesIgnored`,
|
|
`expected legacy timeThresholds.host to be removed`,
|
|
`expected legacy timeThresholds.docker to be removed`,
|
|
`expected legacy timeThresholds.k8s to be removed`,
|
|
`expected legacy metricTimeThresholds.host to be removed`,
|
|
`expected legacy metricTimeThresholds.dockerhost to be removed`,
|
|
`expected legacy metricTimeThresholds.kubernetes-cluster to be removed`,
|
|
`TestAlertConfigUnmarshal_CanonicalKeysTakePrecedence`,
|
|
`did not expect legacy metricTimeThresholds.docker to remain`,
|
|
`TestAlertConfigMarshal_UsesCanonicalAgentKeys`,
|
|
`did not expect legacy hostDefaults in output`,
|
|
`did not expect legacy disableAllHosts in output`,
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required alert-config alias stripping snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestV6BroadLegacyAliasCoverage keeps the broader removed alias set pinned in
|
|
// central API, AI, and alerts tests so coverage does not regress back to host-only.
|
|
func TestV6BroadLegacyAliasCoverage(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
artifacts := []struct {
|
|
path string
|
|
requiredSnippets []string
|
|
}{
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "ai_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`legacy guest rejected`,
|
|
`legacy docker rejected`,
|
|
`legacy container rejected`,
|
|
`legacy k8s alias rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "alert_adapter_test.go"),
|
|
requiredSnippets: []string{
|
|
`vm qemu rejected`,
|
|
`system container lxc rejected`,
|
|
`legacy system_container alias rejected`,
|
|
`legacy docker_container alias rejected`,
|
|
`legacy docker_service alias rejected`,
|
|
`legacy kubernetes_cluster alias rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "patrol_run_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected legacy 'qemu' alias to be rejected`,
|
|
`expected legacy 'container' alias to be rejected`,
|
|
`expected legacy 'system_container' alias to be rejected`,
|
|
`expected legacy 'docker_container' alias to be rejected`,
|
|
`expected legacy 'kubernetes_cluster' alias to be rejected`,
|
|
`expected legacy 'app_container' alias to be rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_metrics_alerts_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected error for legacy system_container resource_type`,
|
|
`expected error for legacy container resource_type`,
|
|
`expected error for legacy app_container resource_type`,
|
|
`expected error for legacy docker resource_type`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "alerts", "utility_test.go"),
|
|
requiredSnippets: []string{
|
|
`legacy host alias type key is dropped`,
|
|
`legacy docker alias type key is dropped`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, artifact := range artifacts {
|
|
data, err := os.ReadFile(artifact.path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", artifact.path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(artifact.path)
|
|
|
|
for _, snippet := range artifact.requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required broad legacy-alias coverage snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestV6ReleaseFacingAPITestsCoverLegacyHostRejection keeps release-facing API
|
|
// contract tests pinned on strict v6 behavior for removed host aliases.
|
|
func TestV6ReleaseFacingAPITestsCoverLegacyHostRejection(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
artifacts := []struct {
|
|
path string
|
|
requiredSnippets []string
|
|
}{
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "ai_handler_test.go"),
|
|
requiredSnippets: []string{
|
|
`canonicalizeChatMentionType("host")`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "ai_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`"target_type":"host"`,
|
|
`unsupported resource_type "host"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "org_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`"resourceType":"host"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "resources_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/resources?type=host`,
|
|
`unsupported type filter token(s): host`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "discovery_handlers_info_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/discovery/info/host`,
|
|
`unsupported resource type "host"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "discovery_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/discovery/type/host`,
|
|
`/api/discovery/host/host-1/host-1`,
|
|
`unsupported resource type "host"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "docker_agents_routes_more_test.go"),
|
|
requiredSnippets: []string{
|
|
`legacy hosts alias status = %d, want 400`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "reporting_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/reporting?format=pdf&resourceType=host&resourceId=h-1`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "router_version_tenant_metrics_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/metrics-store/history?resourceType=host&resourceId=agent-1&metric=cpu&range=1h`,
|
|
`unsupported resourceType "host"`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, artifact := range artifacts {
|
|
data, err := os.ReadFile(artifact.path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", artifact.path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(artifact.path)
|
|
|
|
for _, snippet := range artifact.requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required legacy-host rejection snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestV6DirectHostAliasValidatorCoverage keeps direct validator-level tests in
|
|
// place for the highest-risk host-alias rejection paths.
|
|
func TestV6DirectHostAliasValidatorCoverage(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
artifacts := []struct {
|
|
path string
|
|
requiredSnippets []string
|
|
}{
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "ai_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestNormalizeAndValidateAIExecuteTargetType_StrictCanonicalV6`,
|
|
`legacy host rejected", in: "host"`,
|
|
`TestNormalizeInvestigateAlertTargetType_StrictCanonicalV6`,
|
|
`host target type rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "org_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestIsUnsupportedOrganizationShareResourceType`,
|
|
`host unsupported`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "discovery_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestParseDiscoveryResourceType_RejectsLegacyHostAlias`,
|
|
`legacy host rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "resource_type_legacy_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestIsUnsupportedLegacyAIResourceTypeToken`,
|
|
`legacy host rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_patrol_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestHandlePatrolReportFinding_RejectsLegacyResourceTypeAliases`,
|
|
`TestHandlePatrolReportFinding_AcceptsPhysicalDiskResourceType`,
|
|
`"resource_type"] = "physical_disk"`,
|
|
`[]string{"host", "container", "docker"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_discovery_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestIsUnsupportedDiscoveryLegacyResourceTypeToken`,
|
|
`"host", "lxc"`,
|
|
`TestExecuteListDiscoveries_RejectsLegacyTypeAlias`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_query_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestExecuteGetTopology_RejectsLegacyDockerIncludeAlias`,
|
|
`expected error for legacy include alias`,
|
|
`invalid include`,
|
|
`"type": "host"`,
|
|
`invalid type: host`,
|
|
`"resource_type": "host"`,
|
|
`invalid resource_type: host`,
|
|
`[]string{"lxc", "host"}`,
|
|
`TestExecuteGetGuestConfig_RejectsLegacyResourceTypes`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "alert_adapter_test.go"),
|
|
requiredSnippets: []string{
|
|
`with_metadata_host_legacy_ignored`,
|
|
`agent host alias rejected`,
|
|
`input: "host"`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "findings_resource_type_test.go"),
|
|
requiredSnippets: []string{
|
|
`{in: "host", want: ""}`,
|
|
`TestNormalizeFindingResourceTypes_RejectsLegacyAndInfers`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "patrol_triggers_test.go"),
|
|
requiredSnippets: []string{
|
|
`AnomalyDetectedPatrolScope("res-host", "host", "cpu", 95, 50)`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "patrol_run_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected legacy 'host' alias to be rejected`,
|
|
`expected non-canonical 'docker' alias to be rejected`,
|
|
`expected non-canonical 'agent_raid' alias to be rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "patrol_triage_test.go"),
|
|
requiredSnippets: []string{
|
|
`triageResourceType("host", "qemu/100")`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "adapters", "adapters_additional_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected unsupported host resource ID to be rejected`,
|
|
`expected unsupported host query alias to be rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "knowledge", "store_extended_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected unsupported host guest ID to be rejected`,
|
|
`expected unsupported host guest ID query to be rejected`,
|
|
`expected unsupported host guest type to be rejected`,
|
|
`expected unsupported host file to remain unchanged`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "approval", "store_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected unsupported host target type to be rejected`,
|
|
`expected error for unsupported host target type input`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "metadata_provider_test.go"),
|
|
requiredSnippets: []string{
|
|
`[]string{"host", "guest", "docker", "container", "lxc", "qemu", "docker_container", "docker_service"}`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "incident_coordinator_additional_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestIncidentCoordinator_OnAnomalyDetected_CanonicalizesLegacyHostAlias`,
|
|
`expected anomaly recording resource type to be canonicalized to agent`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_metrics_alerts_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected error for legacy host resource_type`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_read_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestPulseToolExecutor_ExecuteReadRejectsLegacyAppContainerArg`,
|
|
`app_container is no longer supported; use app-container`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "tools", "tools_file_test.go"),
|
|
requiredSnippets: []string{
|
|
`Legacy AppContainer Rejected`,
|
|
`app_container is no longer supported; use app-container`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "ai", "chat", "context_prefetch_additional_test.go"),
|
|
requiredSnippets: []string{
|
|
`expected legacy host mention to be ignored`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "alerts", "utility_test.go"),
|
|
requiredSnippets: []string{
|
|
`legacy host alias rejected`,
|
|
`legacy container alias rejected`,
|
|
`legacy docker alias rejected`,
|
|
`legacy k8s alias rejected`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "metrics", "incident_recorder_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestStartRecordingCanonicalizesLegacyHostAlias`,
|
|
`expected legacy host alias to canonicalize to agent`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "resources_test.go"),
|
|
requiredSnippets: []string{
|
|
`/api/resources?type=host`,
|
|
`unsupported type filter token(s): host`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "resources_frontend_types_test.go"),
|
|
requiredSnippets: []string{
|
|
`unsupported host ignored by parser`,
|
|
`TestUnsupportedResourceTypeFilterTokensRejectsLegacyAliases`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "router_helpers_additional_test.go"),
|
|
requiredSnippets: []string{
|
|
`metadata legacy resource type ignored`,
|
|
`Metadata: map[string]interface{}{"resourceType": "host"}`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "reporting_handlers_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestNormalizeReportResourceType_RejectsLegacyAliases`,
|
|
`{"host", "container"}`,
|
|
},
|
|
},
|
|
{
|
|
path: filepath.Join(repoRoot, "internal", "api", "router_misc_additional_test.go"),
|
|
requiredSnippets: []string{
|
|
`TestNormalizeMetricsHistoryResourceType_RejectsLegacyAliases`,
|
|
`[]string{"host", "guest", "docker", "dockerhost", "dockercontainer", "system_container"}`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, artifact := range artifacts {
|
|
data, err := os.ReadFile(artifact.path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", artifact.path, err)
|
|
}
|
|
content := string(data)
|
|
normalizedPath := filepath.ToSlash(artifact.path)
|
|
|
|
for _, snippet := range artifact.requiredSnippets {
|
|
if !strings.Contains(content, snippet) {
|
|
t.Errorf("%s: missing required direct host-alias validator snippet %q", normalizedPath, snippet)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SRC-04b: Ratchet-to-hard-ban conversion completed 2026-03-01.
|
|
//
|
|
// All state.* field access patterns and GetState() calls in consumer packages
|
|
// reached ceiling 0 through SRC-03a → SRC-04g migration work. They are now
|
|
// enforced as hard bans via migratedResourcePatterns above (per-file, per-line
|
|
// error reporting). The legacy ratchet infrastructure (legacyStateRatchet type,
|
|
// legacyStateRatchets slice, TestLegacyStateAccessRatchet) has been removed.
|
|
//
|
|
// Migration changelog preserved in git history (see commits SRC-03f → SRC-04g).
|
|
|
|
func TestResourceAPIHotPathUsesSingleRegistryListSnapshot(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
path := filepath.Join(repoRoot, "internal", "api", "resources.go")
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", path, err)
|
|
}
|
|
|
|
source := string(data)
|
|
normalizedPath := filepath.ToSlash(path)
|
|
|
|
if strings.Count(source, "allResources := presentationResourcesFromRegistry(registry)") != 2 {
|
|
t.Fatalf("%s: expected HandleListResources and HandleStats to each seed exactly one presentation resource snapshot", normalizedPath)
|
|
}
|
|
if strings.Count(source, "computeResourceContractStats(allResources)") != 2 {
|
|
t.Fatalf("%s: expected canonical aggregations to reuse the seeded presentation snapshot in both handlers", normalizedPath)
|
|
}
|
|
if strings.Contains(source, "computeResourceContractByType(registry.List())") {
|
|
t.Fatalf("%s: duplicate registry.List() hot-path aggregation detected", normalizedPath)
|
|
}
|
|
}
|
|
|
|
func TestCanonicalResourceOrderingContractsStayShared(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
resourcesPath := filepath.Join(repoRoot, "internal", "api", "resources.go")
|
|
registryPath := filepath.Join(repoRoot, "internal", "unifiedresources", "registry.go")
|
|
|
|
resourcesSource, err := os.ReadFile(resourcesPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", resourcesPath, err)
|
|
}
|
|
registrySource, err := os.ReadFile(registryPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", registryPath, err)
|
|
}
|
|
|
|
if !strings.Contains(string(resourcesSource), "unified.CompareResourcesByCanonicalName") {
|
|
t.Fatalf("%s: /api/resources must route deterministic list ordering through unified.CompareResourcesByCanonicalName", filepath.ToSlash(resourcesPath))
|
|
}
|
|
if !strings.Contains(string(registrySource), "sortResourcesByName(out)") {
|
|
t.Fatalf("%s: ResourceRegistry.List() must normalize map iteration through sortResourcesByName(out)", filepath.ToSlash(registryPath))
|
|
}
|
|
if !strings.Contains(string(registrySource), "sortNamedResourceViewsByName(rr.cachedStorage)") {
|
|
t.Fatalf("%s: cached unified resource views must share the canonical deterministic name ordering helper", filepath.ToSlash(registryPath))
|
|
}
|
|
}
|
|
|
|
func TestBroadcastStateUsesSharedCanonicalResourceContract(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
typesPath := filepath.Join(repoRoot, "internal", "unifiedresources", "types.go")
|
|
resourcesPath := filepath.Join(repoRoot, "internal", "api", "resources.go")
|
|
monitorPath := filepath.Join(repoRoot, "internal", "monitoring", "monitor.go")
|
|
|
|
typesSource, err := os.ReadFile(typesPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", typesPath, err)
|
|
}
|
|
resourcesSource, err := os.ReadFile(resourcesPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", resourcesPath, err)
|
|
}
|
|
monitorSource, err := os.ReadFile(monitorPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", monitorPath, err)
|
|
}
|
|
|
|
if !strings.Contains(string(typesSource), "func ContractResourceType(resource Resource) ResourceType {") {
|
|
t.Fatalf("%s: unified resource contract type helper must remain canonical", filepath.ToSlash(typesPath))
|
|
}
|
|
if !strings.Contains(string(resourcesSource), "return unified.ContractResourceType(r)") {
|
|
t.Fatalf("%s: /api/resources must derive external resource types from unified.ContractResourceType", filepath.ToSlash(resourcesPath))
|
|
}
|
|
|
|
requiredMonitorSnippets := []string{
|
|
"unifiedView := m.currentUnifiedStateView()",
|
|
"return string(unifiedresources.ContractResourceType(resource))",
|
|
"unifiedresources.ResourceDisplayName(resource)",
|
|
"unifiedresources.ResourceClusterName(resource)",
|
|
}
|
|
for _, snippet := range requiredMonitorSnippets {
|
|
if !strings.Contains(string(monitorSource), snippet) {
|
|
t.Fatalf("%s: websocket/state broadcast must contain %q", filepath.ToSlash(monitorPath), snippet)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloneHostSensorMetaKeepsGPUSensorsIsolated(t *testing.T) {
|
|
temperature := 63.0
|
|
utilization := 42.0
|
|
usedBytes := int64(4 * 1024 * 1024 * 1024)
|
|
totalBytes := int64(16 * 1024 * 1024 * 1024)
|
|
source := &HostSensorMeta{
|
|
GPU: []HostGPUSensor{
|
|
{
|
|
ID: "0",
|
|
Name: "NVIDIA RTX A6000",
|
|
TemperatureCelsius: &temperature,
|
|
UtilizationPercent: &utilization,
|
|
MemoryUsedBytes: &usedBytes,
|
|
MemoryTotalBytes: &totalBytes,
|
|
},
|
|
},
|
|
}
|
|
|
|
clone := cloneHostSensorMeta(source)
|
|
if clone == nil || len(clone.GPU) != 1 {
|
|
t.Fatalf("GPU clone = %+v, want one sensor", clone)
|
|
}
|
|
temperature = 10
|
|
utilization = 1
|
|
usedBytes = 1
|
|
totalBytes = 2
|
|
source.GPU[0].Name = "mutated"
|
|
|
|
gpu := clone.GPU[0]
|
|
if gpu.Name != "NVIDIA RTX A6000" {
|
|
t.Fatalf("GPU name clone = %q, want original name", gpu.Name)
|
|
}
|
|
if gpu.TemperatureCelsius == nil || *gpu.TemperatureCelsius != 63 {
|
|
t.Fatalf("GPU temperature clone = %#v, want 63", gpu.TemperatureCelsius)
|
|
}
|
|
if gpu.UtilizationPercent == nil || *gpu.UtilizationPercent != 42 {
|
|
t.Fatalf("GPU utilization clone = %#v, want 42", gpu.UtilizationPercent)
|
|
}
|
|
if gpu.MemoryUsedBytes == nil || *gpu.MemoryUsedBytes != int64(4*1024*1024*1024) {
|
|
t.Fatalf("GPU used memory clone = %#v, want 4 GiB", gpu.MemoryUsedBytes)
|
|
}
|
|
if gpu.MemoryTotalBytes == nil || *gpu.MemoryTotalBytes != int64(16*1024*1024*1024) {
|
|
t.Fatalf("GPU total memory clone = %#v, want 16 GiB", gpu.MemoryTotalBytes)
|
|
}
|
|
}
|
|
|
|
func TestCloneVMwareDataKeepsNestedRuntimeDetailsIsolated(t *testing.T) {
|
|
repoRoot := filepath.Join("..", "..")
|
|
clonePath := filepath.Join(repoRoot, "internal", "unifiedresources", "clone.go")
|
|
|
|
cloneSource, err := os.ReadFile(clonePath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read %s: %v", clonePath, err)
|
|
}
|
|
source := string(cloneSource)
|
|
|
|
requiredSnippets := []string{
|
|
"out.SnapshotTree = cloneVMwareSnapshotDataSlice(in.SnapshotTree)",
|
|
"out.NetworkAdapters = cloneVMwareNetworkAdapterDataSlice(in.NetworkAdapters)",
|
|
"out.VirtualDisks = cloneVMwareVirtualDiskDataSlice(in.VirtualDisks)",
|
|
"out.ClusterHAEnabled = cloneBoolPtr(in.ClusterHAEnabled)",
|
|
"out.ClusterDRSEnabled = cloneBoolPtr(in.ClusterDRSEnabled)",
|
|
"out.NetworkHostNames = cloneStringSlice(in.NetworkHostNames)",
|
|
"out.NetworkVMNames = cloneStringSlice(in.NetworkVMNames)",
|
|
"out.Tools = cloneVMwareToolsData(in.Tools)",
|
|
"out.Hardware = cloneVMwareVMHardwareData(in.Hardware)",
|
|
"out[i].CreatedAt = cloneTimePtr(in[i].CreatedAt)",
|
|
"out[i].Children = cloneVMwareSnapshotDataSlice(in[i].Children)",
|
|
"out[i].PCISlotNumber = cloneInt64Ptr(in[i].PCISlotNumber)",
|
|
"out[i].CapacityBytes = cloneInt64Ptr(in[i].CapacityBytes)",
|
|
"out.GuestRebootComponents = cloneStringSlice(in.GuestRebootComponents)",
|
|
"out.BootDevices = cloneVMwareBootDeviceDataSlice(in.BootDevices)",
|
|
"out[i].Disks = cloneStringSlice(in[i].Disks)",
|
|
}
|
|
for _, snippet := range requiredSnippets {
|
|
if !strings.Contains(source, snippet) {
|
|
t.Fatalf("%s: VMware clone isolation must include %q", filepath.ToSlash(clonePath), snippet)
|
|
}
|
|
}
|
|
}
|